Abílio Azevedo.

Lista de Tarefas Django Python

Cover Image for Lista de Tarefas Django Python
Abílio Azevedo
Abílio Azevedo

Vamos lá! Primeiro você precisa instalar o Python, você pode gerenciar múltiplas versões usando o pyenv:

brew update
brew install pyenv
pyenv install 3.12.0

O Django é um popular framework web Python de código aberto. Foi lançado em 2005 e é mantido pela Django Software Foundation.

  • Baseado no padrão model-view-controller (MVC). Isso ajuda a manter a lógica de negócios, a lógica de apresentação e a lógica de processamento de dados separadas.
  • Inclui um ORM (Object Relational Mapper) que permite abstrair e mapear facilmente objetos do Python para bancos de dados.
  • Oferece um sistema de templates poderoso baseado em sintaxe simples que permite a separação entre lógica e apresentação.
  • Inclui um sistema de autenticação de usuários e gerenciamento de sessão pronto para uso.
  • Administração automática do site disponível pronta para uso para gerenciar facilmente conteúdo e usuários.
  • Grande comunidade, muitos pacotes de terceiros e boa documentação disponível. Para instalar o Django, você pode seguir a documentação.
> python -m pip install Django
> python -m django --version  
4.2.7

O Django Rest Framework (DRF) é um pacote popular que se integra ao Django para facilitar a construção de APIs.

  • O DRF é construído sobre o Django e se aproveita de muitos de seus recursos, como o sistema de autenticação e o ORM.
  • Ele permite rapidamente criar APIs web escaláveis utilizando conceitos do Django como models e views.
  • Adiciona recursos específicos para construção de APIs, como serialização de dados, controle de requisições HTTP, paginação e limitação de requisições.
  • Usa classes baseadas em views genéricas para lidar com requisições comuns de API como lista, detalhe, criação, exclusão e atualização de dados.
  • Fornece serializadores que traduzem entre representações complexas em Python e representações nativas da web como JSON.
  • Inclui ferramentas de autenticação e permissão específicas para APIs.

Para gerenciar os pacotes de nossa aplicação vamos usar o gerenciador de pacotes chamado Poetry.

Agora vamos começar nosso projeto

mkdir todo_list
cd todo_list

Vamos inicializar o Poetry:

poetry init -n

E adicionar o Django, o DRF e um pacote para trabalharmos com JWT:

poetry add django djangorestframework djangorestframework-simplejwt

Agora vamos ativar o ambiente shell do Poetry:

poetry shell

E criamos nosso projeto:

django-admin startproject todo_project .

No Django podemos ter diferentes apps/módulos e por isso vamos criar nossos apps de tarefas (todos) e de gestão de usuários (users)

mkdir todo_project/auth
python manage.py startapp todos
python manage.py startapp users

Adicione os apps e rest_framework ao array INSTALLED_APPS em settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', #ADDED
    'auth', #ADDED
    'users', #ADDED
]

Nesse passo vamos efetuar as migrations

poetry run python manage.py migrate

Você verá que um banco de dados sqlite será criado na raiz do projeto.

E pode rodar a aplicação pelo comando:

python manage.py runserver

Agora você pode acessar a aplicação: http://127.0.0.1:8000 Django Start

Você também pode acessar o painel administrativo, mas antes é preciso criar um superuser:

poetry run python manage.py createsuperuser 

Django Admin http://127.0.0.1:8000/admin

APP USERS

Primeiramente, vamos criar um model de User customizado para caso necessário expandirmos as propriedades defaults. O arquivo models.py ficará assim:

from django.contrib.auth.models import AbstractUser
from .managers import CustomUserManager
from django.contrib.auth.models import UserManager

class CustomUserManager(UserManager):
  pass;

class CustomUser(AbstractUser):
    objects = CustomUserManager()

    def __str__(self):
        return f"Username: {self.username} <Email: {self.email}>"

Teremos dois serializers, um para criar o user e outro para mudar a senha do user, portanto, crie o arquivo serializers.py:

from rest_framework import serializers
from .models import CustomUser

class RegistrationSerializer(serializers.ModelSerializer):
    password2 = serializers.CharField(style={"input_type": "password"}, write_only=True)

    class Meta:
        model = CustomUser
        fields = ['username','email', 'password', 'password2']
        extra_kwargs = {
            'password': {'write_only': True}
        }

    def save(self):
        user = CustomUser(username=self.validated_data['username'],email=self.validated_data['email'])
        password = self.validated_data['password']
        password2 = self.validated_data['password2']
        if password != password2:
            raise serializers.ValidationError({'password': 'Passwords must match.'})
        user.set_password(password)
        user.save()
        return user

