Merge branch 'feature/#9_crear_nuevas_app_usuarios' into 'develop'
Feature/#9 crear nuevas app usuarios See merge request !5
Showing
36 changed files
with
662 additions
and
28 deletions
project/apps/core/api.py
0 → 100644
1 | +import requests | ||
2 | +from rest_framework.response import Response | ||
3 | +from rest_framework.decorators import api_view | ||
4 | + | ||
5 | +from django.conf import settings | ||
6 | + | ||
7 | + | ||
8 | +@api_view(['POST']) | ||
9 | +def recaptcha(request): | ||
10 | + r = requests.post( | ||
11 | + 'https://www.google.com/recaptcha/api/siteverify', | ||
12 | + data={ | ||
13 | + 'secret': settings.SECRET_KEY_CAPTCHA, | ||
14 | + 'response': request.data['captcha_value'], | ||
15 | + } | ||
16 | + ) | ||
17 | + | ||
18 | + return Response({'captcha': r.json()}) |
project/apps/core/paginations.py
0 → 100644
project/apps/core/permissions.py
0 → 100644
1 | +from rest_framework.permissions import DjangoModelPermissions | ||
2 | + | ||
3 | + | ||
4 | +class CustomModelPermissions(DjangoModelPermissions): | ||
5 | + perms_map = { | ||
6 | + 'GET': ['%(app_label)s.view_%(model_name)s'], | ||
7 | + 'OPTIONS': [], | ||
8 | + 'HEAD': [], | ||
9 | + 'POST': ['%(app_label)s.add_%(model_name)s'], | ||
10 | + 'PUT': ['%(app_label)s.change_%(model_name)s'], | ||
11 | + 'PATCH': ['%(app_label)s.change_%(model_name)s'], | ||
12 | + 'DELETE': ['%(app_label)s.delete_%(model_name)s'], | ||
13 | + } |
project/apps/core/tests.py
deleted
100644 → 0
@@ -3,6 +3,8 @@ import json | @@ -3,6 +3,8 @@ import json | ||
3 | import pytest | 3 | import pytest |
4 | 4 | ||
5 | from django.contrib.auth import get_user_model | 5 | from django.contrib.auth import get_user_model |
6 | +from django.contrib.auth.models import Group, Permission | ||
7 | +from django.contrib.contenttypes.models import ContentType | ||
6 | from oauth2_provider.models import get_application_model | 8 | from oauth2_provider.models import get_application_model |
7 | from rest_framework.test import APIClient | 9 | from rest_framework.test import APIClient |
8 | 10 | ||
@@ -11,27 +13,82 @@ User = get_user_model() | @@ -11,27 +13,82 @@ User = get_user_model() | ||
11 | CONTENT_TYPE_JSON = 'application/json' | 13 | CONTENT_TYPE_JSON = 'application/json' |
12 | 14 | ||
13 | 15 | ||
14 | -@pytest.fixture | ||
15 | -def create_user(username, first_name='Admin', last_name='Root', email=None): | 16 | +def create_user(username, first_name='Admin', last_name='Root', email=None, *, is_active=True, |
17 | + documento_identidad='24262155'): | ||
16 | user, created = User.objects.get_or_create( | 18 | user, created = User.objects.get_or_create( |
17 | username=username, | 19 | username=username, |
20 | + documento_identidad=documento_identidad, | ||
18 | email='{}@root.com'.format(username) if email is None else email, | 21 | email='{}@root.com'.format(username) if email is None else email, |
19 | defaults=dict( | 22 | defaults=dict( |
20 | first_name=first_name, | 23 | first_name=first_name, |
21 | last_name=last_name, | 24 | last_name=last_name, |
22 | - password='password' | 25 | + password='password', |
26 | + is_active=is_active | ||
23 | ) | 27 | ) |
24 | ) | 28 | ) |
25 | 29 | ||
26 | return user | 30 | return user |
27 | 31 | ||
28 | 32 | ||
33 | +def crear_grupo_administrador(): | ||
34 | + grupo, _ = Group.objects.get_or_create(name='administrador') | ||
35 | + | ||
36 | + PERMISSIONS = { | ||
37 | + 'auth': { | ||
38 | + 'group': ['add', 'change', 'view', 'delete'] | ||
39 | + }, | ||
40 | + 'organismo': { | ||
41 | + 'organismo': ['add', 'change', 'view', 'delete'], | ||
42 | + }, | ||
43 | + 'usuario': { | ||
44 | + 'usuario': ['add', 'change', 'view', 'delete'], | ||
45 | + } | ||
46 | + } | ||
47 | + agregar_varios_permisos_grupo(PERMISSIONS, grupo) | ||
48 | + return grupo | ||
49 | + | ||
50 | + | ||
51 | +def agregar_permisos_grupo(permisos_por_modelo, grupo, app): | ||
52 | + # Loop models in group | ||
53 | + for model_name in permisos_por_modelo: | ||
54 | + # Loop permissions in group/model | ||
55 | + model_ct = ContentType.objects.get(app_label=app, model=model_name) | ||
56 | + for perm_name in permisos_por_modelo[model_name]: | ||
57 | + # Generate permission name as Django would generate it | ||
58 | + codename = perm_name + "_" + model_name | ||
59 | + name = "Can " + perm_name + " " + model_name | ||
60 | + permission, _ = Permission.objects.get_or_create(codename=codename, | ||
61 | + content_type=model_ct, | ||
62 | + defaults={'name': name}) | ||
63 | + grupo.permissions.add(permission) | ||
64 | + | ||
65 | + | ||
66 | +def agregar_varios_permisos_grupo(permisos_por_app, grupo): | ||
67 | + # iterar sobre nombres de apps | ||
68 | + for nombre_app in permisos_por_app: | ||
69 | + agregar_permisos_grupo(permisos_por_app[nombre_app], grupo, nombre_app) | ||
70 | + | ||
71 | + | ||
29 | @pytest.fixture | 72 | @pytest.fixture |
30 | def get_default_test_user(): | 73 | def get_default_test_user(): |
31 | - test_user = create_user(username='test_user', first_name='Test', last_name='User', email='test@user') | 74 | + test_user = create_user(username='test_user', first_name='Test', last_name='User', email='test@user', documento_identidad='12345678') |
32 | return test_user | 75 | return test_user |
33 | 76 | ||
34 | 77 | ||
78 | +@pytest.fixture | ||
79 | +def crear_usuarios(): | ||
80 | + usuario1 = create_user(username='usuario1', first_name='Usuario', last_name='J', | ||
81 | + email='test@user1', documento_identidad='12345777') | ||
82 | + | ||
83 | + usuario2 = create_user(username='usuario2', first_name='Usuario', last_name='S', | ||
84 | + email='test@user2', documento_identidad='12345679') | ||
85 | + | ||
86 | + usuario3 = create_user(username='usuario3', first_name='Usuario', last_name='P', | ||
87 | + email='test@user3', documento_identidad='12345699') | ||
88 | + | ||
89 | + return usuario1, usuario2, usuario3 | ||
90 | + | ||
91 | + | ||
35 | def get_client_application(): | 92 | def get_client_application(): |
36 | Application = get_application_model() | 93 | Application = get_application_model() |
37 | application, _ = Application.objects.get_or_create( | 94 | application, _ = Application.objects.get_or_create( |
project/apps/core/urls.py
0 → 100644
project/apps/organismo/__init__.py
0 → 100644
project/apps/organismo/admin.py
0 → 100644
1 | +from django.contrib import admin | ||
2 | + | ||
3 | +from core.admin import PublicadoAdmin | ||
4 | +from organismo.models import Organismo | ||
5 | + | ||
6 | + | ||
7 | +@admin.register(Organismo) | ||
8 | +class OrganismoAdmin(PublicadoAdmin): | ||
9 | + list_display = ('nombre', 'descripcion') | ||
10 | + list_filter = ('nombre', ) | ||
11 | + search_fields = ('nombre', ) | ||
12 | + list_per_page = 10 |
project/apps/organismo/api.py
0 → 100644
1 | +from rest_framework import viewsets, filters | ||
2 | +from django_filters.rest_framework import DjangoFilterBackend | ||
3 | +from rest_framework.permissions import IsAuthenticated | ||
4 | + | ||
5 | + | ||
6 | +from core.permissions import CustomModelPermissions | ||
7 | +from organismo.filters import OrganismoFilter | ||
8 | +from organismo.models import Organismo | ||
9 | +from organismo.serializers import OrganismoSerializer | ||
10 | + | ||
11 | + | ||
12 | +class OrganismoViewSet(viewsets.ReadOnlyModelViewSet): | ||
13 | + queryset = Organismo.objects.all() | ||
14 | + permission_classes = (IsAuthenticated, CustomModelPermissions) | ||
15 | + serializer_class = OrganismoSerializer | ||
16 | + filter_backends = (DjangoFilterBackend, filters.OrderingFilter) | ||
17 | + filterset_class = OrganismoFilter | ||
18 | + ordering_fields = ('id',) |
project/apps/organismo/apps.py
0 → 100644
project/apps/organismo/filters.py
0 → 100644
1 | +# Generated by Django 3.2.8 on 2023-06-07 13:24 | ||
2 | + | ||
3 | +from django.db import migrations, models | ||
4 | + | ||
5 | + | ||
6 | +class Migration(migrations.Migration): | ||
7 | + | ||
8 | + initial = True | ||
9 | + | ||
10 | + dependencies = [ | ||
11 | + ] | ||
12 | + | ||
13 | + operations = [ | ||
14 | + migrations.CreateModel( | ||
15 | + name='Organismo', | ||
16 | + fields=[ | ||
17 | + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
18 | + ('publicado', models.DateTimeField(blank=True, null=True)), | ||
19 | + ('nombre', models.CharField(max_length=150, unique=True)), | ||
20 | + ('descripcion', models.TextField(blank=True)), | ||
21 | + ('domicilio', models.CharField(blank=True, max_length=200)), | ||
22 | + ('telefono', models.CharField(blank=True, max_length=100)), | ||
23 | + ('email', models.EmailField(blank=True, max_length=100)), | ||
24 | + ], | ||
25 | + options={ | ||
26 | + 'ordering': ('nombre', 'descripcion'), | ||
27 | + }, | ||
28 | + ), | ||
29 | + ] |
1 | +# Generated by Django 3.2.8 on 2023-06-13 13:30 | ||
2 | + | ||
3 | +from django.db import migrations, models | ||
4 | + | ||
5 | + | ||
6 | +class Migration(migrations.Migration): | ||
7 | + | ||
8 | + dependencies = [ | ||
9 | + ('organismo', '0001_initial'), | ||
10 | + ] | ||
11 | + | ||
12 | + operations = [ | ||
13 | + migrations.AddField( | ||
14 | + model_name='organismo', | ||
15 | + name='es_publico', | ||
16 | + field=models.BooleanField(default=False), | ||
17 | + ), | ||
18 | + ] |
project/apps/organismo/models.py
0 → 100644
1 | +from django.db import models | ||
2 | + | ||
3 | +from core.models import Publicado | ||
4 | + | ||
5 | + | ||
6 | +class Organismo(Publicado): | ||
7 | + class Meta: | ||
8 | + ordering = ('nombre', 'descripcion') | ||
9 | + | ||
10 | + nombre = models.CharField(max_length=150, unique=True) | ||
11 | + descripcion = models.TextField(blank=True) | ||
12 | + domicilio = models.CharField(max_length=200, blank=True) | ||
13 | + telefono = models.CharField(max_length=100, blank=True) | ||
14 | + email = models.EmailField(max_length=100, blank=True) | ||
15 | + es_publico = models.BooleanField(default=False) | ||
16 | + | ||
17 | + def __str__(self): | ||
18 | + return f'{self.nombre}' |
project/apps/organismo/serializers.py
0 → 100644
1 | +from rest_framework_json_api import serializers | ||
2 | + | ||
3 | +from organismo.models import Organismo | ||
4 | + | ||
5 | + | ||
6 | +class OrganismoSerializer(serializers.ModelSerializer): | ||
7 | + class Meta: | ||
8 | + model = Organismo | ||
9 | + fields = ( | ||
10 | + 'nombre', | ||
11 | + 'descripcion', | ||
12 | + 'domicilio', | ||
13 | + 'telefono', | ||
14 | + 'email', | ||
15 | + ) |
project/apps/organismo/tests/__init__.py
0 → 100644
1 | +import pytest | ||
2 | + | ||
3 | +from organismo.models import Organismo | ||
4 | + | ||
5 | + | ||
6 | +@pytest.fixture | ||
7 | +def crear_organismo(): | ||
8 | + osep, _ = Organismo.objects.get_or_create( | ||
9 | + nombre='Osep', | ||
10 | + descripcion='Obra Social de la Provincia de Catamarca', | ||
11 | + domicilio='San Fernando del Valle de Catamarca', | ||
12 | + telefono='0303456', | ||
13 | + email='osep@catamarca.gob.ar' | ||
14 | + ) | ||
15 | + | ||
16 | + juzgado_primer_instancia, _ = Organismo.objects.get_or_create( | ||
17 | + nombre='Secretaria de Modernizacion', | ||
18 | + descripcion='Secretaria de Modernizacion', | ||
19 | + domicilio='San Fernando del Valle de Catamarca', | ||
20 | + telefono='0303458', | ||
21 | + email='modernizacion_del_estado@catamarca.gob.ar' | ||
22 | + ) | ||
23 | + | ||
24 | + return osep, juzgado_primer_instancia |
1 | +import pytest | ||
2 | + | ||
3 | +from core.tests.utils import get | ||
4 | + | ||
5 | +from organismo.tests.fixture_organismo import crear_organismo | ||
6 | +from core.tests.fixtures import ( | ||
7 | + get_default_test_user, | ||
8 | + create_user, | ||
9 | + crear_grupo_administrador | ||
10 | +) | ||
11 | + | ||
12 | + | ||
13 | +@pytest.mark.django_db | ||
14 | +def test_listado_de_organismo(get_default_test_user, crear_organismo): | ||
15 | + | ||
16 | + endpoint = '/api/v1/organismo/' | ||
17 | + | ||
18 | + usuario = get_default_test_user | ||
19 | + grupo_admin = crear_grupo_administrador() | ||
20 | + usuario.groups.add(grupo_admin) | ||
21 | + | ||
22 | + response = get(endpoint, user_logged=usuario) | ||
23 | + assert response.status_code == 200 | ||
24 | + meta = response.json()['meta'] | ||
25 | + | ||
26 | + assert meta['pagination']['count'] == 2 | ||
27 | + | ||
28 | + | ||
29 | +@pytest.mark.django_db | ||
30 | +def test_detalle_de_organismo(get_default_test_user, crear_organismo): | ||
31 | + osep, juzgado_primer_instancia = crear_organismo | ||
32 | + | ||
33 | + endpoint = f'/api/v1/organismo/{osep.id}/' | ||
34 | + | ||
35 | + usuario = get_default_test_user | ||
36 | + grupo_admin = crear_grupo_administrador() | ||
37 | + usuario.groups.add(grupo_admin) | ||
38 | + | ||
39 | + response = get(endpoint, user_logged=usuario) | ||
40 | + assert response.status_code == 200 | ||
41 | + data = response.json()['data'] | ||
42 | + | ||
43 | + assert data['id'] == str(osep.id) | ||
44 | + assert data['attributes']['nombre'] == osep.nombre | ||
45 | + assert data['attributes']['descripcion'] == osep.descripcion | ||
46 | + assert data['attributes']['domicilio'] == osep.domicilio | ||
47 | + assert data['attributes']['telefono'] == osep.telefono |
project/apps/organismo/views.py
0 → 100644
project/apps/usuario/__init__.py
0 → 100644
project/apps/usuario/admin.py
0 → 100644
1 | +from django.contrib import admin | ||
2 | +from django.contrib.auth.admin import UserAdmin | ||
3 | +from django.utils.translation import gettext as _ | ||
4 | + | ||
5 | +from usuario.models import Usuario | ||
6 | + | ||
7 | + | ||
8 | +@admin.register(Usuario) | ||
9 | +class UsuarioAdmin(UserAdmin): | ||
10 | + fieldsets = ( | ||
11 | + (None, {'fields': ('username', 'password')}), | ||
12 | + (_('Personal info'), | ||
13 | + { | ||
14 | + 'fields': ( | ||
15 | + 'first_name', | ||
16 | + 'last_name', | ||
17 | + 'email', | ||
18 | + 'documento_identidad' | ||
19 | + ) | ||
20 | + }), | ||
21 | + (_('Organismo'), {'fields': ('organismo',)}), | ||
22 | + (_('Permissions'), { | ||
23 | + 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'), | ||
24 | + }), | ||
25 | + (_('Important dates'), {'fields': ('last_login', 'date_joined')}), | ||
26 | + ) | ||
27 | + add_fieldsets = ( | ||
28 | + (None, { | ||
29 | + 'classes': ('wide',), | ||
30 | + 'fields': ( | ||
31 | + 'first_name', 'last_name', 'username', "password1", "password2", 'email', 'documento_identidad', | ||
32 | + 'organismo', 'is_staff',)} | ||
33 | + ), | ||
34 | + ) | ||
35 | + search_fields = ('email', 'documento_identidad', 'username',) | ||
36 | + autocomplete_fields = ('organismo',) |
project/apps/usuario/api.py
0 → 100644
1 | +from django.contrib.auth.tokens import default_token_generator | ||
2 | +from rest_framework import mixins, viewsets, status | ||
3 | +from rest_framework.decorators import action | ||
4 | +from rest_framework.exceptions import ValidationError | ||
5 | +from rest_framework.parsers import JSONParser | ||
6 | +from rest_framework.permissions import IsAuthenticated, AllowAny | ||
7 | +from rest_framework.response import Response | ||
8 | + | ||
9 | +from usuario.models import Usuario | ||
10 | +from usuario.serializers import UsuarioSerializer, CambiarClaveSecretaSerializer | ||
11 | + | ||
12 | + | ||
13 | +class UsuarioViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet): | ||
14 | + queryset = Usuario.objects.all() | ||
15 | + permission_classes = (IsAuthenticated,) | ||
16 | + serializer_class = UsuarioSerializer | ||
17 | + | ||
18 | + def get_object(self, base_method=False): | ||
19 | + user = self.request.user | ||
20 | + if base_method: | ||
21 | + user = super().get_object() | ||
22 | + | ||
23 | + return user | ||
24 | + | ||
25 | + @action( | ||
26 | + methods=('patch',), | ||
27 | + detail=False, | ||
28 | + url_path='cambiar-clave-secreta', | ||
29 | + parser_classes=(JSONParser,), | ||
30 | + serializer_class=CambiarClaveSecretaSerializer | ||
31 | + ) | ||
32 | + def cambiar_clave_secreta(self, request): | ||
33 | + serializer = self.get_serializer(data=request.data) | ||
34 | + serializer.is_valid(raise_exception=True) | ||
35 | + serializer.save() | ||
36 | + return Response(status=status.HTTP_200_OK) |
project/apps/usuario/apps.py
0 → 100644
1 | +# Generated by Django 3.2.8 on 2023-06-08 11:10 | ||
2 | + | ||
3 | +import django.contrib.auth.models | ||
4 | +import django.contrib.auth.validators | ||
5 | +from django.db import migrations, models | ||
6 | +import django.db.models.deletion | ||
7 | +import django.utils.timezone | ||
8 | + | ||
9 | + | ||
10 | +class Migration(migrations.Migration): | ||
11 | + | ||
12 | + initial = True | ||
13 | + | ||
14 | + dependencies = [ | ||
15 | + ('auth', '0012_alter_user_first_name_max_length'), | ||
16 | + ('organismo', '0001_initial'), | ||
17 | + ] | ||
18 | + | ||
19 | + operations = [ | ||
20 | + migrations.CreateModel( | ||
21 | + name='Usuario', | ||
22 | + fields=[ | ||
23 | + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | ||
24 | + ('password', models.CharField(max_length=128, verbose_name='password')), | ||
25 | + ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), | ||
26 | + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), | ||
27 | + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), | ||
28 | + ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), | ||
29 | + ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), | ||
30 | + ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), | ||
31 | + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), | ||
32 | + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), | ||
33 | + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), | ||
34 | + ('documento_identidad', models.CharField(max_length=15, unique=True, verbose_name='Número de documento')), | ||
35 | + ('groups', 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')), | ||
36 | + ('organismo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organismo.organismo')), | ||
37 | + ('user_permissions', 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')), | ||
38 | + ], | ||
39 | + options={ | ||
40 | + 'db_table': 'auth_user', | ||
41 | + }, | ||
42 | + managers=[ | ||
43 | + ('objects', django.contrib.auth.models.UserManager()), | ||
44 | + ], | ||
45 | + ), | ||
46 | + ] |
project/apps/usuario/migrations/__init__.py
0 → 100644
project/apps/usuario/models.py
0 → 100644
1 | +from django.contrib.auth.models import AbstractUser | ||
2 | +from django.db import models | ||
3 | + | ||
4 | + | ||
5 | +class Usuario(AbstractUser): | ||
6 | + class Meta: | ||
7 | + db_table = 'auth_user' | ||
8 | + | ||
9 | + documento_identidad = models.CharField(max_length=15, verbose_name='Número de documento', unique=True) | ||
10 | + organismo = models.ForeignKey('organismo.Organismo', on_delete=models.SET_NULL, blank=True, null=True) | ||
11 | + | ||
12 | + def __str__(self): | ||
13 | + return f'{self.username}' | ||
14 | + | ||
15 | + def obtener_nombre_completo(self): | ||
16 | + nombre_completo = f'{self.last_name}, {self.first_name}' | ||
17 | + return nombre_completo.strip() | ||
18 | + | ||
19 | + obtener_nombre_completo.short_description = 'Nombre Completo' |
project/apps/usuario/serializers.py
0 → 100644
1 | +from django.contrib.auth import password_validation | ||
2 | +from rest_framework_json_api import serializers | ||
3 | +from rest_framework.serializers import Serializer as DRFSerializer | ||
4 | + | ||
5 | +from usuario.models import Usuario | ||
6 | + | ||
7 | + | ||
8 | +class UsuarioSerializer(serializers.ModelSerializer): | ||
9 | + permisos_usuario = serializers.SerializerMethodField() | ||
10 | + | ||
11 | + class Meta: | ||
12 | + model = Usuario | ||
13 | + fields = ( | ||
14 | + 'first_name', | ||
15 | + 'last_name', | ||
16 | + 'email', | ||
17 | + 'documento_identidad', | ||
18 | + 'permisos_usuario', | ||
19 | + ) | ||
20 | + | ||
21 | + @staticmethod | ||
22 | + def get_permisos_usuario(instance): | ||
23 | + return instance.get_all_permissions() | ||
24 | + | ||
25 | + included_serializers = { | ||
26 | + 'organismo': 'organismo.serializers.OrganismoSerializer', | ||
27 | + } | ||
28 | + | ||
29 | + | ||
30 | +class CambiarClaveSecretaSerializer(DRFSerializer): | ||
31 | + clave = serializers.CharField(max_length=128, write_only=True, required=True) | ||
32 | + clave_nueva = serializers.CharField(max_length=128, write_only=True, required=True) | ||
33 | + clave_nueva_2 = serializers.CharField(max_length=128, write_only=True, required=True) | ||
34 | + | ||
35 | + def validate_clave(self, value): | ||
36 | + user = self.context['request'].user | ||
37 | + if not user.check_password(value): | ||
38 | + raise serializers.ValidationError("La contraseña anterior no es válida. ¡Intentalo nuevamente!") | ||
39 | + return value | ||
40 | + | ||
41 | + def validate(self, data): | ||
42 | + if data['clave_nueva'] != data['clave_nueva_2']: | ||
43 | + raise serializers.ValidationError({'clave_nueva_2': "Los nuevos campos de contraseñas no coinciden"}) | ||
44 | + password_validation.validate_password(data['clave_nueva'], self.context['request'].user) | ||
45 | + return data | ||
46 | + | ||
47 | + def save(self, **kwargs): | ||
48 | + clave = self.validated_data['clave_nueva'] | ||
49 | + usuario = self.context['request'].user | ||
50 | + usuario.set_password(clave) | ||
51 | + usuario.save() | ||
52 | + return usuario |
project/apps/usuario/tests/__init__.py
0 → 100644
project/apps/usuario/tests/test_usuario.py
0 → 100644
1 | +import pytest | ||
2 | +from django.contrib.auth import get_user_model | ||
3 | + | ||
4 | +from core.tests.fixtures import create_user, CONTENT_TYPE_JSON | ||
5 | +from core.tests.utils import post, get, JSON_CONTENT_TYPE, patch | ||
6 | + | ||
7 | + | ||
8 | +@pytest.mark.django_db | ||
9 | +def test_usuario_cambio_password_satisfactoriamente(): | ||
10 | + usuario_autenticado = create_user(username='mbarrera') | ||
11 | + usuario_autenticado.set_password('ultima_contraseña') | ||
12 | + | ||
13 | + data = { | ||
14 | + "clave": "ultima_contraseña", | ||
15 | + "clave_nueva": "nueva_contraseña", | ||
16 | + "clave_nueva_2": "nueva_contraseña" | ||
17 | + } | ||
18 | + | ||
19 | + endpoint = "/api/v1/usuario/cambiar-clave-secreta/" | ||
20 | + response = patch(endpoint, data=data, content_type=CONTENT_TYPE_JSON, user_logged=usuario_autenticado) | ||
21 | + | ||
22 | + assert response.status_code == 200 | ||
23 | + | ||
24 | + debianitram = get_user_model().objects.get(username='mbarrera') | ||
25 | + assert debianitram.check_password('nueva_contraseña') | ||
26 | + | ||
27 | + | ||
28 | +@pytest.mark.django_db | ||
29 | +def test_usuario_cambio_password_falla_con_clave(): | ||
30 | + usuario_autenticado = create_user(username='mbarrera') | ||
31 | + usuario_autenticado.set_password('ultima_contraseña') | ||
32 | + | ||
33 | + data = { | ||
34 | + "clave": "ultima.-.", | ||
35 | + "clave_nueva": "nueva_contraseña", | ||
36 | + "clave_nueva_2": "nueva_contraseña" | ||
37 | + } | ||
38 | + | ||
39 | + endpoint = "/api/v1/usuario/cambiar-clave-secreta/" | ||
40 | + response = patch(endpoint, data=data, content_type=CONTENT_TYPE_JSON, user_logged=usuario_autenticado) | ||
41 | + | ||
42 | + assert response.status_code == 400 | ||
43 | + errors = response.json()['errors'] | ||
44 | + assert errors[0]['detail'] == 'La contraseña anterior no es válida. ¡Intentalo nuevamente!' | ||
45 | + | ||
46 | + | ||
47 | +@pytest.mark.django_db | ||
48 | +def test_usuario_cambio_password_falla_no_coinciden_nuevas_password(): | ||
49 | + usuario_autenticado = create_user(username='mbarrera') | ||
50 | + usuario_autenticado.set_password('ultima_contraseña') | ||
51 | + | ||
52 | + data = { | ||
53 | + "clave": "ultima_contraseña", | ||
54 | + "clave_nueva": "NuevaContraseña", | ||
55 | + "clave_nueva_2": "nueva_contraseña" | ||
56 | + } | ||
57 | + | ||
58 | + endpoint = "/api/v1/usuario/cambiar-clave-secreta/" | ||
59 | + response = patch(endpoint, data=data, content_type=CONTENT_TYPE_JSON, user_logged=usuario_autenticado) | ||
60 | + | ||
61 | + assert response.status_code == 400 | ||
62 | + errors = response.json()['errors'] | ||
63 | + assert errors[0]['detail'] == 'Los nuevos campos de contraseñas no coinciden' | ||
64 | + |
project/apps/usuario/views.py
0 → 100644
1 | +import requests | ||
2 | +from django.conf import settings | ||
3 | +from oauth2_provider.views import TokenView | ||
4 | + | ||
5 | + | ||
6 | +class CustomTokenView(TokenView): | ||
7 | + def post(self, request, *args, **kwargs): | ||
8 | + | ||
9 | + # realizar validaciones de recaptcha | ||
10 | + if 'captcha_value' not in request.POST: | ||
11 | + raise AttributeError( | ||
12 | + "El campo Valor de Captcha es obligatorio" | ||
13 | + ) | ||
14 | + | ||
15 | + r = requests.post( | ||
16 | + 'https://www.google.com/recaptcha/api/siteverify', | ||
17 | + data={ | ||
18 | + 'secret': settings.SECRET_KEY_CAPTCHA, | ||
19 | + 'response': request.POST['captcha_value'], | ||
20 | + } | ||
21 | + ) | ||
22 | + respuesta = r.json() | ||
23 | + if not respuesta["success"]: | ||
24 | + raise AttributeError( | ||
25 | + "Captcha inválido: " + respuesta['error-codes'][0] | ||
26 | + ) | ||
27 | + | ||
28 | + return super().post(request, *args, **kwargs) |
1 | from rest_framework import routers | 1 | from rest_framework import routers |
2 | 2 | ||
3 | +from organismo import api as organismo_api | ||
4 | +from usuario import api as usuario_api | ||
5 | + | ||
3 | # Define routes | 6 | # Define routes |
4 | router = routers.DefaultRouter() | 7 | router = routers.DefaultRouter() |
8 | + | ||
9 | +router.register(prefix='usuario', viewset=usuario_api.UsuarioViewSet) | ||
10 | +router.register(prefix='organismo', viewset=organismo_api.OrganismoViewSet) |
1 | import sys | 1 | import sys |
2 | +import os | ||
2 | 3 | ||
3 | import environ | 4 | import environ |
4 | 5 | ||
5 | -from django.utils.translation import ugettext_lazy as _ | ||
6 | - | 6 | +from django.utils.translation import gettext_lazy as _ |
7 | 7 | ||
8 | ROOT_DIR = environ.Path(__file__) - 3 | 8 | ROOT_DIR = environ.Path(__file__) - 3 |
9 | PROJECT_DIR = ROOT_DIR.path('project') | 9 | PROJECT_DIR = ROOT_DIR.path('project') |
@@ -33,7 +33,6 @@ DATABASES = { | @@ -33,7 +33,6 @@ DATABASES = { | ||
33 | 'default': env.db('DATABASE_URL') | 33 | 'default': env.db('DATABASE_URL') |
34 | } | 34 | } |
35 | 35 | ||
36 | - | ||
37 | DJANGO_APPS = ( | 36 | DJANGO_APPS = ( |
38 | 'django.contrib.admin', | 37 | 'django.contrib.admin', |
39 | 'django.contrib.auth', | 38 | 'django.contrib.auth', |
@@ -47,10 +46,14 @@ THIRD_PARTY_APPS = ( | @@ -47,10 +46,14 @@ THIRD_PARTY_APPS = ( | ||
47 | 'rest_framework', | 46 | 'rest_framework', |
48 | 'django_filters', | 47 | 'django_filters', |
49 | 'corsheaders', | 48 | 'corsheaders', |
49 | + 'oauth2_provider', | ||
50 | + 'requests', | ||
50 | ) | 51 | ) |
51 | 52 | ||
52 | PROJECT_APPS = ( | 53 | PROJECT_APPS = ( |
53 | 'core', | 54 | 'core', |
55 | + 'organismo', | ||
56 | + 'usuario', | ||
54 | ) | 57 | ) |
55 | 58 | ||
56 | INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS | 59 | INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS |
@@ -64,11 +67,15 @@ MIDDLEWARE = ( | @@ -64,11 +67,15 @@ MIDDLEWARE = ( | ||
64 | 'django.contrib.auth.middleware.AuthenticationMiddleware', | 67 | 'django.contrib.auth.middleware.AuthenticationMiddleware', |
65 | 'django.contrib.messages.middleware.MessageMiddleware', | 68 | 'django.contrib.messages.middleware.MessageMiddleware', |
66 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', | 69 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', |
70 | + 'django.middleware.locale.LocaleMiddleware' | ||
67 | 71 | ||
68 | ) | 72 | ) |
69 | 73 | ||
70 | ROOT_URLCONF = 'project.urls' | 74 | ROOT_URLCONF = 'project.urls' |
71 | 75 | ||
76 | +# Usuario Personalizado | ||
77 | +AUTH_USER_MODEL = 'usuario.Usuario' | ||
78 | + | ||
72 | # Python dotted path to the WSGI application used by Django's runserver. | 79 | # Python dotted path to the WSGI application used by Django's runserver. |
73 | WSGI_APPLICATION = 'project.wsgi.application' | 80 | WSGI_APPLICATION = 'project.wsgi.application' |
74 | 81 | ||
@@ -131,18 +138,37 @@ AUTH_PASSWORD_VALIDATORS = [ | @@ -131,18 +138,37 @@ AUTH_PASSWORD_VALIDATORS = [ | ||
131 | }, | 138 | }, |
132 | ] | 139 | ] |
133 | 140 | ||
141 | +REST_FRAMEWORK = { | ||
142 | + 'PAGE_SIZE': 50, | ||
143 | + 'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler', | ||
144 | + 'DEFAULT_PAGINATION_CLASS': 'core.paginations.LargePagination', | ||
145 | + 'DEFAULT_PARSER_CLASSES': ( | ||
146 | + 'rest_framework_json_api.parsers.JSONParser', | ||
147 | + 'rest_framework.parsers.FormParser', | ||
148 | + 'rest_framework.parsers.MultiPartParser' | ||
149 | + ), | ||
150 | + 'DEFAULT_AUTHENTICATION_CLASSES': ( | ||
151 | + 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', | ||
152 | + 'rest_framework.authentication.BasicAuthentication', | ||
153 | + 'rest_framework.authentication.SessionAuthentication', | ||
154 | + ), | ||
155 | + 'DEFAULT_RENDERER_CLASSES': ('rest_framework_json_api.renderers.JSONRenderer',), | ||
156 | + 'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata', | ||
157 | + 'NON_FIELD_ERRORS_KEY': 'error_messages' | ||
158 | +} | ||
159 | + | ||
160 | +ACTIVAR_HERRAMIENTAS_DEBUGGING = env.bool('ACTIVAR_HERRAMIENTAS_DEBUGGING', default=False) | ||
161 | +if ACTIVAR_HERRAMIENTAS_DEBUGGING: | ||
162 | + INTERNAL_IPS = ['127.0.0.1'] | ||
163 | + INSTALLED_APPS += ('debug_toolbar', 'django_extensions') | ||
164 | + MIDDLEWARE = ('debug_toolbar.middleware.DebugToolbarMiddleware',) + MIDDLEWARE | ||
165 | + REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] += ('rest_framework.renderers.BrowsableAPIRenderer',) | ||
166 | + REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] += ('rest_framework.authentication.SessionAuthentication',) | ||
134 | 167 | ||
135 | AUTHENTICATION_BACKENDS = ( | 168 | AUTHENTICATION_BACKENDS = ( |
136 | 'django.contrib.auth.backends.ModelBackend', | 169 | 'django.contrib.auth.backends.ModelBackend', |
137 | ) | 170 | ) |
138 | 171 | ||
172 | +# Secret Key para Captcha. | ||
173 | +SECRET_KEY_CAPTCHA = '6LczX70hAAAAANBGK03-2l48lmn7QAftwQkR7vUI' | ||
139 | 174 | ||
140 | -ACTIVAR_HERRAMIENTAS_DEBBUGING = env.bool('ACTIVAR_HERRAMIENTAS_DEBBUGING', default=False) | ||
141 | - | ||
142 | -if ACTIVAR_HERRAMIENTAS_DEBBUGING: | ||
143 | - INSTALLED_APPS += ( | ||
144 | - 'debug_toolbar', | ||
145 | - 'django_extensions', | ||
146 | - ) | ||
147 | - | ||
148 | - MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',) |
@@ -16,6 +16,9 @@ Including another URLconf | @@ -16,6 +16,9 @@ Including another URLconf | ||
16 | from django.contrib import admin | 16 | from django.contrib import admin |
17 | from django.urls import path, include | 17 | from django.urls import path, include |
18 | from django.conf import settings | 18 | from django.conf import settings |
19 | +from django.conf.urls.static import static | ||
20 | +from oauth2_provider.urls import base_urlpatterns | ||
21 | +from usuario.views import CustomTokenView | ||
19 | 22 | ||
20 | from .router import router | 23 | from .router import router |
21 | 24 | ||
@@ -25,5 +28,16 @@ admin.site.site_title = getattr(settings, 'PROJECT_NAME_TITLE') | @@ -25,5 +28,16 @@ admin.site.site_title = getattr(settings, 'PROJECT_NAME_TITLE') | ||
25 | 28 | ||
26 | urlpatterns = [ | 29 | urlpatterns = [ |
27 | path('admin/', admin.site.urls), | 30 | path('admin/', admin.site.urls), |
31 | + path('oauth2/token/', CustomTokenView.as_view(), name="token"), | ||
32 | + path('oauth2/', include((base_urlpatterns, 'oauth2_provider'), namespace='oauth2_provider')), | ||
28 | path('api/v1/', include(router.urls)), | 33 | path('api/v1/', include(router.urls)), |
29 | -] | 34 | + path('recaptcha/', include('core.urls', namespace='core')), |
35 | +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | ||
36 | + | ||
37 | +if settings.ACTIVAR_HERRAMIENTAS_DEBUGGING: | ||
38 | + import debug_toolbar | ||
39 | + | ||
40 | + urlpatterns = [ | ||
41 | + path('__debug__/', include(debug_toolbar.urls)), | ||
42 | + path('api-auth/', include('rest_framework.urls')), | ||
43 | + ] + urlpatterns |
1 | [pytest] | 1 | [pytest] |
2 | -DJANGO_SETTINGS_MODULE=PROJECTproject-NAME.settings.testing | 2 | +DJANGO_SETTINGS_MODULE=project.settings.testing |
3 | norecursedirs = requirements deployment | 3 | norecursedirs = requirements deployment |
4 | testpaths = tests | 4 | testpaths = tests |
5 | addopts = --capture=fd --nomigrations | 5 | addopts = --capture=fd --nomigrations |
1 | # Requeriments base. | 1 | # Requeriments base. |
2 | 2 | ||
3 | -Django==3.2.8 | 3 | +Django==4.1.9 |
4 | django-cors-headers==3.10.0 | 4 | django-cors-headers==3.10.0 |
5 | django-filter==21.1 | 5 | django-filter==21.1 |
6 | + | ||
6 | djangorestframework==3.14.0 | 7 | djangorestframework==3.14.0 |
8 | +djangorestframework-jsonapi==6.0.0 | ||
7 | django-environ==0.10.0 | 9 | django-environ==0.10.0 |
8 | -django-crispy-forms==2.0 | 10 | +django-oauth-toolkit==2.3.0 |
11 | + | ||
9 | Pillow==9.5.0 | 12 | Pillow==9.5.0 |
10 | 13 | ||
14 | +# Requests and SSL. | ||
15 | +requests_toolbelt==0.9.1 | ||
16 | +requests==2.27.1 | ||
17 | +pyOpenSSL==22.0.0 | ||
11 | 18 | ||
12 | # database | 19 | # database |
13 | -psycopg2==2.9.6 | ||
14 | -psycopg2-binary==2.7.4 | ||
15 | - | ||
16 | - | ||
17 | - | 20 | +psycopg2==2.9.1 |
21 | +psycopg2-binary==2.9.1 |
-
Please register or login to post a comment