diff --git a/cosiap_api/dynamic_forms/models.py b/cosiap_api/dynamic_forms/models.py index 4a7fa20bc07ef466dc525f78faecf58a8e7fcd07..82b9643bfeb568f24caaf85dc85242d8597eb0a7 100644 --- a/cosiap_api/dynamic_forms/models.py +++ b/cosiap_api/dynamic_forms/models.py @@ -182,17 +182,42 @@ class Respuesta(models.Model): verbose_name_plural = '09. Respuestas' unique_together = ('registro_seccion', 'elemento') - ''' - def __str__(self): - return f"Respuesta {type(self)} - Elemento: {self.elemento} - Solicitante: {self.solicitud.solicitante_id}" - - def save(self, *args, **kwargs): - if self._state.adding: - if self.elemento.seccioneselementos_set.filter(seccion__tipo='unico').exists(): - 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) - ''' + @classmethod + def create_respuesta(cls, registro_seccion, elemento, valor=None, otro=None): + """ + Crea una nueva respuesta basada en el tipo de elemento. + """ + respuesta_class = cls.RESPUESTA_TYPES.get(elemento.tipo) + if not respuesta_class: + raise ValidationError(f"No se puede encontrar un tipo de respuesta para el tipo de elemento {elemento.tipo}") + + respuesta = respuesta_class( + registro_seccion=registro_seccion, + elemento=elemento, + valor=valor, + otro=otro + ) + + respuesta.clean() + respuesta.save() + return respuesta + + def update_respuesta(self, valor=None, otro=None): + """ + Actualiza una respuesta existente con los nuevos valores proporcionados. + """ + self.valor = valor + self.otro = otro + + self.clean() + self.save() + return self + + def clean(self): + """ + Método `clean` abstracto para ser sobrescrito en las subclases. + """ + pass def getStringValue(self): return 'Respuesta no Implementado' diff --git a/cosiap_api/dynamic_tables/DynamicTableDynamicForm.py b/cosiap_api/dynamic_tables/DynamicTableDynamicForm.py index 0edff3f4baec796a220968f3649510b4983b0c33..7007f279697d30ec4d26fff9dddec7b3dc272298 100644 --- a/cosiap_api/dynamic_tables/DynamicTableDynamicForm.py +++ b/cosiap_api/dynamic_tables/DynamicTableDynamicForm.py @@ -1,8 +1,8 @@ from .DynamicTable import DynamicTable from dynamic_forms.models import DynamicForm from solicitudes.models import Solicitud -from dynamic_forms.models import Respuesta, RCasillas, RDesplegable, RDocumento, RFecha, RHora, RNumerico, ROpcionMultiple, RTextoCorto, RTextoParrafo -from dynamic_forms.serializers import OpcionSerializer, ElementosOpcionesSerializer, ElementoSerializer, SeccionesElementosSerializer, SeccionSerializer, DynamicFormsSeccionesSerializer, DynamicFormSerializer, RespuestaFormularioSerializer +from dynamic_forms.models import Respuesta +from dynamic_forms.serializers import RespuestaFormularioSerializer class DynamicTableDynamicForm(DynamicTable): @@ -24,10 +24,4 @@ class DynamicTableDynamicForm(DynamicTable): solicitud['formulario'] = form_data - return solicitud - - - - - - \ No newline at end of file + return solicitud \ No newline at end of file diff --git a/cosiap_api/modalidades/admin.py b/cosiap_api/modalidades/admin.py index d967b01301cda67d5074e206fea6ee47d0a608a3..04c2ad2653eb8280047ac1c2dbc28a12956f359e 100644 --- a/cosiap_api/modalidades/admin.py +++ b/cosiap_api/modalidades/admin.py @@ -1,6 +1,5 @@ from django.contrib import admin -from .models import Modalidad, MontoModalidad +from .models import Modalidad # Register your models here. -admin.site.register(Modalidad) -admin.site.register(MontoModalidad) \ No newline at end of file +admin.site.register(Modalidad) \ No newline at end of file diff --git a/cosiap_api/modalidades/migrations/0002_delete_montomodalidad_model.py b/cosiap_api/modalidades/migrations/0002_delete_montomodalidad_model.py new file mode 100644 index 0000000000000000000000000000000000000000..be1d04af6bb443174521db0f87b4354a7e4ec445 --- /dev/null +++ b/cosiap_api/modalidades/migrations/0002_delete_montomodalidad_model.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.7 on 2024-08-27 17:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('modalidades', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='modalidad', + name='monto_maximo', + field=models.FloatField(default=0.0, verbose_name='Monto'), + ), + migrations.DeleteModel( + name='MontoModalidad', + ), + ] diff --git a/cosiap_api/modalidades/models.py b/cosiap_api/modalidades/models.py index 9d10bd7eb055c2d181db6abd59dfb8cc8e09b06a..0c774c7d5b6b72bc090fa850afe3feb10ad80133 100644 --- a/cosiap_api/modalidades/models.py +++ b/cosiap_api/modalidades/models.py @@ -25,7 +25,8 @@ class Modalidad(models.Model): ''' nombre = models.CharField(max_length=255, verbose_name="Nombre", null=False) imagen = models.ImageField(upload_to=nombre_archivo_modalidad, verbose_name="Imagen", null=False) - descripcion = models.TextField(verbose_name="Descripción", null=False) + descripcion = models.TextField(verbose_name="Descripción", null=False) + monto_maximo = models.FloatField(default=0.0, verbose_name="Monto") mostrar = models.BooleanField(default=True) archivado = models.BooleanField(default=False) dynamic_form = models.ForeignKey('dynamic_forms.DynamicForm', on_delete=models.SET_NULL, verbose_name="Formulario", null=True, blank=False) @@ -47,36 +48,3 @@ def borrar_imagen_vieja(sender, instance, **kwargs): os.remove(old_instance.imagen.path) except Modalidad.DoesNotExist: pass - - -class MontoModalidad(models.Model): - ''' - Modelo que contiene los valores de los montos de las modalidades a través del tiempo. - - Campos: - - modalidad: Instancia de Modalidad a la que pertenece el monto. - - monto: Número flotante que indica el valor del monto de esa modalidad en ese periodo. Por defecto es 0.0. - - fecha_inicio: Fecha en la que comenzó a estar vigente el monto. Campo autogenerado. - - fecha_fin: Fecha en la que dejó de estar vigente el monto. Por defecto es None. Campo autogenerado: si existe algún MontoModalidad cuya fecha de fin esté sin definir, al momento de crearse un nuevo MontoModalidad para la misma Modalidad, el campo indefinido de ese MontoModalidad se define a la fecha actual. - ''' - modalidad = models.ForeignKey(Modalidad, on_delete=models.CASCADE, verbose_name="Modalidad") - monto = models.FloatField(default=0.0, verbose_name="Monto") - fecha_inicio = models.DateTimeField(auto_now_add=True, verbose_name="Fecha de Inicio") - fecha_fin = models.DateTimeField(null=True, blank=True, verbose_name="Fecha de Fin") - - def save(self, *args, **kwargs): - # Autogenerar la fecha de fin del MontoModalidad anterior - if not self.pk: # Solo aplica para nuevas instancias - ultimo_monto = MontoModalidad.objects.filter(modalidad=self.modalidad, fecha_fin__isnull=True).last() - if ultimo_monto: - ultimo_monto.fecha_fin = timezone.now() - ultimo_monto.save() - super().save(*args, **kwargs) - - def __str__(self): - return f'{self.modalidad.nombre} - {self.monto}' - - class Meta: - verbose_name = "Monto de Modalidad" - verbose_name_plural = "Montos de Modalidades" - ordering = ['modalidad', '-fecha_inicio'] \ No newline at end of file diff --git a/cosiap_api/modalidades/serializers.py b/cosiap_api/modalidades/serializers.py index 98d2b7fc0ed008fbf3088ca4bf507bcd475181c0..21428b76dc8eb1acaf310baeaef1a2dec0e6432e 100644 --- a/cosiap_api/modalidades/serializers.py +++ b/cosiap_api/modalidades/serializers.py @@ -1,11 +1,5 @@ from rest_framework import serializers -from modalidades.models import Modalidad, MontoModalidad - -class MontoModalidadSerializer(serializers.ModelSerializer): - class Meta: - model = MontoModalidad - fields = ['monto'] - +from modalidades.models import Modalidad class ModalidadSerializer(serializers.ModelSerializer): monto = serializers.FloatField(write_only=True) @@ -14,45 +8,12 @@ class ModalidadSerializer(serializers.ModelSerializer): model = Modalidad fields = '__all__' - ''' - def to_representation(self, instance): - representation = super().to_representation(instance) - user = self.context['request'].user - - # Si el usuario no es administrador, retiramos los campos de monto - if not user.is_staff : - representation.pop('monto', None) - - return representation ''' - - def to_representation(self, instance): - representation = super().to_representation(instance) - representation['monto'] = self.get_monto(instance) - return representation - - - - def get_monto(self, obj): - ultimo_monto = MontoModalidad.objects.filter(modalidad=obj).order_by('-fecha_inicio').first() - if ultimo_monto: - return MontoModalidadSerializer(ultimo_monto).data - return None - def create(self, validated_data): - monto = validated_data.pop('monto', None) modalidad = Modalidad.objects.create(**validated_data) - MontoModalidad.objects.create(modalidad=modalidad, monto=monto) return modalidad def update(self, instance, validated_data): - monto = validated_data.pop('monto', None) modalidad = super().update(instance, validated_data) - - if monto is not None: - ultimo_monto = MontoModalidad.objects.filter(modalidad=modalidad).order_by('-fecha_inicio').first() - if not ultimo_monto or ultimo_monto.monto != monto: - MontoModalidad.objects.create(modalidad=modalidad, monto=monto) - return modalidad diff --git a/cosiap_api/modalidades/tests.py b/cosiap_api/modalidades/tests.py index bd11a3865a6550919fe4fb6b35b877bdd4e2a9d9..8c6f42dd3390971e56f52e16ff9ac95e6121be38 100644 --- a/cosiap_api/modalidades/tests.py +++ b/cosiap_api/modalidades/tests.py @@ -1,6 +1,6 @@ from rest_framework import status from rest_framework.test import APITestCase -from modalidades.models import Modalidad, MontoModalidad +from modalidades.models import Modalidad from modalidades.views import ModalidadAPIView from common.custom_tests import BasePerUserTestCase from common.utils import print_dict diff --git a/cosiap_api/modalidades/urls.py b/cosiap_api/modalidades/urls.py index b391b8ef3b23c11c4bf66ff8de0e48a5fc37f887..a76e7737e8bc490127358f31e0bc4472799f113d 100644 --- a/cosiap_api/modalidades/urls.py +++ b/cosiap_api/modalidades/urls.py @@ -1,5 +1,5 @@ from . import views -from modalidades.views import ModalidadAPIView, MontoModalidadAPIView +from modalidades.views import ModalidadAPIView from django.urls import path from django.contrib.auth import views as auth_views @@ -7,6 +7,5 @@ from django.contrib.auth import views as auth_views app_name = 'modalidades' urlpatterns = [ path('', ModalidadAPIView.as_view(), name='modalidades'), - path('/', ModalidadAPIView.as_view(), name='modalidades_pk'), - path('monto-modalidades/', MontoModalidadAPIView.as_view(), name='monto_modalidad_create'), + path('/', ModalidadAPIView.as_view(), name='modalidades_pk'), ] \ No newline at end of file diff --git a/cosiap_api/modalidades/views.py b/cosiap_api/modalidades/views.py index f9a9a552597c19d2e2db53fcf8171b0ccbee5e66..bbc4c0a4f3f50bdfb52a60b4a6037b95cc9c6f76 100644 --- a/cosiap_api/modalidades/views.py +++ b/cosiap_api/modalidades/views.py @@ -5,8 +5,8 @@ from rest_framework.permissions import IsAuthenticated, IsAdminUser, AllowAny from users.permisos import es_admin from rest_framework.views import APIView from rest_framework.response import Response -from modalidades.models import Modalidad, MontoModalidad -from modalidades.serializers import ModalidadSerializer, MontoModalidadSerializer +from modalidades.models import Modalidad +from modalidades.serializers import ModalidadSerializer from notificaciones.mensajes import Mensaje from django.shortcuts import get_object_or_404 @@ -76,29 +76,4 @@ class ModalidadAPIView(BasePermissionAPIView): modalidad.save() response_data = {} Mensaje.success(response_data, 'Modalidad archivada con éxito.') - return Response(response_data, status=status.HTTP_204_NO_CONTENT) - - -class MontoModalidadAPIView(APIView): - ''' - Clase que maneja las solicitudes de los recursos de MontoModalidad - - Tipos de solicitud: - - POST (Crea un nuevo monto para una modalidad específica) - - PUT (Actualiza el monto de una modalidad específica) - ''' - - permission_classes = [IsAuthenticated, IsAdminUser] - - def post(self, request): - serializer = MontoModalidadSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() - response_data = {'data': serializer.data} - Mensaje.success(response_data, f'Nuevo monto para la modalidad {serializer.instance.modalidad.nombre} actualizado.') - return Response(response_data, status=status.HTTP_201_CREATED) - else: - response_data = {'errors': serializer.errors} - Mensaje.warning(response_data, 'No se pudo guardar el nuevo monto de la modalidad') - Mensaje.error(response_data, serializer.errors) - return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + return Response(response_data, status=status.HTTP_204_NO_CONTENT) \ No newline at end of file diff --git a/cosiap_api/users/serializers.py b/cosiap_api/users/serializers.py index 67dbc99415057cf099223d660a1a5bb0e24b85ea..7fbbef75585332f3a821420a3e5fb7df96bfafac 100644 --- a/cosiap_api/users/serializers.py +++ b/cosiap_api/users/serializers.py @@ -93,9 +93,9 @@ class SolicitanteSerializer(serializers.ModelSerializer): # indicamos los campos que debe ingresar el usuario fields = ['ap_paterno', 'ap_materno', 'telefono', 'RFC', 'direccion', 'codigo_postal', 'municipio', 'poblacion', 'INE'] # Agregamos validadores para asegurar que los campos requeridos no estén vacíos - extra_kwargs = {'ap_paterno': {'required': True}, 'ap_materno': {'required': False},'telefono': {'required': True},'RFC': {'required': True}, - 'direccion': {'required': True},'codigo_postal': {'required': True},'municipio': {'required': True},'poblacion': {'required': True}, - 'INE': {'required': True} + extra_kwargs = {'ap_paterno': {'required': False}, 'ap_materno': {'required': False},'telefono': {'required': False},'RFC': {'required': False}, + 'direccion': {'required': False},'codigo_postal': {'required': False},'municipio': {'required': False},'poblacion': {'required': False}, + 'INE': {'required': False} } # Definimos una función para crear al Solicitante diff --git a/cosiap_api/users/urls.py b/cosiap_api/users/urls.py index db0e4aa4f07eb9fe3eeb986c9cd0317dc073b752..c2c03ae04bf8476b31637938be3231909705088a 100644 --- a/cosiap_api/users/urls.py +++ b/cosiap_api/users/urls.py @@ -11,6 +11,8 @@ urlpatterns = [ path('token/refresh/', CustomTokenRefreshView.as_view(), name='token_refresh'), path('uid/', views.UserID.as_view(), name='user_id'), path('user-is-admin/', views.UserIsStaff.as_view(), name='user_staff'), + path('user-complete-data/', views.SolicitanteDatosCompletos.as_view(), name='user_complete_data'), + path('logout/', views.LogoutAPIView.as_view(), name='logout'), path('', views.UsuarioAPIView.as_view(), name = 'usuarios'), path('/', views.UsuarioAPIView.as_view(), name = 'usuarios_pk'), diff --git a/cosiap_api/users/views.py b/cosiap_api/users/views.py index 1abba574a6e76020b7f772ff7c6415d4f056326d..ea93c6296eea46d80450ac945b3803e447d7d817 100644 --- a/cosiap_api/users/views.py +++ b/cosiap_api/users/views.py @@ -60,24 +60,38 @@ class CustomTokenRefreshView(TokenRefreshView): return response +class LogoutAPIView(APIView): + ''' APIView que maneja el cierre de sesión ''' + permission_classes = [AllowAny] + + def get(self, request, *args, **kwargs): + data = {} + Mensaje.success(data, 'Logout exitoso.') + response = Response(data, status=status.HTTP_200_OK) + # Eliminar las cookies de access_token y refresh_token + response.delete_cookie('access_token') + response.delete_cookie('refresh_token') + return response + + + class UserID(APIView): ''' Clase para recuperar el ID del usuario logeado actual ''' permission_classes = [IsAuthenticated] - data = {} def get(self, request, *args, **kwargs): - + data = {} try: user = request.user uid = user.id - self.data['user_id'] = uid - return Response(self.data, status= status.HTTP_200_OK) + data['user_id'] = uid + return Response(data, status= status.HTTP_200_OK) except Exception as e: - Mensaje.error(self.data, str(e)) - return Response(self.data, status=status.HTTP_400_BAD_REQUEST) + Mensaje.error(data, str(e)) + return Response(data, status=status.HTTP_400_BAD_REQUEST) class UserIsStaff(APIView): @@ -86,20 +100,36 @@ class UserIsStaff(APIView): ''' permission_classes = [IsAuthenticated] - data = {} def get(self, request, *args, **kwargs): - + data = {} try: user = request.user user_is_admin = user.is_staff - self.data['user_is_admin'] = user_is_admin - return Response(self.data, status= status.HTTP_200_OK) + data['user_is_admin'] = user_is_admin + return Response(data, status= status.HTTP_200_OK) except Exception as e: - Mensaje.error(self.data, str(e)) - return Response(self.data, status=status.HTTP_400_BAD_REQUEST) + Mensaje.error(data, str(e)) + return Response(data, status=status.HTTP_400_BAD_REQUEST) + + +class SolicitanteDatosCompletos(APIView): + ''' + APIView para conocer si el solicitante tiene sus datos completos + ''' - + permission_classes = [IsAuthenticated] + + def get(self, request, *args, **kwargs): + data = {} + try: + user = request.user + solicitante = Solicitante.objects.get(id=user.id) + data['solicitante_datos_completos'] = solicitante.datos_completos + return Response(data, status = status.HTTP_200_OK) + except Exception as e: + Mensaje.error(data, str(e)) + return Response(data, status=status.HTTP_400_BAD_REQUEST) class UsuarioAPIView(DynamicTableAPIView): @@ -164,8 +194,8 @@ class SolicitanteAPIView(DynamicTableAPIView): """ permission_classes_list = [IsAuthenticated, es_admin] permission_classes_create = [IsAuthenticated] - permission_classes_update = [IsAuthenticated, primer_login] - permission_classes_delete = [IsAuthenticated, primer_login] + permission_classes_update = [IsAuthenticated] + permission_classes_delete = [IsAuthenticated] model_class = Solicitante model_name = 'Solicitante' @@ -190,11 +220,8 @@ class SolicitanteAPIView(DynamicTableAPIView): serializer = SolicitanteSerializer(instance= solicitante, data=request.data) if serializer.is_valid(): serializer.save() - if solicitante.datos_completos: - Mensaje.success(response_data, 'Acceso permitido.') - return Response(response_data, status=status.HTTP_200_OK) - Mensaje.error(response_data, 'Favor de completar sus datos.') - return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + Mensaje.success(response_data, 'Datos guardados.') + return Response(response_data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/cosiap_frontend/src/App.jsx b/cosiap_frontend/src/App.jsx index feef957305f3729d61a38362d2e199c61c00a1e0..c35512a59d27fbf65b4a4b189bc017f2ace66a51 100644 --- a/cosiap_frontend/src/App.jsx +++ b/cosiap_frontend/src/App.jsx @@ -7,7 +7,7 @@ import PageLoader from '@/components/common/ui/PageLoader'; import { Login } from './components/users/Login/Login'; import Register from './components/users/Register/Register'; import ResetPassword from './components/users/Password/ResetPassword'; -import Inicio from "./components/users/Inicio"; +import Inicio from "./components/Inicio"; import { useState } from 'react'; import LayoutBaseAuthenticator from './components/common/layouts/LayoutBaseAuthenticator';