class PasswordChangeSerializer(serializers.Serializer):
    current_password = serializers.CharField(style={"input_type": "password"}, required=True)
    new_password = serializers.CharField(style={"input_type": "password"}, required=True)

    def validate_current_password(self, value):
        if not self.context['request'].user.check_password(value):
            raise serializers.ValidationError({'current_password': 'Does not match'})
        return value

Nas Views da aplicação user vamos criar nosso CRUD de usuário:

from django.contrib.auth import authenticate, login, logout
from rest_framework import status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import  Response
from rest_framework.views import APIView
from .serializers import RegistrationSerializer, PasswordChangeSerializer
from rest_framework_simplejwt.tokens import RefreshToken

def get_tokens_for_user(user):
    refresh = RefreshToken.for_user(user)

    return {
        'refresh': str(refresh),
        'access': str(refresh.access_token),
    }

class RegistrationView(APIView):
    def post(self, request):
        serializer = RegistrationSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class LoginView(APIView):
    def post(self, request):
        if 'username' not in request.data or 'password' not in request.data:
            return Response({'msg': 'Credentials missing'},          status=status.HTTP_400_BAD_REQUEST)
        username = request.data.get('username')
        password = request.data.get('password')
        user = authenticate(request, username=username, password=password)
        if user is not None:
            login(request, user)
            auth_data = get_tokens_for_user(request.user)
            return Response({'msg': 'Login Success', **auth_data}, status=status.HTTP_200_OK)
        return Response({'msg': 'Invalid Credentials'}, status=status.HTTP_401_UNAUTHORIZED)

class LogoutView(APIView):
    def post(self, request):
        logout(request)
        return Response({'msg': 'Successfully Logged out'}, status=status.HTTP_200_OK)

class ProfileView(APIView):
    permission_classes = [IsAuthenticated, ]

    def get(self, request):
        user = request.user
        data = {
            'id': user.id, 
            'username': user.username,
            'first_name': user.first_name,
            'last_name': user.last_name,
            'email': user.email
        }
        return Response(data, status=status.HTTP_200_OK)

class ChangePasswordView(APIView):
    permission_classes = [IsAuthenticated, ]

    def post(self, request):
        serializer = PasswordChangeSerializer(context={'request': request}, data=request.data)
        serializer.is_valid(raise_exception=True)
        request.user.set_password(serializer.validated_data['new_password'])
        request.user.save()
        return Response(status=status.HTTP_204_NO_CONTENT)

Agora vamos expor essas views nas URL:

from django.urls import path
from .views import RegistrationView, LoginView, LogoutView, ChangePasswordView, ProfileView
from rest_framework_simplejwt import views as jwt_views

app_name = 'users'

urlpatterns = [
    path('register', RegistrationView.as_view(), name='register'),
    path('login', LoginView.as_view(), name='login'),
    path('me', ProfileView.as_view(), name='profile'),
    path('logout', LogoutView.as_view(), name='logout'),
    path('change-password', ChangePasswordView.as_view(), name='password'),
    path('token-refresh/', jwt_views.TokenRefreshView.as_view(), name='token_refresh'),
]

Também vamos adicionar algumas configurações de autenticação ao arquivo settings.py da pasta todo_project:

REST_FRAMEWORK = {
    # Use Django&#39;s standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    &#39;DEFAULT_PERMISSION_CLASSES&#39;: [
        &#39;rest_framework.permissions.AllowAny&#39;
    ],
    &#39;DEFAULT_AUTHENTICATION_CLASSES&#39;: [
        &#39;rest_framework_simplejwt.authentication.JWTAuthentication&#39;,
    ]
}

SIMPLE_JWT = {
    &#39;ACCESS_TOKEN_LIFETIME&#39;: datetime.timedelta(days=1),
    &#39;REFRESH_TOKEN_LIFETIME&#39;: datetime.timedelta(days=1),
}

AUTH_PROFILE_MODULE = &#39;users.CustomUser&#39;

AUTH_USER_MODEL = &#39;users.CustomUser&#39;

APP TODOS

Crie o model para os todos no arquivo models.py do módulo todos:

from django.db import models

class Todo(models.Model):
    title = models.TextField()
    description = models.TextField(null=True, blank=True)
    date_created = models.DateTimeField(auto_now_add=True)
    date_modified = models.DateTimeField(auto_now=True)
    is_completed = models.BooleanField(default=False)
    owner = models.ForeignKey(
      'users.CustomUser',
      related_name='todos',
      on_delete=models.CASCADE,
    )

    def __str__(self):
      return f"Title: {self.title} <Completed: {self.is_completed}>"

Crie o arquivo serializers.py:

from rest_framework import serializers
from .models import Todo

class TodoSerializer(serializers.ModelSerializer):
    class Meta:
        fields = (
            'id',
            'title',
            'description',
            'is_completed',
            'owner',
        )
        model = Todo

