diff --git a/cosiap_api/common/custom_tests.py b/cosiap_api/common/custom_tests.py index b1b5029fb1689c6707397dadd342d9d9672d558a..86b8de6810ccdd60fb8df4a079940ecf4e7e4a54 100644 --- a/cosiap_api/common/custom_tests.py +++ b/cosiap_api/common/custom_tests.py @@ -7,6 +7,7 @@ from common.views import BasePermissionAPIView from rest_framework.response import Response from django.urls import reverse from django.test import tag +from urllib.parse import urlencode from users.permisos import es_admin, primer_login from rest_framework.permissions import AllowAny, IsAuthenticated @@ -63,7 +64,7 @@ class BasePerUserTestCase(APITestCase): RFC='1234567890123', # Ajustar tipo de dato direccion='Calle sin Nombre', codigo_postal='89890', # Ajustar tipo de dato - municipio_id=1, + municipio_id=2, poblacion=5, INE='awdawd' ) @@ -79,7 +80,7 @@ class BasePerUserTestCase(APITestCase): 'access': str(refresh.access_token), } - def perform_request(self, method, url_name, url_kwargs=None, token=None, user=None, data=None, is_multipart=False): + def perform_request(self, method, url_name, url_kwargs=None, query_params=None, token=None, user=None, data=None, is_multipart=False): """ Realiza una solicitud a la vista especificada utilizando el método HTTP indicado. @@ -100,6 +101,9 @@ class BasePerUserTestCase(APITestCase): client.force_authenticate(user=user, token=token['access']) url = reverse(url_name, kwargs=url_kwargs) + if query_params: + query_string = urlencode(query_params) + url = f"{url}?{query_string}" method = method.lower() if is_multipart: diff --git a/cosiap_api/common/serializers.py b/cosiap_api/common/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..5db575078ce5353ed46cb3e685e2c76b8358e203 --- /dev/null +++ b/cosiap_api/common/serializers.py @@ -0,0 +1,13 @@ +from rest_framework import serializers + +class DynamicModelSerializer(serializers.ModelSerializer): + def __init__(self, *args, **kwargs): + # Expecting 'model' to be passed in kwargs + model = kwargs.pop('model', None) + if model: + self.Meta.model = model + super().__init__(*args, **kwargs) + + class Meta: + model = None + fields = '__all__' \ No newline at end of file diff --git a/cosiap_api/common/utils.py b/cosiap_api/common/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e63ba45c9870a48515eebab68403ac673dc24570 --- /dev/null +++ b/cosiap_api/common/utils.py @@ -0,0 +1,5 @@ +import json + +def printDict(diccionario): + #print(json.dumps(diccionario, indent=4, separators=(",", ": "), ensure_ascii=False)) + pass \ No newline at end of file diff --git a/cosiap_api/cosiap_api/urls.py b/cosiap_api/cosiap_api/urls.py index a08789bf85edb82787465ac2f4ebfeb8f7330d85..b881abe2bfd1a89f5af308d350d2c503fe0b1210 100644 --- a/cosiap_api/cosiap_api/urls.py +++ b/cosiap_api/cosiap_api/urls.py @@ -28,6 +28,8 @@ urlpatterns = [ path('api/notificaciones/',include('notificaciones.urls')), path('api/solicitudes/',include('solicitudes.urls')), path('api/dynamic-tables/',include('dynamic_tables.urls')), + path('api/formularios/',include('dynamic_forms.urls')), + # API Doc UI: path('api/schema/', SpectacularAPIView.as_view(), name='schema'), diff --git a/cosiap_api/dynamic_forms/migrations/0003_permitir_null_campos_many_to_many.py b/cosiap_api/dynamic_forms/migrations/0003_permitir_null_campos_many_to_many.py new file mode 100644 index 0000000000000000000000000000000000000000..8536fabd41e168123c675b5aeae3bc4cb1891e2f --- /dev/null +++ b/cosiap_api/dynamic_forms/migrations/0003_permitir_null_campos_many_to_many.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.7 on 2024-08-02 18:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dynamic_forms', '0002_creacion_modelos_formularios_dinamicos_02'), + ] + + operations = [ + migrations.AlterField( + model_name='dynamicform', + name='secciones', + field=models.ManyToManyField(blank=True, null=True, through='dynamic_forms.DynamicFormsSecciones', to='dynamic_forms.seccion', verbose_name='Secciones'), + ), + migrations.AlterField( + model_name='elemento', + name='opciones', + field=models.ManyToManyField(blank=True, null=True, through='dynamic_forms.ElementosOpciones', to='dynamic_forms.opcion', verbose_name='Opciones'), + ), + migrations.AlterField( + model_name='rcasillas', + name='valor', + field=models.ManyToManyField(blank=True, null=True, to='dynamic_forms.opcion'), + ), + migrations.AlterField( + model_name='seccion', + name='elementos', + field=models.ManyToManyField(blank=True, null=True, through='dynamic_forms.SeccionesElementos', to='dynamic_forms.elemento', verbose_name='Elementos'), + ), + ] diff --git a/cosiap_api/dynamic_forms/migrations/0004_permitir_null_campos_many_to_many.py b/cosiap_api/dynamic_forms/migrations/0004_permitir_null_campos_many_to_many.py new file mode 100644 index 0000000000000000000000000000000000000000..f77abf34e53b43531f166d79dc54264c58ab4a83 --- /dev/null +++ b/cosiap_api/dynamic_forms/migrations/0004_permitir_null_campos_many_to_many.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.7 on 2024-08-02 19:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dynamic_forms', '0003_permitir_null_campos_many_to_many'), + ] + + operations = [ + migrations.AlterField( + model_name='dynamicform', + name='secciones', + field=models.ManyToManyField(blank=True, through='dynamic_forms.DynamicFormsSecciones', to='dynamic_forms.seccion', verbose_name='Secciones'), + ), + migrations.AlterField( + model_name='elemento', + name='opciones', + field=models.ManyToManyField(blank=True, through='dynamic_forms.ElementosOpciones', to='dynamic_forms.opcion', verbose_name='Opciones'), + ), + migrations.AlterField( + model_name='rcasillas', + name='valor', + field=models.ManyToManyField(blank=True, to='dynamic_forms.opcion'), + ), + migrations.AlterField( + model_name='seccion', + name='elementos', + field=models.ManyToManyField(blank=True, through='dynamic_forms.SeccionesElementos', to='dynamic_forms.elemento', verbose_name='Elementos'), + ), + ] diff --git a/cosiap_api/dynamic_forms/migrations/0005_set_null_ordenes.py b/cosiap_api/dynamic_forms/migrations/0005_set_null_ordenes.py new file mode 100644 index 0000000000000000000000000000000000000000..5ad7597fdbdecf93f70e65a71aa1b06a68b0821c --- /dev/null +++ b/cosiap_api/dynamic_forms/migrations/0005_set_null_ordenes.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.7 on 2024-08-02 19:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dynamic_forms', '0004_permitir_null_campos_many_to_many'), + ] + + operations = [ + migrations.AlterField( + model_name='dynamicformssecciones', + name='orden', + field=models.IntegerField(default=0, verbose_name='Orden'), + ), + migrations.AlterField( + model_name='elementosopciones', + name='orden', + field=models.IntegerField(default=0, verbose_name='Orden'), + ), + migrations.AlterField( + model_name='seccioneselementos', + name='orden', + field=models.IntegerField(default=0, verbose_name='Orden'), + ), + ] diff --git a/cosiap_api/dynamic_forms/models.py b/cosiap_api/dynamic_forms/models.py index 80fd960c3eb36fccd4b45557848bbe172204ce8a..2711f42f7062245ee6fec5882e20d45f57916d70 100644 --- a/cosiap_api/dynamic_forms/models.py +++ b/cosiap_api/dynamic_forms/models.py @@ -4,6 +4,8 @@ from model_utils.managers import InheritanceManager from django.db import IntegrityError from dynamic_formats.models import DynamicFormat from common.nombres_archivos import nombre_archivo_respuesta_doc +from django.core.exceptions import ValidationError +from django.core.validators import MinLengthValidator, MaxLengthValidator class Opcion(models.Model): """ @@ -49,7 +51,7 @@ class Elemento(models.Model): min_digits = models.IntegerField(default=0, verbose_name='Mínimo de dígitos') max_digits = models.IntegerField(default=10, verbose_name='Máximo de dígitos') - opciones = models.ManyToManyField(Opcion, through='ElementosOpciones', verbose_name='Opciones') + opciones = models.ManyToManyField(Opcion, through='ElementosOpciones', verbose_name='Opciones', blank=True) formato = models.ForeignKey(DynamicFormat, on_delete=models.SET_NULL, verbose_name='Elemento', default=None, null=True, blank=True) class Meta: verbose_name_plural = '05. Elementos' @@ -66,7 +68,7 @@ class ElementosOpciones(models.Model): """ elemento = models.ForeignKey(Elemento, on_delete=models.CASCADE, verbose_name='Elemento') opcion = models.ForeignKey(Opcion, on_delete=models.CASCADE, verbose_name='Opción') - orden = models.IntegerField(verbose_name='Orden') + orden = models.IntegerField(verbose_name='Orden', default=0) class Meta: unique_together = ('elemento', 'opcion') verbose_name_plural = '06. Rel Elemento - Opciones' @@ -81,12 +83,12 @@ class Seccion(models.Model): """ class Tipo(models.TextChoices): UNICO = 'unico', 'Único' - AGREGACION = 'agregacion', 'Agregación' + LISTA = 'lista', 'Lista' nombre = models.CharField(max_length=100, verbose_name='Nombre de la sección') tipo = models.CharField(max_length=20, choices=Tipo.choices, default=Tipo.UNICO, verbose_name='Tipo de sección') - elementos = models.ManyToManyField(Elemento, through='SeccionesElementos', verbose_name='Elementos') + elementos = models.ManyToManyField(Elemento, through='SeccionesElementos', verbose_name='Elementos', blank=True) class Meta: verbose_name_plural = '03. Secciones' @@ -102,7 +104,7 @@ class SeccionesElementos(models.Model): """ seccion = models.ForeignKey(Seccion, on_delete=models.CASCADE, verbose_name='Sección') elemento = models.ForeignKey(Elemento, on_delete=models.CASCADE, verbose_name='Elemento') - orden = models.IntegerField(verbose_name='Orden') + orden = models.IntegerField(verbose_name='Orden', default=0) class Meta: unique_together = ('seccion', 'elemento') verbose_name_plural = '04. Rel Secciones - Elementos' @@ -116,7 +118,7 @@ class DynamicForm(models.Model): - nombre (str, Nombre del formulario dinámico) """ nombre = models.CharField(max_length=100, verbose_name='Nombre del formulario') - secciones = models.ManyToManyField(Seccion, through='DynamicFormsSecciones', verbose_name='Secciones') + secciones = models.ManyToManyField(Seccion, through='DynamicFormsSecciones', verbose_name='Secciones', blank=True) class Meta: verbose_name_plural = "01. Formularios" @@ -131,7 +133,7 @@ class DynamicFormsSecciones(models.Model): """ dynamic_form = models.ForeignKey(DynamicForm, on_delete=models.CASCADE, verbose_name='Formulario dinámico') seccion = models.ForeignKey(Seccion, on_delete=models.CASCADE, verbose_name='Sección') - orden = models.IntegerField(verbose_name='Orden') + orden = models.IntegerField(verbose_name='Orden', default=0) class Meta: unique_together = ('dynamic_form', 'seccion') verbose_name_plural = '02. Rel ormulario - Secciones' @@ -165,11 +167,13 @@ class Respuesta(models.Model): objects = InheritanceManager() def __str__(self): - return f"Respuesta {type(self)} - Elemento: {self.elemento} - Solicitante: {self.solicitud.solicitante_id}" + return f"Respuesta {type(self)} - Elemento: {self.elemento} - Solicitud: {self.solicitud_id}" def save(self, *args, **kwargs): if self._state.adding: - if self.elemento.seccioneselementos_set.filter(seccion__tipo='unico').exists(): + # Solo realiza la verificación si estás creando una respuesta nueva + if self.elemento.seccion.tipo == 'unico': + # Verificar si ya existe una respuesta para esta combinación if Respuesta.objects.filter(elemento=self.elemento, solicitud=self.solicitud).exists(): raise IntegrityError('Ya existe una respuesta para este elemento y solicitud') super().save(*args, **kwargs) @@ -193,6 +197,25 @@ class RNumerico(Respuesta): return str(self.valor) class Meta: verbose_name_plural = '10. R Numericos' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Accede a la instancia de elemento y obtén los valores numMin y numMax + elemento = self.elemento if self.pk else None + numMin = elemento.numMin if elemento else None + numMax = elemento.numMax if elemento else None + # Configura validadores de longitud mínima y máxima en el campo 'valor' + if numMin is not None: + self._meta.get_field('valor').validators.append(MinLengthValidator(numMin)) + if numMax is not None: + self._meta.get_field('valor').validators.append(MaxLengthValidator(numMax)) + + + def clean(self): + super().clean() + obligatorio = self.elemento.obligatorio + if (not self.valor or not self.valor.strip()) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") class RTextoCorto(Respuesta): @@ -206,6 +229,12 @@ class RTextoCorto(Respuesta): class Meta: verbose_name_plural = '11. R TextoCortos' + def clean(self): + super().clean() + obligatorio = self.elemento.obligatorio + if (not self.valor or not self.valor.strip()) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") + class RTextoParrafo(Respuesta): valor = models.TextField(null=True, blank=True) @@ -218,6 +247,12 @@ class RTextoParrafo(Respuesta): class Meta: verbose_name_plural = '12. R TextoParrafos' + def clean(self): + super().clean() + obligatorio = self.elemento.obligatorio + if (not self.valor or not self.valor.strip()) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") + class RHora(Respuesta): valor = models.TimeField(null=True, blank=True) @@ -230,6 +265,12 @@ class RHora(Respuesta): class Meta: verbose_name_plural = '13. R Horas' + def clean(self): + super().clean() + obligatorio = self.elemento.obligatorio + if (not self.valor) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") + class RFecha(Respuesta): valor = models.DateField(null=True, blank=True) @@ -242,6 +283,12 @@ class RFecha(Respuesta): class Meta: verbose_name_plural = '14. R Fechas' + def clean(self): + super().clean() + obligatorio = self.elemento.obligatorio + if (not self.valor) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") + class ROpcionMultiple(Respuesta): valor = models.ForeignKey(Opcion, on_delete=models.CASCADE, null=True, blank=True) otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=True) @@ -257,8 +304,24 @@ class ROpcionMultiple(Respuesta): class Meta: verbose_name_plural = '15. R Opcion Multiples' + def clean(self): + super().clean() + obligatorio = self.elemento.obligatorio + opcOtro = self.elemento.opcion_otro + respuesta = self.valor + otro = self.otro + if (not respuesta) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") + if obligatorio: + if not ((opcOtro and (otro and otro.strip())) or not opcOtro) and (respuesta and respuesta.nombre == 'Otro'): + raise ValidationError("Este campo es obligatorio") + if (otro and otro.strip()) and (respuesta and not respuesta.nombre == 'Otro'): + raise ValidationError("No esta seleccionada opcion Otro") + if respuesta and (respuesta.nombre == 'Otro' and not( otro and otro.strip())): + raise ValidationError("Si eliges 'otro', debes proporcionar más detalles en el campo 'otro'.") + class RCasillas(Respuesta): - valor = models.ManyToManyField(Opcion, blank=True) + valor = models.ManyToManyField(Opcion, blank=True) otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=True) def getStringValue(self): @@ -276,6 +339,23 @@ class RCasillas(Respuesta): return string class Meta: verbose_name_plural = '16. R Casillas' + + def clean(self): + super().clean() + respuesta = self.valor + noRespuesta = respuesta.count() == 0 + obligatorio = self.elemento.obligatorio + opcOtro = self.elemento.opcionOtro + otro = self.otro + if (noRespuesta) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") + if obligatorio: + if not ((opcOtro and (otro and otro.strip())) or not opcOtro) and (respuesta and respuesta.filter(nombre='Otro').exists()): + raise ValidationError("Este campo es obligatorio") + if (otro and otro.strip()) and (respuesta and not respuesta.filter(nombre='Otro').exists()): + raise ValidationError("No esta seleccionada opcion Otro") + if respuesta and (respuesta.filter(nombre='Otro').exists() and not( otro and otro.strip())): + raise ValidationError("Si eliges 'otro', debes proporcionar más detalles en el campo 'otro'.") class RDesplegable(Respuesta): @@ -293,6 +373,22 @@ class RDesplegable(Respuesta): class Meta: verbose_name_plural = '17. R Desplegables' + def clean(self): + super().clean() + respuesta = self.valor + obligatorio = self.elemento.obligatorio + opcOtro = self.elemento.opcionOtro + otro = self.otro + if (not respuesta) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") + if obligatorio: + if not ((opcOtro and (otro and otro.strip())) or not opcOtro) and (respuesta and respuesta.nombre == 'Otro'): + raise ValidationError("Este campo es obligatorio") + if (otro and otro.strip()) and (respuesta and not respuesta.nombre == 'Otro'): + raise ValidationError("No esta seleccionada opcion Otro") + if respuesta and (respuesta.nombre == 'Otro' and not( otro and otro.strip())): + raise ValidationError("Si eliges 'otro', debes proporcionar más detalles en el campo 'otro'.") + class RDocumento(Respuesta): valor = models.FileField(verbose_name='Subir Documento', upload_to=nombre_archivo_respuesta_doc , null=True, blank=True) @@ -300,3 +396,10 @@ class RDocumento(Respuesta): return self.valor.name if self.valor else '-----' class Meta: verbose_name_plural = '18. R Documentos' + + def clean(self): + super().clean() + respuesta = self.valor + obligatorio = self.elemento.obligatorio + if (not respuesta) and obligatorio: + raise ValidationError("Este campo es Obligatorio.") \ No newline at end of file diff --git a/cosiap_api/dynamic_forms/serializers.py b/cosiap_api/dynamic_forms/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..2e95c3ff513e7d5efc348d7cc475f10230023443 --- /dev/null +++ b/cosiap_api/dynamic_forms/serializers.py @@ -0,0 +1,137 @@ +from rest_framework import serializers +from django.db import models +from dynamic_forms.models import ( + Opcion, Elemento, ElementosOpciones, Seccion, SeccionesElementos, + DynamicForm, DynamicFormsSecciones, RAgregacion, Respuesta, RNumerico, + RTextoCorto, RTextoParrafo, RHora, RFecha, ROpcionMultiple, RCasillas, + RDesplegable, RDocumento +) +from django.core.validators import MinLengthValidator, MaxLengthValidator +from common.serializers import DynamicModelSerializer + +class BaseDynamicFormSerializer(serializers.ModelSerializer): + def get_child_queryset(self, obj, lookup_str, debug=False): + child_objs = getattr(obj, lookup_str) + child_objs = child_objs.all() if isinstance(child_objs, models.manager.BaseManager) else child_objs + if debug: + print(f'\nPREFETCH DATA {self.__class__.__name__}({obj}) ::: {obj._prefetched_objects_cache}') if hasattr(obj, '_prefetched_objects_cache') else print(f"\n> {self.__class__.__name__}({obj}) no tiene _prefetched_objects_cache") + print(f'|-CHILD OBJECTS {obj}: {child_objs}') + return child_objs + + def __str__(self): + # Representa la clase y el objeto relacionado si existe + instance_str = f"{self.instance}" if self.instance else "No instance" + return f"{self.__class__.__name__}({instance_str})" + +# Serializer para Opcion +class OpcionSerializer(BaseDynamicFormSerializer): + class Meta: + model = Opcion + fields = '__all__' + + +class ElementosOpcionesSerializer(BaseDynamicFormSerializer): + opcion = serializers.SerializerMethodField() + + class Meta: + model = ElementosOpciones + fields = ['opcion','orden'] + + def get_opcion(self, obj): + ''' Definimos la logica para obtener la representacion de opcion''' + instance = self.get_child_queryset(obj, 'opcion') + return OpcionSerializer(instance).data + + +class ElementoSerializer(BaseDynamicFormSerializer): + opciones = serializers.SerializerMethodField() + + class Meta: + model = Elemento + fields = '__all__' + + def get_opciones(self, obj): + ''' Definimos la logica para obtener la representacion de opciones''' + queryset = self.get_child_queryset(obj, 'elementosopciones_set') + return ElementosOpcionesSerializer(queryset, many=True).data + +# Serializer para SeccionesElementos +class SeccionesElementosSerializer(BaseDynamicFormSerializer): + elemento = serializers.SerializerMethodField() + + class Meta: + model = SeccionesElementos + fields = ['elemento','orden'] + + def get_elemento(self, obj): + ''' Definimos la logica para obtener la representacion de elemento''' + instance = self.get_child_queryset(obj, 'elemento') + return ElementoSerializer(instance).data + +# Serializer para Seccion +class SeccionSerializer(BaseDynamicFormSerializer): + elementos = serializers.SerializerMethodField() + + class Meta: + model = Seccion + fields = '__all__' + + def get_elementos(self, obj): + ''' Definimos la logica para obtener la representacion de elementos''' + queryset = self.get_child_queryset(obj, 'seccioneselementos_set') + return SeccionesElementosSerializer(queryset, many=True).data + + +# Serializer para DynamicFormsSecciones +class DynamicFormsSeccionesSerializer(BaseDynamicFormSerializer): + seccion = serializers.SerializerMethodField() + + class Meta: + model = DynamicFormsSecciones + fields = ['seccion','orden'] + + def get_seccion(self, obj): + ''' Definimos la logica para obtener la representacion de seccion''' + instance = self.get_child_queryset(obj, 'seccion') + return SeccionSerializer(instance).data + +# Serializer para DynamicForm +class DynamicFormSerializer(BaseDynamicFormSerializer): + secciones = serializers.SerializerMethodField() + + class Meta: + model = DynamicForm + fields = '__all__' + + def get_secciones(self, obj): + ''' Definimos la logica para obtener la representacion de secciones''' + queryset = self.get_child_queryset(obj, 'dynamicformssecciones_set') + return DynamicFormsSeccionesSerializer(queryset, many=True).data + + + + +RESPUESTA_MODELOS = { + 'RNumerico': RNumerico, + 'RTextoCorto': RTextoCorto, + 'RTextoParrafo': RTextoParrafo, + 'RHora': RHora, + 'RFecha': RFecha, + 'ROpcionMultiple': ROpcionMultiple, + 'RCasillas': RCasillas, + 'RDesplegable': RDesplegable, + 'RDocumento': RDocumento, +} + +class RespuestaSerializer(serializers.ModelSerializer): + class Meta: + model = Respuesta + fields = '__all__' + + def to_representation(self, instance): + serializer = DynamicModelSerializer(instance=instance, model=type(instance)) + return serializer.data + + def to_internal_value(self, data): + # Implementación del mapeo inverso según sea necesario + raise NotImplementedError("This method should be implemented dynamically.") diff --git a/cosiap_api/dynamic_forms/tests.py b/cosiap_api/dynamic_forms/tests.py index 7ce503c2dd97ba78597f6ff6e4393132753573f6..4f4866a7e167eaf3cea4c7dd806dc817e849ec79 100644 --- a/cosiap_api/dynamic_forms/tests.py +++ b/cosiap_api/dynamic_forms/tests.py @@ -1,3 +1,728 @@ -from django.test import TestCase +from rest_framework import status +from common import custom_tests as c_tests +from common.custom_tests import BasePerUserTestCase +from django.core.files.uploadedfile import SimpleUploadedFile +from common.utils import printDict +from PIL import Image +import io +import os +from dynamic_forms.models import ( + Opcion, Elemento, ElementosOpciones, Seccion, SeccionesElementos, + DynamicForm, DynamicFormsSecciones, Respuesta +) + +from django.db import connection +from django.test.utils import CaptureQueriesContext + + + +#TESTS PARA OPCIONES + +class TestsPermisosOpcion(c_tests.PermissionTestCase): + url_name = 'dynamic_forms:opciones' + + methods_responses = { + 'get': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_200_OK, + 'solicitante': status.HTTP_200_OK, + 'anonymous': status.HTTP_401_UNAUTHORIZED + }, + 'post': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_400_BAD_REQUEST, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + } + } + +class TestsPermisosOpcionPK(c_tests.PermissionTestCase): + url_name = 'dynamic_forms:opciones_pk' + + def get_url_kwargs(self): + self.opcion = Opcion.objects.create(nombre='Opcion de Prueba 0') + return {'pk': self.opcion.pk} + + methods_responses = { + 'put': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_400_BAD_REQUEST, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + }, + 'delete': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_204_NO_CONTENT, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + } + } + + +class OpcionTests(BasePerUserTestCase): + def reset(self): + print('') + Opcion.objects.all().exclude(nombre='Otro').delete() + Elemento.objects.all().delete() + Seccion.objects.all().delete() + DynamicForm.objects.all().delete() + + self.opcion1 = Opcion.objects.create(nombre='Opcion prueba 1 piña') + self.opcion2 = Opcion.objects.create(nombre='Opcion prueba 2 manzana') + self.opcion3 = Opcion.objects.create(nombre='Opcion prueba 3 naranja') + self.opcion4 = Opcion.objects.create(nombre='Opcion prueba 4 perejil') + self.opcion5 = Opcion.objects.create(nombre='Opcion prueba 5 apio') + self.opcion6 = Opcion.objects.create(nombre='Opcion prueba 6 tomate') + self.opcion7 = Opcion.objects.create(nombre='Opcion prueba 7 pez') + self.opcion_count = 8 + + def tests(self): + subtest_name = 'test_get_opciones' + with self.subTest(subtest_name): + self.reset() + with CaptureQueriesContext(connection) as ctx: + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:opciones', token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), self.opcion_count) + print(f'\nRENDIMIENTO QUERYS: {len(ctx.captured_queries)}') + + subtest_name = 'test_get_opciones_incorrect_user' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:opciones', token=self.user_token, user=self.user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertFalse('data' in response.data) + + subtest_name = 'test_get_opciones_substring_filter' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:opciones', query_params={'q': 'an'}, token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), 2) + + subtest_name = 'test_get_opciones_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:opciones_pk', url_kwargs={'pk': self.opcion1.pk}, token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['data']['nombre'], 'Opcion prueba 1 piña') + self.assertEqual(response.status_code, status.HTTP_200_OK) + + subtest_name = 'test_get_opciones_pk_not_found' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:opciones_pk', url_kwargs={'pk': 99999999999}, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + subtest_name = 'test_post_opciones' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + data = { + 'nombre': 'Nueva opcion recien creada' + } + response = self.perform_request('post', url_name='dynamic_forms:opciones', data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Opcion.objects.count(), self.opcion_count+1) + + subtest_name = 'test_post_opciones_bad_request' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + data = { + + } + response = self.perform_request('post', url_name='dynamic_forms:opciones', data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(Opcion.objects.count(), self.opcion_count) + + subtest_name = 'test_put_opciones_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + dict_original = self.opcion1.__dict__.copy() + dict_original.pop('_state', None) + print(dict_original) + data = { + 'nombre': 'Nombre modificado' + } + response = self.perform_request('put', url_name='dynamic_forms:opciones_pk', url_kwargs={'pk': dict_original['id']}, data=data, token=self.admin_token, user=self.admin_user) + dict_nuevo = Opcion.objects.get(id=dict_original['id']).__dict__.copy() + dict_nuevo.pop('_state', None) + print(dict_nuevo) + printDict(response.data) + self.assertNotEqual(dict_nuevo, dict_original) + self.assertEqual(dict_nuevo, response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(Opcion.objects.count(), self.opcion_count) + + subtest_name = 'test_put_opciones_pk_bad_request' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + dict_original = self.opcion1.__dict__.copy() + dict_original.pop('_state', None) + print(dict_original) + data = { + + } + response = self.perform_request('put', url_name='dynamic_forms:opciones_pk', url_kwargs={'pk': dict_original['id']}, data=data, token=self.admin_token, user=self.admin_user) + dict_nuevo = Opcion.objects.get(id=dict_original['id']).__dict__.copy() + dict_nuevo.pop('_state', None) + print(dict_nuevo) + printDict(response.data) + self.assertEqual(dict_nuevo, dict_original) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(Opcion.objects.count(), self.opcion_count) + + subtest_name = 'test_put_opciones_pk_not_found' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('put', url_name='dynamic_forms:opciones_pk', url_kwargs={'pk': 99999999999}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + subtest_name = 'test_delete_opciones_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('delete', url_name='dynamic_forms:opciones_pk', url_kwargs={'pk': self.opcion1.pk}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Opcion.objects.count(), self.opcion_count-1) + + subtest_name = 'test_delete_opciones_pk_not_found' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('delete', url_name='dynamic_forms:opciones_pk', url_kwargs={'pk': 99999999999}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + +#TESTS PARA ElementoAPIVIEW +class TestsPermisosElemento(c_tests.PermissionTestCase): + url_name = 'dynamic_forms:elementos' + + methods_responses = { + 'get': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_200_OK, + 'solicitante': status.HTTP_200_OK, + 'anonymous': status.HTTP_401_UNAUTHORIZED + }, + 'post': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_400_BAD_REQUEST, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + } + } + +class TestsPermisosElementoPK(c_tests.PermissionTestCase): + url_name = 'dynamic_forms:elementos_pk' + + def get_url_kwargs(self): + self.elemento = Elemento.objects.create(nombre='Elemento de Prueba 0', tipo=Elemento.Tipo.TEXTO_CORTO) + return {'pk': self.elemento.pk} + + methods_responses = { + 'put': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_400_BAD_REQUEST, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + }, + 'delete': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_204_NO_CONTENT, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + } + } + +class ElementoTests(OpcionTests): + def reset(self): + super().reset() + Elemento.objects.all().delete() + + self.elemento1 = Elemento.objects.create(nombre='Elemento prueba 1', tipo=Elemento.Tipo.TEXTO_CORTO) + self.elemento2 = Elemento.objects.create(nombre='Elemento prueba 2', tipo=Elemento.Tipo.NUMERICO) + self.elemento3 = Elemento.objects.create(nombre='Elemento prueba 3', tipo=Elemento.Tipo.FECHA) + + self.elemento4 = Elemento.objects.create(nombre='Elemento prueba 4', tipo=Elemento.Tipo.NUMERICO) + self.elemento5 = Elemento.objects.create(nombre='Elemento prueba 5', tipo=Elemento.Tipo.FECHA) + self.elemento_count = 5 + + self.elemento2.opciones.set([self.opcion1, self.opcion2, self.opcion3, self.opcion4]) + self.elemento3.opciones.set([self.opcion5, self.opcion6, self.opcion7]) + + self.elemento4.opciones.set([self.opcion1, self.opcion2, self.opcion3, self.opcion4]) + self.elemento5.opciones.set([self.opcion5, self.opcion6, self.opcion7]) + + def tests(self): + subtest_name = 'test_get_elementos' + with self.subTest(subtest_name): + self.reset() + with CaptureQueriesContext(connection) as ctx: + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:elementos', token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), self.elemento_count) + + print(f'\nRENDIMIENTO QUERYS: {len(ctx.captured_queries)}') + + subtest_name = 'test_get_elementos_incorrect_user' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:elementos', token=self.user_token, user=self.user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertFalse('data' in response.data) + + subtest_name = 'test_get_elementos_substring_filter' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:elementos', query_params={'q': 'prueba 2'}, token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), 1) + + subtest_name = 'test_get_elementos_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:elementos_pk', url_kwargs={'pk': self.elemento1.pk}, token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['data']['nombre'], 'Elemento prueba 1') + + subtest_name = 'test_get_elementos_pk_not_found' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:elementos_pk', url_kwargs={'pk': 99999999999}, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + subtest_name = 'test_post_elementos' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + data = { + 'nombre': 'Nuevo elemento', + 'tipo': Elemento.Tipo.TEXTO_CORTO + } + response = self.perform_request('post', url_name='dynamic_forms:elementos', data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Elemento.objects.count(), self.elemento_count+1) + + subtest_name = 'test_post_elementos_bad_request' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + data = {} + response = self.perform_request('post', url_name='dynamic_forms:elementos', data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(Elemento.objects.count(), self.elemento_count) + + subtest_name = 'test_put_elementos_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + dict_original = self.elemento1.__dict__.copy() + dict_original.pop('_state', None) + print(dict_original) + data = { + 'nombre': 'Nombre modificado' + } + response = self.perform_request('put', url_name='dynamic_forms:elementos_pk', url_kwargs={'pk': dict_original['id']}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(Elemento.objects.get(pk=self.elemento1.pk).nombre, 'Nombre modificado') + + subtest_name = 'test_put_elementos_pk_bad_request' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + dict_original = self.elemento1.__dict__.copy() + dict_original.pop('_state', None) + print(dict_original) + data = { + 'nombre': '' + } + response = self.perform_request('put', url_name='dynamic_forms:elementos_pk', url_kwargs={'pk': dict_original['id']}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(Elemento.objects.get(pk=self.elemento1.pk).nombre, 'Elemento prueba 1') + + subtest_name = 'test_delete_elementos_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('delete', url_name='dynamic_forms:elementos_pk', url_kwargs={'pk': self.elemento1.pk}, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Elemento.objects.count(), self.elemento_count-1) + +#TESTS para SeccionAPIView + +class TestsPermisosSeccion(c_tests.PermissionTestCase): + url_name = 'dynamic_forms:secciones' + + methods_responses = { + 'get': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_200_OK, + 'solicitante': status.HTTP_200_OK, + 'anonymous': status.HTTP_401_UNAUTHORIZED + }, + 'post': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_400_BAD_REQUEST, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + } + } + +class TestsPermisosSeccionPK(c_tests.PermissionTestCase): + url_name = 'dynamic_forms:secciones_pk' + + def get_url_kwargs(self): + self.seccion = Seccion.objects.create(nombre='Sección de Prueba 0', tipo=Seccion.Tipo.UNICO) + return {'pk': self.seccion.pk} + + methods_responses = { + 'put': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_400_BAD_REQUEST, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + }, + 'delete': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_204_NO_CONTENT, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + } + } + +class SeccionTests(ElementoTests): + def reset(self): + super().reset() + Seccion.objects.all().delete() + + self.seccion1 = Seccion.objects.create(nombre='Sección prueba 1', tipo=Seccion.Tipo.UNICO) + self.seccion2 = Seccion.objects.create(nombre='Sección prueba 2', tipo=Seccion.Tipo.UNICO) + self.seccion3 = Seccion.objects.create(nombre='Sección prueba 3', tipo=Seccion.Tipo.LISTA) + + self.seccion4 = Seccion.objects.create(nombre='Sección prueba 4', tipo=Seccion.Tipo.UNICO) + self.seccion5 = Seccion.objects.create(nombre='Sección prueba 5', tipo=Seccion.Tipo.LISTA) + self.seccion_count = 5 + + self.seccion2.elementos.set([self.elemento1, self.elemento2]) + self.seccion3.elementos.set([self.elemento2, self.elemento3, self.elemento5]) + + self.seccion4.elementos.set([self.elemento4, self.elemento2]) + self.seccion5.elementos.set([self.elemento2, self.elemento5]) + + def tests(self): + subtest_name = 'test_get_secciones' + with self.subTest(subtest_name): + self.reset() + with CaptureQueriesContext(connection) as ctx: + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:secciones', token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), self.seccion_count) + print(f'\nRENDIMIENTO QUERYS: {len(ctx.captured_queries)}') + + subtest_name = 'test_get_secciones_incorrect_user' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:secciones', token=self.user_token, user=self.user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertFalse('data' in response.data) + + subtest_name = 'test_get_secciones_substring_filter' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:secciones', query_params={'q': 'prueba 2'}, token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), 1) + + subtest_name = 'test_get_secciones_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:secciones_pk', url_kwargs={'pk': self.seccion1.pk}, token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['data']['nombre'], 'Sección prueba 1') + + subtest_name = 'test_get_secciones_pk_not_found' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:secciones_pk', url_kwargs={'pk': 99999999999}, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + subtest_name = 'test_post_secciones' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + data = { + 'nombre': 'Nueva seccion', + 'tipo': Seccion.Tipo.UNICO + } + response = self.perform_request('post', url_name='dynamic_forms:secciones', data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Seccion.objects.count(), self.seccion_count+1) + + subtest_name = 'test_post_secciones_bad_request' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + data = {} + response = self.perform_request('post', url_name='dynamic_forms:secciones', data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(Seccion.objects.count(), self.seccion_count) + + subtest_name = 'test_put_secciones_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + dict_original = self.seccion1.__dict__.copy() + dict_original.pop('_state', None) + print(dict_original) + data = { + 'nombre': 'Nombre modificado' + } + response = self.perform_request('put', url_name='dynamic_forms:secciones_pk', url_kwargs={'pk': dict_original['id']}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(Seccion.objects.get(pk=self.seccion1.pk).nombre, 'Nombre modificado') + + subtest_name = 'test_put_secciones_pk_bad_request' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + dict_original = self.seccion1.__dict__.copy() + dict_original.pop('_state', None) + print(dict_original) + data = { + 'nombre': '' + } + response = self.perform_request('put', url_name='dynamic_forms:secciones_pk', url_kwargs={'pk': dict_original['id']}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(Seccion.objects.get(pk=self.seccion1.pk).nombre, 'Sección prueba 1') + + subtest_name = 'test_delete_secciones_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('delete', url_name='dynamic_forms:secciones_pk', url_kwargs={'pk': self.seccion1.pk}, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(Seccion.objects.count(), self.seccion_count-1) + +#TESTS para DynamicFormAPIView +class TestsPermisosDynamicForm(c_tests.PermissionTestCase): + url_name = 'dynamic_forms:dynamic_forms' + + methods_responses = { + 'get': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_200_OK, + 'solicitante': status.HTTP_200_OK, + 'anonymous': status.HTTP_401_UNAUTHORIZED + }, + 'post': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_400_BAD_REQUEST, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + } + } + +class TestsPermisosDynamicFormPK(c_tests.PermissionTestCase): + url_name = 'dynamic_forms:dynamic_forms_pk' + + def get_url_kwargs(self): + self.dynamic_form = DynamicForm.objects.create(nombre='Form prueba') + print(f'LLAVE PRIMARIA: {self.dynamic_form.pk}') + return {'pk': self.dynamic_form.pk} + + methods_responses = { + 'put': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_400_BAD_REQUEST, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + }, + 'delete': { + 'user': status.HTTP_403_FORBIDDEN, + 'admin': status.HTTP_204_NO_CONTENT, + 'solicitante': status.HTTP_403_FORBIDDEN, + 'anonymous': status.HTTP_401_UNAUTHORIZED + } + } + +class DynamicFormTests(SeccionTests): + def reset(self): + super().reset() + DynamicForm.objects.all().delete() + + self.dynamic_form1 = DynamicForm.objects.create(nombre='Form prueba 1') + self.dynamic_form2 = DynamicForm.objects.create(nombre='Form prueba 2') + self.dynamic_form3 = DynamicForm.objects.create(nombre='Form prueba 3') + + self.dynamic_form4 = DynamicForm.objects.create(nombre='Form prueba 4') + self.dynamic_form5 = DynamicForm.objects.create(nombre='Form prueba 5') + self.dynamic_form6 = DynamicForm.objects.create(nombre='Form prueba 6') + self.dynamic_form_count = 6 + + self.dynamic_form2.secciones.set([self.seccion1, self.seccion5]) + self.dynamic_form3.secciones.set([self.seccion3, self.seccion3]) + + self.dynamic_form4.secciones.set([self.seccion1, self.seccion2]) + self.dynamic_form5.secciones.set([self.seccion4, self.seccion4]) + self.dynamic_form6.secciones.set([self.seccion3, self.seccion5]) + + def tests(self): + subtest_name = 'test_get_forms' + with self.subTest(subtest_name): + self.reset() + with CaptureQueriesContext(connection) as ctx: + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:dynamic_forms', token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), self.dynamic_form_count) + print(f'\nRENDIMIENTO QUERYS: {len(ctx.captured_queries)}') + + subtest_name = 'test_get_forms_incorrect_user' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:dynamic_forms', token=self.user_token, user=self.user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertFalse('data' in response.data) + + subtest_name = 'test_get_forms_substring_filter' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:dynamic_forms', query_params={'q': 'prueba 1'}, token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['data']), 1) + + subtest_name = 'test_get_form_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:dynamic_forms_pk', url_kwargs={'pk': self.dynamic_form1.pk}, token=self.solicitante_token, user=self.solicitante_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.data['data']['nombre'], 'Form prueba 1') + + subtest_name = 'test_get_form_pk_not_found' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('get', url_name='dynamic_forms:dynamic_forms_pk', url_kwargs={'pk': 99999999999}, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) + + subtest_name = 'test_post_form' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + data = { + 'nombre': 'Nuevo form', + 'seccion': self.seccion1.pk + } + response = self.perform_request('post', url_name='dynamic_forms:dynamic_forms', data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(DynamicForm.objects.count(), self.dynamic_form_count+1) + + subtest_name = 'test_post_form_bad_request' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + data = {} + response = self.perform_request('post', url_name='dynamic_forms:dynamic_forms', data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(DynamicForm.objects.count(), self.dynamic_form_count) + + subtest_name = 'test_put_form_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + dict_original = self.dynamic_form1.__dict__.copy() + dict_original.pop('_state', None) + print(dict_original) + data = { + 'nombre': 'Form modificado' + } + response = self.perform_request('put', url_name='dynamic_forms:dynamic_forms_pk', url_kwargs={'pk': dict_original['id']}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(DynamicForm.objects.get(pk=self.dynamic_form1.pk).nombre, 'Form modificado') + + subtest_name = 'test_put_form_pk_bad_request' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + dict_original = self.dynamic_form1.__dict__.copy() + dict_original.pop('_state', None) + print(dict_original) + data = { + 'nombre': '' + } + response = self.perform_request('put', url_name='dynamic_forms:dynamic_forms_pk', url_kwargs={'pk': dict_original['id']}, data=data, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(DynamicForm.objects.get(pk=self.dynamic_form1.pk).nombre, 'Form prueba 1') + + subtest_name = 'test_delete_form_pk' + with self.subTest(subtest_name): + self.reset() + print(subtest_name) + response = self.perform_request('delete', url_name='dynamic_forms:dynamic_forms_pk', url_kwargs={'pk': self.dynamic_form1.pk}, token=self.admin_token, user=self.admin_user) + printDict(response.data) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertEqual(DynamicForm.objects.count(), self.dynamic_form_count-1) + -# Create your tests here. diff --git a/cosiap_api/dynamic_forms/urls.py b/cosiap_api/dynamic_forms/urls.py new file mode 100644 index 0000000000000000000000000000000000000000..9c81ba69c9bdb9c2e6d9ff253ebaa626da16af50 --- /dev/null +++ b/cosiap_api/dynamic_forms/urls.py @@ -0,0 +1,32 @@ +from django.urls import path +from dynamic_forms.views import ( + OpcionAPIView, ElementoAPIView, SeccionAPIView, DynamicFormAPIView, RespuestaAPIView, + #DynamicFormsSeccionesAPIView, ElementosOpcionesAPIView, SeccionesElementosAPIView, +) + +app_name = 'dynamic_forms' +urlpatterns = [ + path('opciones/', OpcionAPIView.as_view(), name='opciones'), + path('opciones//', OpcionAPIView.as_view(), name='opciones_pk'), + + #path('elementos-opciones/', ElementosOpcionesAPIView.as_view(), name='elementos-opciones'), + #path('elementos-opciones//', ElementosOpcionesAPIView.as_view(), name='elementos-opciones_pk'), + + path('elementos/', ElementoAPIView.as_view(), name='elementos'), + path('elementos//', ElementoAPIView.as_view(), name='elementos_pk'), + + #path('secciones-elementos/', SeccionesElementosAPIView.as_view(), name='secciones_elementos'), + #path('secciones-elementos//', SeccionesElementosAPIView.as_view(), name='secciones_elementos_pk'), + + path('secciones/', SeccionAPIView.as_view(), name='secciones'), + path('secciones//', SeccionAPIView.as_view(), name='secciones_pk'), + + #path('forms-secciones/', DynamicFormsSeccionesAPIView.as_view(), name='dynamic_forms_secciones'), + #path('forms-secciones//', DynamicFormsSeccionesAPIView.as_view(), name='dynamic-forms-secciones_pk'), + + path('', DynamicFormAPIView.as_view(), name='dynamic_forms'), + path('/', DynamicFormAPIView.as_view(), name='dynamic_forms_pk'), + + path('respuestas/', RespuestaAPIView.as_view(), name='respuestas'), #FormularioRecord + path('respuestas//', RespuestaAPIView.as_view(), name='respuestas_pk'), +] diff --git a/cosiap_api/dynamic_forms/views.py b/cosiap_api/dynamic_forms/views.py index 91ea44a218fbd2f408430959283f0419c921093e..b8188927837eb254e6ab5c5424b4484713940819 100644 --- a/cosiap_api/dynamic_forms/views.py +++ b/cosiap_api/dynamic_forms/views.py @@ -1,3 +1,197 @@ -from django.shortcuts import render +from common.views import BasePermissionAPIView +from rest_framework.response import Response +from rest_framework import status +from notificaciones.mensajes import Mensaje +from django.shortcuts import get_object_or_404 +from dynamic_forms.models import ( + Opcion, Elemento, ElementosOpciones, Seccion, SeccionesElementos, + DynamicForm, DynamicFormsSecciones, Respuesta +) +from .serializers import ( + OpcionSerializer, ElementoSerializer, ElementosOpcionesSerializer, + SeccionSerializer, SeccionesElementosSerializer, DynamicFormSerializer, + DynamicFormsSeccionesSerializer, RespuestaSerializer +) +from rest_framework.permissions import IsAuthenticated +from users.permisos import es_admin, primer_login -# Create your views here. + + +class BaseFormAPIView(BasePermissionAPIView): + model_class = None + serializer_class = None + genero_gramatical = False #False masculino, True Femenino + str_simple = None + str_plural = None + model_queryset = None + + permission_classes_create = [IsAuthenticated, es_admin] + permission_classes_delete = [IsAuthenticated, es_admin] + permission_classes_list = [IsAuthenticated, primer_login] + permission_classes_update = [IsAuthenticated, es_admin] + + def __init__(self): + if not self.model_queryset: + self.model_queryset = self.model_class.objects.all() + if not self.genero_gramatical: + # Masculino + self.articulo_definido = "el" + self.articulo_indefinido = "un" + self.desinencia_singular = "o" + self.desinencia_plural = "os" + else: + # Femenino + self.articulo_definido = "la" + self.articulo_indefinido = "una" + self.desinencia_singular = "a" + self.desinencia_plural = "as" + super().__init__() + + def get(self, request, pk=None, *args, **kwargs): + if pk: + instance = get_object_or_404(self.model_class, pk=pk) + serializer = self.serializer_class(instance) + else: + substring = request.query_params.get('q', None) + if substring: + instances = self.model_queryset.filter(nombre__icontains=substring) + else: + instances = self.model_queryset + serializer = self.serializer_class(instances, many=True) + + response_data = {'data': serializer.data} + return Response(response_data, status=status.HTTP_200_OK) + + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + serializer.save() + response_data = {'data': serializer.data} + Mensaje.success(response_data, f'{self.str_simple.capitalize()} cread{self.desinencia_singular} con éxito.') + return Response(response_data, status=status.HTTP_201_CREATED) + response_data = {'errors': serializer.errors} + Mensaje.warning(response_data, f'No se pudo guardar {self.articulo_definido} {self.str_simple.lower()}.') + Mensaje.error(response_data, serializer.errors) + return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + instance = get_object_or_404(self.model_class, pk=pk) + serializer = self.serializer_class(instance, data=request.data) + if serializer.is_valid(): + serializer.save() + response_data = {'data': serializer.data} + Mensaje.success(response_data, f'{self.str_simple.capitalize()} actualizad{self.desinencia_singular} con éxito.') + return Response(serializer.data, status=status.HTTP_200_OK) + response_data = {'errors': serializer.errors} + Mensaje.warning(response_data, f'No se pudo actualizar {self.articulo_definido} {self.str_simple.lower()}.') + Mensaje.error(response_data, serializer.errors) + return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + instance = get_object_or_404(self.model_class, pk=pk) + instance.delete() + response_data = {} + Mensaje.success(response_data, f'{self.str_simple.capitalize()} eliminad{self.desinencia_singular} con éxito') + return Response(response_data, status=status.HTTP_204_NO_CONTENT) + +# APIView para Opcion +class OpcionAPIView(BaseFormAPIView): + model_class = Opcion + serializer_class = OpcionSerializer + genero_gramatical = True #False masculino, True Femenino + str_simple = 'Opción' + str_plural = 'Opciones' + +# APIView para ElementosOpciones +''' +class ElementosOpcionesAPIView(BaseFormAPIView): + model_class = ElementosOpciones + serializer_class = ElementosOpcionesSerializer + genero_gramatical = True #False masculino, True Femenino + str_simple = 'Relación Elemento Opción' + str_plural = 'Relaciones Elemento Opción' +''' + +# APIView para Elemento +class ElementoAPIView(BaseFormAPIView): + model_class = Elemento + model_queryset = Elemento.objects.all().prefetch_related('elementosopciones_set__opcion') + serializer_class = ElementoSerializer + genero_gramatical = False #False masculino, True Femenino + str_simple = 'Elemento' + str_plural = 'Elementos' + +# APIView para SeccionesElementos +''' +class SeccionesElementosAPIView(BaseFormAPIView): + model_class = SeccionesElementos + serializer_class = SeccionesElementosSerializer + genero_gramatical = True #False masculino, True Femenino + str_simple = 'Relación Sección Elemento' + str_plural = 'Relaciones Sección Elemento' + ''' + +# APIView para Seccion +class SeccionAPIView(BaseFormAPIView): + model_class = Seccion + model_queryset = Seccion.objects.all().prefetch_related('seccioneselementos_set__elemento__elementosopciones_set__opcion') + serializer_class = SeccionSerializer + genero_gramatical = True #False masculino, True Femenino + str_simple = 'Sección' + str_plural = 'Secciones' + +# APIView para DynamicFormsSecciones +''' +class DynamicFormsSeccionesAPIView(BaseFormAPIView): + model_class = DynamicFormsSecciones + serializer_class = DynamicFormsSeccionesSerializer + genero_gramatical = True #False masculino, True Femenino + str_simple = 'Relación Formulario Sección' + str_plural = 'Relaciones Formulario Sección' + ''' + +# APIView para DynamicForm +class DynamicFormAPIView(BaseFormAPIView): + model_class = DynamicForm + model_queryset = DynamicForm.objects.all().prefetch_related('dynamicformssecciones_set__seccion__seccioneselementos_set__elemento__elementosopciones_set__opcion') + serializer_class = DynamicFormSerializer + genero_gramatical = False #False masculino, True Femenino + str_simple = 'Formulario' + str_plural = 'Formularios' + +# APIView para Respuesta +class RespuestaAPIView(BasePermissionAPIView): + permission_classes_create = [IsAuthenticated, primer_login] + permission_classes_delete = [IsAuthenticated, primer_login] + permission_classes_list = [IsAuthenticated, primer_login] + permission_classes_update = [IsAuthenticated, primer_login] + + def get(self, request, pk=None): + if pk: + respuesta = Respuesta.objects.get(pk=pk) + serializer = RespuestaSerializer(respuesta) + else: + respuestas = Respuesta.objects.all() + serializer = RespuestaSerializer(respuestas, many=True) + response_data = {'data': serializer.data} + return Response(response_data, status=status.HTTP_200_OK) + + def post(self, request): + serializer = RespuestaSerializer(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) + + def put(self, request, pk): + respuesta = Respuesta.objects.get(pk=pk) + serializer = RespuestaSerializer(respuesta, data=request.data) + if serializer.is_valid(): + serializer.save() + return Response(serializer.data) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + respuesta = Respuesta.objects.get(pk=pk) + respuesta.delete() + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/cosiap_api/modalidades/migrations/0002_relacion_modalidad_dynamicForm.py b/cosiap_api/modalidades/migrations/0002_relacion_modalidad_dynamicForm.py new file mode 100644 index 0000000000000000000000000000000000000000..2b02d55e67d3b2dbb4c47f5a50aa108f13d9ac5d --- /dev/null +++ b/cosiap_api/modalidades/migrations/0002_relacion_modalidad_dynamicForm.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.7 on 2024-07-29 18:23 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dynamic_forms', '0002_creacion_modelos_formularios_dinamicos_02'), + ('modalidades', '0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes'), + ] + + operations = [ + migrations.AddField( + model_name='modalidad', + name='dynamic_form', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='dynamic_forms.dynamicform', verbose_name='Modalidad'), + ), + ] diff --git a/cosiap_api/modalidades/models.py b/cosiap_api/modalidades/models.py index b36adc01c627a223b6e9ab0b07f7f89f59773553..0f85d785adacabb9a0d83d8ebcef9fd78d132d1f 100644 --- a/cosiap_api/modalidades/models.py +++ b/cosiap_api/modalidades/models.py @@ -28,7 +28,7 @@ class Modalidad(models.Model): descripcion = models.TextField(verbose_name="Descripción", null=False) mostrar = models.BooleanField(default=True) archivado = models.BooleanField(default=False) - dynamic_form = models.ForeignKey(DynamicForm, verbose_name="Dynamic Form", on_delete=models.SET_NULL, null=True) + dynamic_form = models.ForeignKey('dynamic_forms.DynamicForm', on_delete=models.SET_NULL, verbose_name="Modalidad", null=True, blank=False) def __str__(self): return f'{self.nombre}' @@ -38,7 +38,7 @@ class Modalidad(models.Model): @receiver(pre_save, sender=Modalidad) def borrar_imagen_vieja(sender, instance, **kwargs): - #sie el objeto ya existe (es un update), removemos la imagen vieja + #si el objeto ya existe (es un update), removemos la imagen vieja if instance.pk: try: old_instance = Modalidad.objects.get(pk=instance.pk)