From 6056cb4a682a1034143b638398583f0ddf63579b Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Wed, 18 Sep 2024 10:45:34 -0300 Subject: [PATCH 01/11] func: Add Usuario model and UsuarioManager --- src/tupan/tupan/settings.py | 3 ++ src/tupan/tupan/urls.py | 1 + src/tupan/usuarios/__init__.py | 0 src/tupan/usuarios/admin.py | 6 +++ src/tupan/usuarios/apps.py | 6 +++ src/tupan/usuarios/migrations/0001_initial.py | 31 ++++++++++++ src/tupan/usuarios/migrations/__init__.py | 0 src/tupan/usuarios/models.py | 50 +++++++++++++++++++ src/tupan/usuarios/serializer.py | 7 +++ src/tupan/usuarios/tests.py | 3 ++ src/tupan/usuarios/urls.py | 7 +++ src/tupan/usuarios/views.py | 22 ++++++++ 12 files changed, 136 insertions(+) create mode 100644 src/tupan/usuarios/__init__.py create mode 100644 src/tupan/usuarios/admin.py create mode 100644 src/tupan/usuarios/apps.py create mode 100644 src/tupan/usuarios/migrations/0001_initial.py create mode 100644 src/tupan/usuarios/migrations/__init__.py create mode 100644 src/tupan/usuarios/models.py create mode 100644 src/tupan/usuarios/serializer.py create mode 100644 src/tupan/usuarios/tests.py create mode 100644 src/tupan/usuarios/urls.py create mode 100644 src/tupan/usuarios/views.py diff --git a/src/tupan/tupan/settings.py b/src/tupan/tupan/settings.py index 6dcf951..78bb009 100644 --- a/src/tupan/tupan/settings.py +++ b/src/tupan/tupan/settings.py @@ -31,6 +31,8 @@ ALLOWED_HOSTS = [] +AUTH_USER_MODEL = 'usuarios.Usuario' + TEST_RUNNER = 'pytest_django.runner.PytestTestRunner' # Application definition @@ -46,6 +48,7 @@ 'alertas', 'estacoes' 'drf_spectacular', + 'usuarios', ] REST_FRAMEWORK = { diff --git a/src/tupan/tupan/urls.py b/src/tupan/tupan/urls.py index 2fd62b6..ea677b8 100644 --- a/src/tupan/tupan/urls.py +++ b/src/tupan/tupan/urls.py @@ -25,4 +25,5 @@ path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), + path('usuarios/', include('usuarios.urls')), ] diff --git a/src/tupan/usuarios/__init__.py b/src/tupan/usuarios/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tupan/usuarios/admin.py b/src/tupan/usuarios/admin.py new file mode 100644 index 0000000..b04419f --- /dev/null +++ b/src/tupan/usuarios/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import Usuario + +@admin.register(Usuario) +class UsuarioAdmin(admin.ModelAdmin): + list_display = ('email', 'password') \ No newline at end of file diff --git a/src/tupan/usuarios/apps.py b/src/tupan/usuarios/apps.py new file mode 100644 index 0000000..36e07d0 --- /dev/null +++ b/src/tupan/usuarios/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UsuariosConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'usuarios' diff --git a/src/tupan/usuarios/migrations/0001_initial.py b/src/tupan/usuarios/migrations/0001_initial.py new file mode 100644 index 0000000..e31befa --- /dev/null +++ b/src/tupan/usuarios/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# Generated by Django 5.1.1 on 2024-09-18 11:02 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Usuario', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('criacao', models.DateTimeField(auto_now_add=True)), + ('alterado', models.DateTimeField(auto_now=True)), + ('ativo', models.BooleanField(default=True)), + ('email', models.EmailField(max_length=254, unique=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/src/tupan/usuarios/migrations/__init__.py b/src/tupan/usuarios/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/tupan/usuarios/models.py b/src/tupan/usuarios/models.py new file mode 100644 index 0000000..77fa580 --- /dev/null +++ b/src/tupan/usuarios/models.py @@ -0,0 +1,50 @@ +from django.db import models +from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager +from django.core import validators +from django.utils.translation import gettext_lazy as _ +from django.utils import timezone + +class Base(models.Model): + criacao = models.DateTimeField(auto_now_add=True) + alterado = models.DateTimeField(auto_now=True) + ativo = models.BooleanField(default=True) + + class Meta: + abstract = True + +class UsuarioManager(BaseUserManager): + def _create_user(self, email, password, is_staff, is_superuser, **extra_fields): + now = timezone.now() + email = self.normalize_email(email) + user = self.model(email=email, is_staff=is_staff, is_active=True, is_superuser=is_superuser, last_login=now, date_joined=now, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_user(self, email=None, password=None, **extra_fields): + return self._create_user(email, password, False, False, **extra_fields) + + def create_superuser(self, email, password, **extra_fields): + user=self._create_user(email, password, True, True, **extra_fields) + user.is_active=True + user.save(using=self._db) + return user + + +class Usuario(Base): + email = models.EmailField(_('email address'), unique=True) + password = models.CharField(_('password'), max_length=255) + is_anonymous = models.BooleanField() + is_authenticated = models.BooleanField() + + + is_staff = models.BooleanField(_('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.')) + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['senha'] + + objects = UsuarioManager() + + class Meta: + verbose_name = "Usuário" + verbose_name_plural = "Usuários" \ No newline at end of file diff --git a/src/tupan/usuarios/serializer.py b/src/tupan/usuarios/serializer.py new file mode 100644 index 0000000..87efb7d --- /dev/null +++ b/src/tupan/usuarios/serializer.py @@ -0,0 +1,7 @@ +from rest_framework import serializers +from usuarios.models import Usuario + +class UsuarioSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Usuario + fields = ['user', 'email', 'ativo', 'criacao', 'alterado'] \ No newline at end of file diff --git a/src/tupan/usuarios/tests.py b/src/tupan/usuarios/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/src/tupan/usuarios/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/tupan/usuarios/urls.py b/src/tupan/usuarios/urls.py new file mode 100644 index 0000000..1c45bfc --- /dev/null +++ b/src/tupan/usuarios/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from rest_framework.urlpatterns import format_suffix_patterns +from usuarios.views import UsuarioList + +urlpatterns = [ + path('', UsuarioList.as_view()) +] diff --git a/src/tupan/usuarios/views.py b/src/tupan/usuarios/views.py new file mode 100644 index 0000000..51c7caf --- /dev/null +++ b/src/tupan/usuarios/views.py @@ -0,0 +1,22 @@ +from .models import Usuario +from usuarios.serializer import UsuarioSerializer +from django.http import Http404 +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status + +class UsuarioList(APIView): + """ + List all users, or create a new user. + """ + def get(self, request, format=None): + usuarios = Usuario.objects.all() + serializer = UsuarioSerializer(usuarios, many=True) + return Response(serializer.data) + + def post(self, request, format=None): + serializer = UsuarioSerializer(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) \ No newline at end of file From e64678ecb084a77576df8fae42a6f5afa8509579 Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Fri, 20 Sep 2024 08:45:16 -0300 Subject: [PATCH 02/11] refa: Alterando classe Usuario e UsuarioManager --- src/tupan/tupan/settings.py | 2 + ...io_options_remove_usuario_user_and_more.py | 48 +++++++++++++++++ ..._date_joined_usuario_is_active_and_more.py | 34 ++++++++++++ ...oined_remove_usuario_is_active_and_more.py | 53 +++++++++++++++++++ ...5_usuario_is_staff_usuario_is_superuser.py | 23 ++++++++ src/tupan/usuarios/models.py | 43 ++++++++------- src/tupan/usuarios/serializer.py | 7 ++- 7 files changed, 187 insertions(+), 23 deletions(-) create mode 100644 src/tupan/usuarios/migrations/0002_alter_usuario_options_remove_usuario_user_and_more.py create mode 100644 src/tupan/usuarios/migrations/0003_usuario_date_joined_usuario_is_active_and_more.py create mode 100644 src/tupan/usuarios/migrations/0004_remove_usuario_date_joined_remove_usuario_is_active_and_more.py create mode 100644 src/tupan/usuarios/migrations/0005_usuario_is_staff_usuario_is_superuser.py diff --git a/src/tupan/tupan/settings.py b/src/tupan/tupan/settings.py index 78bb009..9afa832 100644 --- a/src/tupan/tupan/settings.py +++ b/src/tupan/tupan/settings.py @@ -33,6 +33,8 @@ AUTH_USER_MODEL = 'usuarios.Usuario' +LOGIN_URL = "/" + TEST_RUNNER = 'pytest_django.runner.PytestTestRunner' # Application definition diff --git a/src/tupan/usuarios/migrations/0002_alter_usuario_options_remove_usuario_user_and_more.py b/src/tupan/usuarios/migrations/0002_alter_usuario_options_remove_usuario_user_and_more.py new file mode 100644 index 0000000..1a2edc6 --- /dev/null +++ b/src/tupan/usuarios/migrations/0002_alter_usuario_options_remove_usuario_user_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 5.1.1 on 2024-09-20 11:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='usuario', + options={'verbose_name': 'Usuário', 'verbose_name_plural': 'Usuários'}, + ), + migrations.RemoveField( + model_name='usuario', + name='user', + ), + migrations.AddField( + model_name='usuario', + name='is_anonymous', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='usuario', + name='is_authenticated', + field=models.BooleanField(default=False), + preserve_default=False, + ), + migrations.AddField( + model_name='usuario', + name='is_staff', + field=models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status'), + ), + migrations.AddField( + model_name='usuario', + name='password', + field=models.CharField(default=False, max_length=255, verbose_name='password'), + preserve_default=False, + ), + migrations.AlterField( + model_name='usuario', + name='email', + field=models.EmailField(max_length=254, unique=True, verbose_name='email address'), + ), + ] diff --git a/src/tupan/usuarios/migrations/0003_usuario_date_joined_usuario_is_active_and_more.py b/src/tupan/usuarios/migrations/0003_usuario_date_joined_usuario_is_active_and_more.py new file mode 100644 index 0000000..86be506 --- /dev/null +++ b/src/tupan/usuarios/migrations/0003_usuario_date_joined_usuario_is_active_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.1.1 on 2024-09-20 11:08 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0002_alter_usuario_options_remove_usuario_user_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='usuario', + name='date_joined', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined'), + ), + migrations.AddField( + model_name='usuario', + name='is_active', + field=models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active'), + ), + migrations.AddField( + model_name='usuario', + name='is_superuser', + field=models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status'), + ), + migrations.AddField( + model_name='usuario', + name='last_login', + field=models.DateTimeField(blank=True, null=True, verbose_name='last login'), + ), + ] diff --git a/src/tupan/usuarios/migrations/0004_remove_usuario_date_joined_remove_usuario_is_active_and_more.py b/src/tupan/usuarios/migrations/0004_remove_usuario_date_joined_remove_usuario_is_active_and_more.py new file mode 100644 index 0000000..80720c2 --- /dev/null +++ b/src/tupan/usuarios/migrations/0004_remove_usuario_date_joined_remove_usuario_is_active_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.1.1 on 2024-09-20 11:39 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0003_usuario_date_joined_usuario_is_active_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='usuario', + name='date_joined', + ), + migrations.RemoveField( + model_name='usuario', + name='is_active', + ), + migrations.RemoveField( + model_name='usuario', + name='is_anonymous', + ), + migrations.RemoveField( + model_name='usuario', + name='is_authenticated', + ), + migrations.RemoveField( + model_name='usuario', + name='is_staff', + ), + migrations.RemoveField( + model_name='usuario', + name='is_superuser', + ), + migrations.AlterField( + model_name='usuario', + name='alterado', + field=models.DateTimeField(auto_now=True, verbose_name='updated at'), + ), + migrations.AlterField( + model_name='usuario', + name='ativo', + field=models.BooleanField(default=True, verbose_name='active'), + ), + migrations.AlterField( + model_name='usuario', + name='criacao', + field=models.DateTimeField(default=django.utils.timezone.now, verbose_name='created at'), + ), + ] diff --git a/src/tupan/usuarios/migrations/0005_usuario_is_staff_usuario_is_superuser.py b/src/tupan/usuarios/migrations/0005_usuario_is_staff_usuario_is_superuser.py new file mode 100644 index 0000000..00082fa --- /dev/null +++ b/src/tupan/usuarios/migrations/0005_usuario_is_staff_usuario_is_superuser.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.1 on 2024-09-20 11:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('usuarios', '0004_remove_usuario_date_joined_remove_usuario_is_active_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='usuario', + name='is_staff', + field=models.BooleanField(default=False, verbose_name='staff'), + ), + migrations.AddField( + model_name='usuario', + name='is_superuser', + field=models.BooleanField(default=False, verbose_name='superuser'), + ), + ] diff --git a/src/tupan/usuarios/models.py b/src/tupan/usuarios/models.py index 77fa580..75e8983 100644 --- a/src/tupan/usuarios/models.py +++ b/src/tupan/usuarios/models.py @@ -13,38 +13,37 @@ class Meta: abstract = True class UsuarioManager(BaseUserManager): - def _create_user(self, email, password, is_staff, is_superuser, **extra_fields): - now = timezone.now() + def create_user(self, email, password=None, **extra_fields): + if not email: + raise ValueError('The Email field must be set') email = self.normalize_email(email) - user = self.model(email=email, is_staff=is_staff, is_active=True, is_superuser=is_superuser, last_login=now, date_joined=now, **extra_fields) + user = self.model(email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user - - def create_user(self, email=None, password=None, **extra_fields): - return self._create_user(email, password, False, False, **extra_fields) - - def create_superuser(self, email, password, **extra_fields): - user=self._create_user(email, password, True, True, **extra_fields) - user.is_active=True - user.save(using=self._db) - return user - + + def create_superuser(self, email, password=None, **extra_fields): + extra_fields.setdefault('is_superuser', True) + extra_fields.setdefault('is_staff', True) + return self.create_user(email, password, **extra_fields) -class Usuario(Base): +class Usuario(AbstractBaseUser): email = models.EmailField(_('email address'), unique=True) password = models.CharField(_('password'), max_length=255) - is_anonymous = models.BooleanField() - is_authenticated = models.BooleanField() + ativo = models.BooleanField(_('active'), default=True) + criacao = models.DateTimeField(_('created at'), default=timezone.now) + alterado = models.DateTimeField(_('updated at'), auto_now=True) + is_superuser = models.BooleanField(_('superuser'), default=False) + is_staff = models.BooleanField(_('staff'), default=False) - - is_staff = models.BooleanField(_('staff status'), default=False, help_text=_('Designates whether the user can log into this admin site.')) + objects = UsuarioManager() USERNAME_FIELD = 'email' - REQUIRED_FIELDS = ['senha'] - - objects = UsuarioManager() + REQUIRED_FIELDS = ['password'] class Meta: verbose_name = "Usuário" - verbose_name_plural = "Usuários" \ No newline at end of file + verbose_name_plural = "Usuários" + + def __str__(self): return self.email + \ No newline at end of file diff --git a/src/tupan/usuarios/serializer.py b/src/tupan/usuarios/serializer.py index 87efb7d..84ce7b8 100644 --- a/src/tupan/usuarios/serializer.py +++ b/src/tupan/usuarios/serializer.py @@ -4,4 +4,9 @@ class UsuarioSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Usuario - fields = ['user', 'email', 'ativo', 'criacao', 'alterado'] \ No newline at end of file + fields = ['email', 'password', 'ativo', 'criacao', 'alterado'] + extra_kwargs = {'password': {'write_only': True}} + + def create(self, validated_data): + user = Usuario.objects.create_user(**validated_data) + return user \ No newline at end of file From 4e65cf7da03cbb798e3318e7bdf2228b070d5563 Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Fri, 20 Sep 2024 10:27:58 -0300 Subject: [PATCH 03/11] oper: Altera o local o arquivo pytest.ini para o diretorio ./src/tupan --- src/tupan/pytest.ini | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/tupan/pytest.ini diff --git a/src/tupan/pytest.ini b/src/tupan/pytest.ini new file mode 100644 index 0000000..ce2b34b --- /dev/null +++ b/src/tupan/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +DJANGO_SETTINGS_MODULE = tupan.settings +python_files = tests.py test_*.py *_tests.py +pythonpath = . \ No newline at end of file From 0d590fe5159165768dedfd8fee86ecd3f8810976 Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Fri, 20 Sep 2024 10:44:20 -0300 Subject: [PATCH 04/11] func: Cria os testes --- src/tupan/tupan/pytest.ini | 4 --- ...usuario_groups_usuario_user_permissions.py | 24 +++++++++++++++ src/tupan/usuarios/models.py | 10 +------ src/tupan/usuarios/tests.py | 30 +++++++++++++++++-- src/tupan/usuarios/views.py | 13 +++++++- 5 files changed, 65 insertions(+), 16 deletions(-) delete mode 100644 src/tupan/tupan/pytest.ini create mode 100644 src/tupan/usuarios/migrations/0006_usuario_groups_usuario_user_permissions.py diff --git a/src/tupan/tupan/pytest.ini b/src/tupan/tupan/pytest.ini deleted file mode 100644 index ce2b34b..0000000 --- a/src/tupan/tupan/pytest.ini +++ /dev/null @@ -1,4 +0,0 @@ -[pytest] -DJANGO_SETTINGS_MODULE = tupan.settings -python_files = tests.py test_*.py *_tests.py -pythonpath = . \ No newline at end of file diff --git a/src/tupan/usuarios/migrations/0006_usuario_groups_usuario_user_permissions.py b/src/tupan/usuarios/migrations/0006_usuario_groups_usuario_user_permissions.py new file mode 100644 index 0000000..62f26f6 --- /dev/null +++ b/src/tupan/usuarios/migrations/0006_usuario_groups_usuario_user_permissions.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.1 on 2024-09-20 12:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('usuarios', '0005_usuario_is_staff_usuario_is_superuser'), + ] + + operations = [ + migrations.AddField( + model_name='usuario', + name='groups', + field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups'), + ), + migrations.AddField( + model_name='usuario', + name='user_permissions', + field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'), + ), + ] diff --git a/src/tupan/usuarios/models.py b/src/tupan/usuarios/models.py index 75e8983..0624ef8 100644 --- a/src/tupan/usuarios/models.py +++ b/src/tupan/usuarios/models.py @@ -4,14 +4,6 @@ from django.utils.translation import gettext_lazy as _ from django.utils import timezone -class Base(models.Model): - criacao = models.DateTimeField(auto_now_add=True) - alterado = models.DateTimeField(auto_now=True) - ativo = models.BooleanField(default=True) - - class Meta: - abstract = True - class UsuarioManager(BaseUserManager): def create_user(self, email, password=None, **extra_fields): if not email: @@ -27,7 +19,7 @@ def create_superuser(self, email, password=None, **extra_fields): extra_fields.setdefault('is_staff', True) return self.create_user(email, password, **extra_fields) -class Usuario(AbstractBaseUser): +class Usuario(AbstractBaseUser, PermissionsMixin): email = models.EmailField(_('email address'), unique=True) password = models.CharField(_('password'), max_length=255) ativo = models.BooleanField(_('active'), default=True) diff --git a/src/tupan/usuarios/tests.py b/src/tupan/usuarios/tests.py index 7ce503c..88df74c 100644 --- a/src/tupan/usuarios/tests.py +++ b/src/tupan/usuarios/tests.py @@ -1,3 +1,29 @@ -from django.test import TestCase +import pytest +from .models import Usuario -# Create your tests here. +class TestUsuario: + + @pytest.mark.django_db + def test_criacao_usuarios(self): + user1 = Usuario.objects.create_user(email='user@gmail.com', password='senha1') + user2 = Usuario.objects.create_user(email='well@gmail.com', password='senha2') + + assert Usuario.objects.count() == 2 + assert user1.email == 'user@gmail.com' + assert user2.email == 'well@gmail.com' + + assert user1.check_password("senha1") + assert user2.check_password("senha2") + + assert user1.ativo == True + assert user2.ativo == True + + @pytest.mark.django_db + def test_listagem_usuarios(self): + user1 = Usuario.objects.create_user(email='user@gmail.com', password='senha1') + user2 = Usuario.objects.create_user(email='well@gmail.com', password='senha2') + + usuarios = Usuario.objects.all() + assert len(usuarios) == 2 + assert user1 in usuarios + assert user2 in usuarios \ No newline at end of file diff --git a/src/tupan/usuarios/views.py b/src/tupan/usuarios/views.py index 51c7caf..e64829c 100644 --- a/src/tupan/usuarios/views.py +++ b/src/tupan/usuarios/views.py @@ -7,13 +7,24 @@ class UsuarioList(APIView): """ - List all users, or create a new user. + Lista, cria, atualiza e deleta os usuários. """ def get(self, request, format=None): usuarios = Usuario.objects.all() serializer = UsuarioSerializer(usuarios, many=True) return Response(serializer.data) + def delete(self, request, format=None): + try: + id_usuario = request.data.get("id") + usuario = Usuario.objects.get(id=id_usuario) + usuario.ativo = False + usuario.save() + return Response(usuario, status=status.HTTP_200_OK) + except Usuario.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + except Exception as e: + return Response({"message": str(e)}, status=status.HTTP_400_BAD_REQUEST) def post(self, request, format=None): serializer = UsuarioSerializer(data=request.data) if serializer.is_valid(): From 953417c79e18b2e7e4899080f10cea960556c463 Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Fri, 20 Sep 2024 11:52:39 -0300 Subject: [PATCH 05/11] func: Adiciona rota para deletar usuario --- src/tupan/usuarios/serializer.py | 2 +- src/tupan/usuarios/tests.py | 9 ++++++++- src/tupan/usuarios/views.py | 8 +++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/tupan/usuarios/serializer.py b/src/tupan/usuarios/serializer.py index 84ce7b8..ad2bbf2 100644 --- a/src/tupan/usuarios/serializer.py +++ b/src/tupan/usuarios/serializer.py @@ -4,7 +4,7 @@ class UsuarioSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Usuario - fields = ['email', 'password', 'ativo', 'criacao', 'alterado'] + fields = ['id', 'email', 'password', 'ativo', 'criacao', 'alterado'] extra_kwargs = {'password': {'write_only': True}} def create(self, validated_data): diff --git a/src/tupan/usuarios/tests.py b/src/tupan/usuarios/tests.py index 88df74c..7f249bc 100644 --- a/src/tupan/usuarios/tests.py +++ b/src/tupan/usuarios/tests.py @@ -26,4 +26,11 @@ def test_listagem_usuarios(self): usuarios = Usuario.objects.all() assert len(usuarios) == 2 assert user1 in usuarios - assert user2 in usuarios \ No newline at end of file + assert user2 in usuarios + + @pytest.mark.django_db + def test_inativar_usuario(self): + user1 = Usuario.objects.create_user(email="wellingtonll.faria@gmail.com", password="123123") + user1.ativo = False + user1.save() + assert user1.ativo == False \ No newline at end of file diff --git a/src/tupan/usuarios/views.py b/src/tupan/usuarios/views.py index e64829c..8e71a3f 100644 --- a/src/tupan/usuarios/views.py +++ b/src/tupan/usuarios/views.py @@ -10,7 +10,7 @@ class UsuarioList(APIView): Lista, cria, atualiza e deleta os usuários. """ def get(self, request, format=None): - usuarios = Usuario.objects.all() + usuarios = Usuario.objects.all().filter(ativo=True) serializer = UsuarioSerializer(usuarios, many=True) return Response(serializer.data) @@ -20,14 +20,16 @@ def delete(self, request, format=None): usuario = Usuario.objects.get(id=id_usuario) usuario.ativo = False usuario.save() - return Response(usuario, status=status.HTTP_200_OK) + serializer = UsuarioSerializer(usuario) + return Response(serializer.data, status=status.HTTP_200_OK) except Usuario.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) except Exception as e: return Response({"message": str(e)}, status=status.HTTP_400_BAD_REQUEST) + def post(self, request, format=None): serializer = UsuarioSerializer(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) \ No newline at end of file + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) From 64694fcc537a091dac2a007dade635700c186acc Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Mon, 23 Sep 2024 08:29:48 -0300 Subject: [PATCH 06/11] func: Adiciona rota de update de Usuario --- src/tupan/tupan/tests/tests.py | 8 -------- src/tupan/usuarios/tests.py | 24 +++++++++++++++++------- src/tupan/usuarios/views.py | 10 ++++++++++ 3 files changed, 27 insertions(+), 15 deletions(-) delete mode 100644 src/tupan/tupan/tests/tests.py diff --git a/src/tupan/tupan/tests/tests.py b/src/tupan/tupan/tests/tests.py deleted file mode 100644 index 60557b3..0000000 --- a/src/tupan/tupan/tests/tests.py +++ /dev/null @@ -1,8 +0,0 @@ -import pytest -from django.urls import reverse - -@pytest.mark.django_db -def test_homepage(client): - url = reverse('admin') - response = client.get(url) - assert response.status_code == 200 diff --git a/src/tupan/usuarios/tests.py b/src/tupan/usuarios/tests.py index 7f249bc..9620999 100644 --- a/src/tupan/usuarios/tests.py +++ b/src/tupan/usuarios/tests.py @@ -6,11 +6,11 @@ class TestUsuario: @pytest.mark.django_db def test_criacao_usuarios(self): user1 = Usuario.objects.create_user(email='user@gmail.com', password='senha1') - user2 = Usuario.objects.create_user(email='well@gmail.com', password='senha2') + user2 = Usuario.objects.create_user(email='user2@gmail.com', password='senha2') assert Usuario.objects.count() == 2 assert user1.email == 'user@gmail.com' - assert user2.email == 'well@gmail.com' + assert user2.email == 'user2@gmail.com' assert user1.check_password("senha1") assert user2.check_password("senha2") @@ -21,7 +21,7 @@ def test_criacao_usuarios(self): @pytest.mark.django_db def test_listagem_usuarios(self): user1 = Usuario.objects.create_user(email='user@gmail.com', password='senha1') - user2 = Usuario.objects.create_user(email='well@gmail.com', password='senha2') + user2 = Usuario.objects.create_user(email='user2@gmail.com', password='senha2') usuarios = Usuario.objects.all() assert len(usuarios) == 2 @@ -30,7 +30,17 @@ def test_listagem_usuarios(self): @pytest.mark.django_db def test_inativar_usuario(self): - user1 = Usuario.objects.create_user(email="wellingtonll.faria@gmail.com", password="123123") - user1.ativo = False - user1.save() - assert user1.ativo == False \ No newline at end of file + user = Usuario.objects.create_user(email="user@gmail.com", password="123123") + user.ativo = False + user.save() + assert user.ativo == False + + @pytest.mark.django_db + def test_atualizar_usuario(self): + user = Usuario.objects.create_user(email="user@gmail.com", password="userpass") + user.email = "test@gmail.com" + user.password = "passwd" + user.save() + + assert user.email == "test@gmail.com" + assert user.password == "passwd" \ No newline at end of file diff --git a/src/tupan/usuarios/views.py b/src/tupan/usuarios/views.py index 8e71a3f..c0e1d48 100644 --- a/src/tupan/usuarios/views.py +++ b/src/tupan/usuarios/views.py @@ -33,3 +33,13 @@ def post(self, request, format=None): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def update(self, request, format=None): + serializer = UsuarioSerializer(data=request.data) + if serializer.is_valid(): + user = Usuario.get(id=serializer.data.id) + user.email = serializer.data.email + user.password = serializer.data.password + user.save() + return Response(user, status=status.HTTP_200_OK) + return Response("", status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file From 2fcac52c404f2ca4ee793948630b867f87a7e551 Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Mon, 23 Sep 2024 17:28:48 -0300 Subject: [PATCH 07/11] corr: Alterando o funcionamento do metodo put do endpoint /usuarios/ --- src/tupan/usuarios/views.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/tupan/usuarios/views.py b/src/tupan/usuarios/views.py index c0e1d48..d578b62 100644 --- a/src/tupan/usuarios/views.py +++ b/src/tupan/usuarios/views.py @@ -34,12 +34,16 @@ def post(self, request, format=None): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - def update(self, request, format=None): - serializer = UsuarioSerializer(data=request.data) - if serializer.is_valid(): - user = Usuario.get(id=serializer.data.id) - user.email = serializer.data.email - user.password = serializer.data.password + def put(self, request, format=None): + try: + new_data = request.data + user = Usuario.objects.get(id=new_data["id"]) + user.email = new_data["email"] + user.password = new_data["password"] user.save() - return Response(user, status=status.HTTP_200_OK) - return Response("", status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file + serializer = UsuarioSerializer(user) + return Response(serializer.data, status=status.HTTP_200_OK) + except Usuario.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + except Exception as e: + return Response(str(e), status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file From 80b0ed38ce8946398e8aeb911c8bda160d66ca9f Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Wed, 25 Sep 2024 10:34:28 -0300 Subject: [PATCH 08/11] corr: Corrgindo erro no settings.py --- src/tupan/tupan/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tupan/tupan/settings.py b/src/tupan/tupan/settings.py index 9afa832..f9b85fd 100644 --- a/src/tupan/tupan/settings.py +++ b/src/tupan/tupan/settings.py @@ -48,7 +48,7 @@ 'django.contrib.staticfiles', 'rest_framework', 'alertas', - 'estacoes' + 'estacoes', 'drf_spectacular', 'usuarios', ] From 9d50b26129a529c5cb29295f4e5fa38ad83697e2 Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Thu, 26 Sep 2024 20:34:04 -0300 Subject: [PATCH 09/11] func: Autenticacao de usuario --- src/tupan/tupan/settings.py | 12 ++++++++++++ src/tupan/tupan/urls.py | 5 ++++- src/tupan/tupan/views.py | 15 +++++++++++++++ src/tupan/usuarios/admin.py | 5 ++++- src/tupan/usuarios/backends.py | 13 +++++++++++++ src/tupan/usuarios/models.py | 21 +++++++++++++++++---- src/tupan/usuarios/serializer.py | 12 ++++++------ src/tupan/usuarios/views.py | 10 ++++++++++ 8 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 src/tupan/tupan/views.py create mode 100644 src/tupan/usuarios/backends.py diff --git a/src/tupan/tupan/settings.py b/src/tupan/tupan/settings.py index f9b85fd..b24bd0f 100644 --- a/src/tupan/tupan/settings.py +++ b/src/tupan/tupan/settings.py @@ -47,6 +47,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', + 'rest_framework.authtoken', 'alertas', 'estacoes', 'drf_spectacular', @@ -55,8 +56,19 @@ REST_FRAMEWORK = { 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework.authentication.TokenAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticated', + ], } +AUTHENTICATION_BACKENDS = [ + 'usuarios.backends.EmailBackend', + 'django.contrib.auth.backends.ModelBackend', +] + SPECTACULAR_SETTINGS = { 'TITLE': 'Tupã API', 'DESCRIPTION': '', diff --git a/src/tupan/tupan/urls.py b/src/tupan/tupan/urls.py index ea677b8..ae7f5c8 100644 --- a/src/tupan/tupan/urls.py +++ b/src/tupan/tupan/urls.py @@ -17,13 +17,16 @@ from django.contrib import admin from django.urls import include, path from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView +from rest_framework.authtoken.views import obtain_auth_token +from django.contrib.auth import views as auth_views +from .views import CustomAuthToken urlpatterns = [ path('admin/', admin.site.urls), path('', include('estacoes.urls')), - path('admin/', admin.site.urls), path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), path('usuarios/', include('usuarios.urls')), + path('api-token-auth/', CustomAuthToken.as_view(), name='api_token_auth'), ] diff --git a/src/tupan/tupan/views.py b/src/tupan/tupan/views.py new file mode 100644 index 0000000..ade709a --- /dev/null +++ b/src/tupan/tupan/views.py @@ -0,0 +1,15 @@ +from rest_framework.authtoken.views import ObtainAuthToken +from rest_framework.authtoken.models import Token +from rest_framework.response import Response +from django.contrib.auth import authenticate + +class CustomAuthToken(ObtainAuthToken): + def post(self, request, *args, **kwargs): + email = request.data.get('email') + password = request.data.get('password') + user = authenticate(request, username=email, password=password) + if user is not None: + token, created = Token.objects.get_or_create(user=user) + return Response({'token': token.key}) + else: + return Response({'error': 'Invalid Credentials'}, status=400) \ No newline at end of file diff --git a/src/tupan/usuarios/admin.py b/src/tupan/usuarios/admin.py index b04419f..6450efa 100644 --- a/src/tupan/usuarios/admin.py +++ b/src/tupan/usuarios/admin.py @@ -3,4 +3,7 @@ @admin.register(Usuario) class UsuarioAdmin(admin.ModelAdmin): - list_display = ('email', 'password') \ No newline at end of file + list_display = ('email', 'is_staff', 'is_superuser', 'ativo', 'criacao', 'alterado') + search_fields = ('email',) + readonly_fields = ('criacao', 'alterado') + ordering = ('email',) \ No newline at end of file diff --git a/src/tupan/usuarios/backends.py b/src/tupan/usuarios/backends.py new file mode 100644 index 0000000..efea137 --- /dev/null +++ b/src/tupan/usuarios/backends.py @@ -0,0 +1,13 @@ +from django.contrib.auth.backends import ModelBackend +from django.contrib.auth import get_user_model + +class EmailBackend(ModelBackend): + def authenticate(self, request, username=None, password=None, **kwargs): + UserModel = get_user_model() + try: + user = UserModel.objects.get(email=username) + except UserModel.DoesNotExist: + return None + if user.check_password(password) and self.user_can_authenticate(user): + return user + return None \ No newline at end of file diff --git a/src/tupan/usuarios/models.py b/src/tupan/usuarios/models.py index 0624ef8..01e2ac3 100644 --- a/src/tupan/usuarios/models.py +++ b/src/tupan/usuarios/models.py @@ -13,10 +13,16 @@ def create_user(self, email, password=None, **extra_fields): user.set_password(password) user.save(using=self._db) return user - + def create_superuser(self, email, password=None, **extra_fields): extra_fields.setdefault('is_superuser', True) extra_fields.setdefault('is_staff', True) + + if extra_fields.get('is_superuser') is not True: + raise ValueError('Superuser must have is_superuser=True.') + if extra_fields.get('is_staff') is not True: + raise ValueError('Superuser must have is_staff=True.') + return self.create_user(email, password, **extra_fields) class Usuario(AbstractBaseUser, PermissionsMixin): @@ -31,11 +37,18 @@ class Usuario(AbstractBaseUser, PermissionsMixin): objects = UsuarioManager() USERNAME_FIELD = 'email' - REQUIRED_FIELDS = ['password'] + REQUIRED_FIELDS = [] class Meta: verbose_name = "Usuário" verbose_name_plural = "Usuários" - - def __str__(self): return self.email + + def __str__(self): + return self.email + + def get_full_name(self): + return self.email + + def get_short_name(self): + return self.email \ No newline at end of file diff --git a/src/tupan/usuarios/serializer.py b/src/tupan/usuarios/serializer.py index ad2bbf2..d31ee62 100644 --- a/src/tupan/usuarios/serializer.py +++ b/src/tupan/usuarios/serializer.py @@ -1,12 +1,12 @@ from rest_framework import serializers -from usuarios.models import Usuario +from .models import Usuario -class UsuarioSerializer(serializers.HyperlinkedModelSerializer): +class UsuarioSerializer(serializers.ModelSerializer): class Meta: model = Usuario - fields = ['id', 'email', 'password', 'ativo', 'criacao', 'alterado'] + fields = ['id', 'email', 'password', 'criacao', 'alterado'] extra_kwargs = {'password': {'write_only': True}} - def create(self, validated_data): - user = Usuario.objects.create_user(**validated_data) - return user \ No newline at end of file + def create(self, validated_data): + user = Usuario.objects.create_user(**validated_data) + return user \ No newline at end of file diff --git a/src/tupan/usuarios/views.py b/src/tupan/usuarios/views.py index d578b62..8fed8f9 100644 --- a/src/tupan/usuarios/views.py +++ b/src/tupan/usuarios/views.py @@ -4,11 +4,21 @@ from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status +from rest_framework.permissions import IsAuthenticated, AllowAny class UsuarioList(APIView): """ Lista, cria, atualiza e deleta os usuários. """ + def get_permissions(self): + if self.request.method in ['DELETE', 'PUT']: + self.permission_classes = [IsAuthenticated] + elif self.request.method == 'POST': + self.permission_classes = [AllowAny] + else: + self.permission_classes = [IsAuthenticated] + return super().get_permissions() + def get(self, request, format=None): usuarios = Usuario.objects.all().filter(ativo=True) serializer = UsuarioSerializer(usuarios, many=True) From 3e9c856e9285ab927be27b53010cec52bb60b5d5 Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Sat, 28 Sep 2024 10:22:40 -0300 Subject: [PATCH 10/11] func: Adicionando django-cors-headers ao requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c49bda8..8a2a828 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,5 @@ python-dotenv==1.0.1 sqlparse==0.5.1 typing_extensions==4.12.2 tzdata==2024.1 -drf-spectacular \ No newline at end of file +drf-spectacular +django-cors-headers \ No newline at end of file From a7ecdb66f1d37c48f67eecb0a26277a038f9813b Mon Sep 17 00:00:00 2001 From: WellingtonLFaria Date: Sat, 28 Sep 2024 10:27:07 -0300 Subject: [PATCH 11/11] =?UTF-8?q?func:=20Adicionando=20as=20configura?= =?UTF-8?q?=C3=A7=C3=B5es=20de=20CORS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/tupan/tupan/settings.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tupan/tupan/settings.py b/src/tupan/tupan/settings.py index b24bd0f..6de1858 100644 --- a/src/tupan/tupan/settings.py +++ b/src/tupan/tupan/settings.py @@ -52,6 +52,7 @@ 'estacoes', 'drf_spectacular', 'usuarios', + 'corsheaders', ] REST_FRAMEWORK = { @@ -77,6 +78,7 @@ } MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -86,6 +88,10 @@ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +CORS_ALLOWED_ORIGINS = [ + 'http://localhost:3000', +] + ROOT_URLCONF = 'tupan.urls' TEMPLATES = [