Martín Miranda

Merge branch 'feature/#9_crear_nuevas_app_usuarios' into 'develop'

Feature/#9 crear nuevas app usuarios



See merge request !5
  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()})
  1 +from rest_framework_json_api.pagination import JsonApiPageNumberPagination
  2 +
  3 +
  4 +class LargePagination(JsonApiPageNumberPagination):
  5 + max_page_size = 300
  6 + page_size_query_param = 'page_size'
  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 + }
1 -from django.test import TestCase  
2 -  
3 -# Create your tests here.  
@@ -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(
@@ -55,4 +112,4 @@ def client_authorized(): @@ -55,4 +112,4 @@ def client_authorized():
55 response = json.loads(r.content) 112 response = json.loads(r.content)
56 client = APIClient() 113 client = APIClient()
57 client.credentials(HTTP_AUTHORIZATION='Bearer ' + response['access_token']) 114 client.credentials(HTTP_AUTHORIZATION='Bearer ' + response['access_token'])
58 - return client 115 + return client
  1 +from django.urls import path
  2 +from . import api
  3 +
  4 +app_name = 'core'
  5 +urlpatterns = [
  6 + path('verificar-key/', api.recaptcha)
  7 +]
  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
  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',)
  1 +from django.apps import AppConfig
  2 +
  3 +
  4 +class OrganismoConfig(AppConfig):
  5 + default_auto_field = 'django.db.models.BigAutoField'
  6 + name = 'organismo'
  1 +from django_filters import rest_framework as filters
  2 +
  3 +from organismo.models import Organismo
  4 +
  5 +
  6 +class OrganismoFilter(filters.FilterSet):
  7 + class Meta:
  8 + model = Organismo
  9 + fields = ('nombre', 'descripcion',)
  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 + ]
  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}'
  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 + )
  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
  1 +from django.shortcuts import render
  2 +
  3 +# Create your views here.
  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',)
  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)
  1 +from django.apps import AppConfig
  2 +
  3 +
  4 +class UsuarioConfig(AppConfig):
  5 + default_auto_field = 'django.db.models.BigAutoField'
  6 + name = 'usuario'
  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 + ]
  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'
  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
  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 +
  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')
@@ -26,14 +26,13 @@ PROJECT_NAME_TITLE = env('PROJECT_NAME_TITLE', default='Project Name') @@ -26,14 +26,13 @@ PROJECT_NAME_TITLE = env('PROJECT_NAME_TITLE', default='Project Name')
26 # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug 26 # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
27 DEBUG = env.bool('DJANGO_DEBUG', False) 27 DEBUG = env.bool('DJANGO_DEBUG', False)
28 28
29 -SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!') # noqa  
30 -ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default='*') # noqa 29 +SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!') # noqa
  30 +ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default='*') # noqa
31 31
32 DATABASES = { 32 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