E agora vamos fazer as views de CRUD do todo:

from rest_framework import viewsets, status
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated  

from .models import Todo
from .serializers import TodoSerializer

class TodoViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated]
    serializer_class = TodoSerializer

    def get_queryset(self):
        return Todo.objects.filter(owner=self.request.user)

    def perform_create(self, serializer): 
        serializer.save(owner=self.request.user)

    def destroy(self, request, *args, **kwargs):
        todo = self.get_object() 
        if todo.owner == request.user:
            todo.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)
        else:
            return Response(status=status.HTTP_404_NOT_FOUND)

    def partial_update(self, request, *args, **kwargs):
        todo = self.get_object()
        if todo.owner != request.user:
            return Response(status=status.HTTP_404_NOT_FOUND)

        serializer = self.get_serializer(todo, data=request.data, partial=True) 
        serializer.is_valid(raise_exception=True)
        serializer.save()

        return Response(serializer.data)

Agora precisamos gerar e executar a migration do todo model:

poetry run python manage.py makemigrations
python manage.py migrate

Documentação

Vamos adicionar uma bibliotega para gerar nossa documentação automaticamente:

poetry add drf-yasg

Adicione ao arquivo settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'drf_yasg', # ADDED
    'users',
    'todos',
]

E nosso arquivo de urls.py ficará assim, com as rotas de documentação:

from django.contrib import admin
from django.urls import path, include
from rest_framework import permissions
from drf_yasg.views import get_schema_view
from drf_yasg import openapi

schema_view = get_schema_view(
    openapi.Info(
        title="Todo API",
        default_version='v1',
        description="Simple todo API",
        terms_of_service="https://www.google.com/policies/terms/",
        contact=openapi.Contact(email="me@abilioazevedo.com.br"),
        license=openapi.License(name="BSD License"),
    ),
    public=True,
    permission_classes=(permissions.AllowAny,),
)

urlpatterns = [
    path('swagger<format>/', schema_view.without_ui(cache_timeout=0), name='schema-json'),
    path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
    path('admin/', admin.site.urls),
    path("api/v1/users/", include("users.urls"), name="users"),
    path("api/v1/", include("todos.urls"), name="todos")
]

Agora é só acessar: http://127.0.0.1:8000/swagger/. Todo Swagger Python

Deploy

Você pode usar o https://render.com/ para implantar seu aplicativo, você só precisa conectar seu repositório e implantá-lo usando o Docker. Acesse o render para criar seu aplicativo web: https://dashboard.render.com/create?type=web render create app

Você pode testar a aplicação aqui

Comandos Extras

Para checar por migrations do banco de dados

poetry run python manage.py showmigrations

Se a migration já tiver sido efetuada, mas o Django não marcou como feita, você pode efetuar de maneira falsa:

poetry run python manage.py migrate --fake transactions 0009

Ir para uma migration específica

poetry run python manage.py migrate app_name 0003

Para imprimir alguma informação você pode usar a função:

print(obj)

Rodar testes:

poetry python manage.py test

Rodar a shell do python:

poetry python manage.py shell_plus

Dentro da shell podemos importar e executar arquivos:

from .models import Todo

todo = Todo(title=&#39;Create a todo list&#39;, description=&quot;Using Django&quot;)
todo.save()

print(Todo.objects.all())

Mais posts

Cover Image for Documentos Técnicos

Documentos Técnicos

Aprenda a importância vital da documentação técnica abrangente para projetos de software em crescimento. Descubra as melhores práticas, como Requests for Comments (RFCs) e Architectural Decision Records (ADRs), que promovem transparência, colaboração e registro de decisões arquiteturais. Explore ferramentas poderosas como wiki.js e Backstage para criar centros de documentação eficazes. Mantenha seu projeto organizado, compreensível e sustentável com essa abordagem à documentação técnica.

Abílio Azevedo
Abílio Azevedo
Cover Image for Superlógica - BFF para o Gruvi

Superlógica - BFF para o Gruvi

Construindo um BFF (Backend for Frontend) para o SuperApp Gruvi que tem mais de 120 mil usuários ativos e milhões de possíveis usuários para disponibilizar no ecossistema Superlogica.

Abílio Azevedo
Abílio Azevedo

NewsLetter

Eu enviarei o conteúdo postado aqui no blog. Sem Spam =)

Engenheiro de software experiente, formado em Engenharia Elétrica, com mais de 10 anos de experiência prática na construção de aplicativos móveis, web e back-end robustos e escaláveis em vários projetos, principalmente no setor de fintech. Mobile (React Native), Web (React e Next.JS) e Backend (Node.JS, PHP e DJANGO). Meu objetivo é criar produtos que agreguem valor às pessoas. - © 2024, Abílio Azevedo