diff --git a/cosiap_api/common/nombres_archivos.py b/cosiap_api/common/nombres_archivos.py index 73df4228a1f882fb5933b8256222ea05f78cc004..21e00339c3a365577cd88f6d8360b660feeae750 100644 --- a/cosiap_api/common/nombres_archivos.py +++ b/cosiap_api/common/nombres_archivos.py @@ -10,8 +10,8 @@ def generar_nombre_archivo(nombre_archivo, path, protected=True): ext = nombre_archivo.split('.')[-1] nombre_unico = f"{uuid4().hex}.{ext}" if protected: - os.path.join('protected_uploads/', path) - return os.path.join(settings.MEDIA_ROOT, path, nombre_unico) + path = os.path.join('protected_uploads/', path) + return os.path.join(path, nombre_unico) # Funciones para nombre de archivo específico para cada campo de FileField def nombre_archivo_estado_cuenta(instance, filename): diff --git a/cosiap_api/common/tests.py b/cosiap_api/common/tests.py index 5394338daf7a69211af8baeddf9c42a9a4b1e596..bc31319f6e85d386f7df35c806e138eaa88a7e0e 100644 --- a/cosiap_api/common/tests.py +++ b/cosiap_api/common/tests.py @@ -1,14 +1,122 @@ -from rest_framework.test import APITestCase, APIRequestFactory, force_authenticate +from rest_framework.test import APITestCase, APIRequestFactory, force_authenticate, APIClient from rest_framework_simplejwt.tokens import RefreshToken from users.models import Usuario, Solicitante from rest_framework_simplejwt.tokens import RefreshToken from rest_framework import status from common.views import BasePermissionAPIView from rest_framework.response import Response +from django.urls import reverse from users.permisos import es_admin, primer_login from rest_framework.permissions import AllowAny, IsAuthenticated +class BasePerUserTestCase(APITestCase): + ''' + Clase de APITestCase con configuracion por defecto para hacer tests con los + distintos tipos de Usuario en el sistema + + Atributos: + + - self.user (Usuario comun, sin permisos de administrador) + - self.solicitante_user (Usuario inicializado como Solicitante) + - self.admin_user (Usuario Administrador) + + - self.user_token + - self.admin_token + - self.solicitante_token + + metodos nuevos: + - perform_request() (Funcion de atajo para ejecutar una request y obtener una response) + + ''' + def setUp(self): + self.factory = APIRequestFactory() + + self.user = Usuario.objects.create_user( + curp='testuser', + password='testpassword', + email='usuario1@gmail.com', + nombre='usuario' + ) + + self.admin_user = Usuario.objects.create_superuser( + curp='adminuser', + password='adminpassword', + email='usuarioAdmin@gmail.com', + nombre='Administrador' + ) + + self.solicitante_user = Solicitante.objects.create( + curp='solicitanteuser', + password='solicitantepassword', + email='solicitante@gmail.com', + nombre='Solicitante', + ap_paterno='Marquez', + telefono='0000000001', + RFC='1234567890123', # Ajustar tipo de dato + direccion='Calle sin Nombre', + codigo_postal='89890', # Ajustar tipo de dato + municipio_id=1, + poblacion=5, + INE='awdawd' + ) + + self.user_token = self.get_tokens_for_user(self.user) + self.admin_token = self.get_tokens_for_user(self.admin_user) + self.solicitante_token = self.get_tokens_for_user(self.solicitante_user) + + def get_tokens_for_user(self, user): + refresh = RefreshToken.for_user(user) + return { + 'refresh': str(refresh), + 'access': str(refresh.access_token), + } + + def perform_request(self, method, url_name, url_kwargs=None, token=None, user=None, data=None, is_multipart=False): + """ + Realiza una solicitud a la vista especificada utilizando el método HTTP indicado. + + Parameters: + - method (HTTP method ('get', 'post', 'put', 'patch', 'delete').) + - url_name (Name of the URL to perform the request.) + - url_kwargs (Dictionary of URL keyword arguments.) + - token (Authentication token.) + - user (User making the request.) + - data (Data to be sent with the request (default is None).) + - is_multipart (Boolean indicating if the request is multipart (default is False).) + + Returns: + - response: The response from the request. + """ + client = APIClient() + if token: + client.force_authenticate(user=user, token=token['access']) + + url = reverse(url_name, kwargs=url_kwargs) + method = method.lower() + + if is_multipart: + format_type = 'multipart' + else: + format_type = 'json' + + if method == 'get': + response = client.get(url, data, format=format_type) + elif method == 'post': + response = client.post(url, data, format=format_type) + elif method == 'put': + response = client.put(url, data, format=format_type) + elif method == 'patch': + response = client.patch(url, data, format=format_type) + elif method == 'delete': + response = client.delete(url, data, format=format_type) + else: + raise ValueError(f"Unsupported HTTP method: {method}") + + return response + + + class TestBasePermissionAPIView(BasePermissionAPIView): permission_classes_create = [AllowAny] permission_classes_delete = [es_admin] diff --git a/cosiap_api/modalidades/models.py b/cosiap_api/modalidades/models.py index d43d3fd2e86f3c3d36f7e5f33e24209056b48b5b..f50cdef6fe34942811b18644d90e52bc9baba122 100644 --- a/cosiap_api/modalidades/models.py +++ b/cosiap_api/modalidades/models.py @@ -2,6 +2,9 @@ from django.db import models from common.validadores_campos import validador_pdf from common.nombres_archivos import nombre_archivo_modalidad from django.utils import timezone +import os +from django.db.models.signals import pre_save +from django.dispatch import receiver class Modalidad(models.Model): @@ -24,11 +27,24 @@ class Modalidad(models.Model): dynamic_form = None def __str__(self): - return f'{self.nombre} ({self.tipo})' + return f'{self.nombre}' class Meta: ordering = ['nombre'] +@receiver(pre_save, sender=Modalidad) +def borrar_imagen_vieja(sender, instance, **kwargs): + #sie el objeto ya existe (es un update), removemos la imagen vieja + if instance.pk: + try: + old_instance = Modalidad.objects.get(pk=instance.pk) + if old_instance.imagen and old_instance.imagen != instance.imagen: + if os.path.isfile(old_instance.imagen.path): + 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. @@ -44,14 +60,14 @@ class MontoModalidad(models.Model): 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): + 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(MontoModalidad, self.save(*args, **kwargs)) + super().save(*args, **kwargs) def __str__(self): return f'{self.modalidad.nombre} - {self.monto}' diff --git a/cosiap_api/modalidades/serializers.py b/cosiap_api/modalidades/serializers.py new file mode 100644 index 0000000000000000000000000000000000000000..f0b45a490c0919c2deca41bfe5d98f3c4936d0ef --- /dev/null +++ b/cosiap_api/modalidades/serializers.py @@ -0,0 +1,53 @@ +from rest_framework import serializers +from modalidades.models import Modalidad, MontoModalidad + +class MontoModalidadSerializer(serializers.ModelSerializer): + class Meta: + model = MontoModalidad + fields = ['monto'] + + +class ModalidadSerializer(serializers.ModelSerializer): + monto = serializers.FloatField(write_only=True) + + class Meta: + 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 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') + 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 7ce503c2dd97ba78597f6ff6e4393132753573f6..ec6274994e06a2ee3624aef4bdbf453658860a22 100644 --- a/cosiap_api/modalidades/tests.py +++ b/cosiap_api/modalidades/tests.py @@ -1,3 +1,195 @@ -from django.test import TestCase +from rest_framework import status +from rest_framework.test import APITestCase +from modalidades.models import Modalidad, MontoModalidad +from modalidades.views import ModalidadAPIView +from common.tests import BasePerUserTestCase +from django.core.files.uploadedfile import SimpleUploadedFile +from PIL import Image +import io +import os + +class ModalidadTests(BasePerUserTestCase): + def setUp(self): + super().setUp() # Llama al setup del padre para inicializar usuarios y tokens + + Modalidad.objects.create(nombre='Modalidad Prueba 1', descripcion='Descripción 1', mostrar=True, archivado=False) + Modalidad.objects.create(nombre='Modalidad PRUEBA 2', descripcion='Descripción 2', mostrar=False, archivado=False) + + # Crear una imagen en memoria + image = Image.new('RGB', (100, 100), color=(73, 109, 137)) + byte_array = io.BytesIO() + image.save(byte_array, format='JPEG') + byte_array.seek(0) + + self.test_image = SimpleUploadedFile( + name='test_image.jpg', + content=byte_array.read(), + content_type='image/jpeg' + ) + + image = Image.new('RGB', (100, 100), color=(137, 137, 137)) + byte_array = io.BytesIO() + image.save(byte_array, format='JPEG') + byte_array.seek(0) + + self.test_image_original = SimpleUploadedFile( + name='test_image_original.jpg', + content=byte_array.read(), + content_type='image/jpeg' + ) + def test_get_modalidades_as_anonymous(self): + print("\nRunning test: test_get_modalidades_as_anonymous") + response = self.perform_request('get', url_name='modalidades:modalidades') + print(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_modalidades_as_user(self): + print("\nRunning test: test_get_modalidades_as_user") + response = self.perform_request('get', url_name='modalidades:modalidades', token=self.user_token, user=self.user) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_modalidades_as_admin(self): + print("\nRunning test: test_get_modalidades_as_admin") + response = self.perform_request('get', url_name='modalidades:modalidades', token=self.admin_token, user=self.admin_user) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_modalidades_as_solicitante(self): + print("\nRunning test: test_get_modalidades_as_solicitante") + response = self.perform_request('get', url_name='modalidades:modalidades', token=self.solicitante_token, user=self.solicitante_user) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_post_modalidad_as_admin(self): + print("\nRunning test: test_post_modalidad_as_admin") + data = { + 'nombre': 'Nueva Modalidad', + 'imagen': self.test_image, + 'descripcion': 'Descripción de la nueva modalidad', + 'monto': 100.0 + } + response = self.perform_request('post', url_name='modalidades:modalidades', token=self.admin_token, user=self.admin_user, data=data, is_multipart=True) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Modalidad.objects.count(), 3) + modalidad = Modalidad.objects.get(pk=response.data['data']['id']) + self.assertEqual(modalidad.nombre, data['nombre']) + self.assertEqual(modalidad.descripcion, data['descripcion']) + montoModalidad = MontoModalidad.objects.get(modalidad=modalidad, fecha_fin=None) + print(f'MontoModalidad: {montoModalidad.__dict__}') + self.assertEqual(montoModalidad.monto, data['monto']) + os.remove(modalidad.imagen.path) + + def test_post_modalidad_as_solicitante(self): + print("\nRunning test: test_post_modalidad_as_solicitante") + data = { + 'nombre': 'Nueva Modalidad', + 'imagen': self.test_image, + 'descripcion': 'Descripción de la nueva modalidad', + 'monto': 100.0 + } + response = self.perform_request('post', url_name='modalidades:modalidades', token=self.solicitante_token, user=self.solicitante_user, data=data, is_multipart=True) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(Modalidad.objects.count(), 2) + + def test_put_modalidad_as_admin(self): + print("\nRunning test: test_put_modalidad_as_admin") + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', imagen=self.test_image_original, descripcion='Descripción', mostrar=True, archivado=False) + MontoModalidad.objects.create(modalidad=modalidad, monto=0.5) + montoModalidad = MontoModalidad.objects.get(modalidad = modalidad, fecha_fin=None) + print(f'MontoModalidad: {montoModalidad.__dict__}') + self.assertEqual(montoModalidad.monto, 0.5) + # Verifica que la imagen original existe + original_image_path = modalidad.imagen.path + self.assertTrue(os.path.isfile(original_image_path)) + data = { + 'nombre': 'Modalidad Actualizada', + 'imagen': self.test_image, + 'descripcion': 'Descripción actualizada', + 'monto': 200.0 + } + response = self.perform_request('put', url_name='modalidades:modalidades_pk', url_kwargs={'pk':modalidad.pk}, token=self.admin_token, user=self.admin_user, data=data, is_multipart=True) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + modalidad.refresh_from_db() + self.assertEqual(modalidad.nombre, data['nombre']) + self.assertEqual(modalidad.descripcion, data['descripcion']) + montoModalidad = MontoModalidad.objects.get(modalidad = modalidad, fecha_fin=None) + print(f'MontoModalidad: {montoModalidad.__dict__}') + self.assertEqual(montoModalidad.monto, data['monto']) + self.assertEqual(MontoModalidad.objects.count(), 2) + # Verifica que la imagen original ha sido eliminada + self.assertFalse(os.path.isfile(original_image_path)) + # Verifica que la nueva imagen existe + new_image_path = modalidad.imagen.path + self.assertTrue(os.path.isfile(new_image_path)) + os.remove(new_image_path) + + def test_put_modalidad_as_admin_no_image(self): + print("\nRunning test: test_put_modalidad_as_admin") + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', imagen=self.test_image_original, descripcion='Descripción', mostrar=True, archivado=False) + MontoModalidad.objects.create(modalidad=modalidad, monto=0.5) + montoModalidad = MontoModalidad.objects.get(modalidad = modalidad, fecha_fin=None) + print(f'MontoModalidad: {montoModalidad.__dict__}') + print(f'Modalidad: {modalidad.__dict__}') + self.assertEqual(montoModalidad.monto, 0.5) + # Verifica que la imagen original existe + original_image_path = modalidad.imagen.path + self.assertTrue(os.path.isfile(original_image_path)) + data = { + 'nombre': 'Modalidad Actualizada', + 'descripcion': 'Descripción actualizada', + 'monto': 200.0 + } + response = self.perform_request('put', url_name='modalidades:modalidades_pk', url_kwargs={'pk':modalidad.pk}, token=self.admin_token, user=self.admin_user, data=data, is_multipart=True) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + modalidad.refresh_from_db() + self.assertEqual(modalidad.nombre, data['nombre']) + self.assertEqual(modalidad.descripcion, data['descripcion']) + montoModalidad = MontoModalidad.objects.get(modalidad = modalidad, fecha_fin=None) + print(f'MontoModalidad: {montoModalidad.__dict__}') + self.assertEqual(montoModalidad.monto, data['monto']) + self.assertEqual(MontoModalidad.objects.count(), 2) + # Verifica que la imagen original ha sido eliminada + self.assertTrue(os.path.isfile(original_image_path)) + # Verifica que la nueva imagen existe + new_image_path = modalidad.imagen.path + self.assertTrue(os.path.isfile(new_image_path)) + os.remove(new_image_path) + + def test_put_modalidad_as_solicitante(self): + print("\nRunning test: test_put_modalidad_as_solicitante") + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) + data = { + 'nombre': 'Modalidad Actualizada', + 'imagen': self.test_image, + 'descripcion': 'Descripción actualizada', + 'monto': 200.0 + } + response = self.perform_request('put', url_name='modalidades:modalidades_pk', url_kwargs={'pk':modalidad.pk}, token=self.solicitante_token, user=self.solicitante_user, data=data, is_multipart=True) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + modalidad.refresh_from_db() + self.assertEqual(modalidad.nombre, 'Modalidad Existente') + + def test_delete_modalidad_as_admin(self): + print("\nRunning test: test_delete_modalidad_as_admin") + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) + response = self.perform_request('delete', url_name='modalidades:modalidades_pk', url_kwargs={'pk':modalidad.pk}, token=self.admin_token, user=self.admin_user) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + modalidad.refresh_from_db() + self.assertTrue(modalidad.archivado) + + def test_delete_modalidad_as_solicitante(self): + print("\nRunning test: test_delete_modalidad_as_solicitante") + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) + response = self.perform_request('delete', url_name='modalidades:modalidades_pk', url_kwargs={'pk':modalidad.pk}, token=self.solicitante_token, user=self.solicitante_user) + print(response.data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + modalidad.refresh_from_db() + self.assertFalse(modalidad.archivado) -# Create your tests here. diff --git a/cosiap_api/modalidades/urls.py b/cosiap_api/modalidades/urls.py index b5a1ac34f90ee8a01516f9404f2a57a7201e537f..b391b8ef3b23c11c4bf66ff8de0e48a5fc37f887 100644 --- a/cosiap_api/modalidades/urls.py +++ b/cosiap_api/modalidades/urls.py @@ -1,7 +1,12 @@ from . import views +from modalidades.views import ModalidadAPIView, MontoModalidadAPIView from django.urls import path from django.contrib.auth import views as auth_views app_name = 'modalidades' -urlpatterns = [] \ No newline at end of file +urlpatterns = [ + path('', ModalidadAPIView.as_view(), name='modalidades'), + path('/', ModalidadAPIView.as_view(), name='modalidades_pk'), + path('monto-modalidades/', MontoModalidadAPIView.as_view(), name='monto_modalidad_create'), +] \ No newline at end of file diff --git a/cosiap_api/modalidades/views.py b/cosiap_api/modalidades/views.py index 91ea44a218fbd2f408430959283f0419c921093e..85a830a3e9c98d9b4ea6d6def90f2cc4a9cab02d 100644 --- a/cosiap_api/modalidades/views.py +++ b/cosiap_api/modalidades/views.py @@ -1,3 +1,98 @@ -from django.shortcuts import render +from common.views import BasePermissionAPIView +from rest_framework import response +from rest_framework import status +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 notificaciones.mensajes import Mensaje -# Create your views here. + +class ModalidadAPIView(BasePermissionAPIView): + ''' + Clase que maneja las solicitudes de los recursos de Modalidad + + Tipos de solicitud: + - GET (Obtiene toda la lista de modalidades o una modalidad específica) + - POST (Crea una nueva modalidad) + - PUT (Actualizar los datos de alguna modalidad existente) + - DELETE (Si bien no se permiten eliminar las modalidades, este método la archivará en su lugar) + ''' + + permission_classes_delete = [IsAuthenticated, es_admin] + permission_classes_update = [IsAuthenticated, es_admin] + permission_classes_create = [IsAuthenticated, es_admin] + permission_classes_list = [AllowAny] + + def get(self, request, pk=None): + if pk: + modalidad = Modalidad.objects.get(pk=pk) + serializer = ModalidadSerializer(modalidad) + else: + modalidades = Modalidad.objects.all() + serializer = ModalidadSerializer(modalidades, many=True) + response_data = {'data': serializer.data} + return Response(response_data, status=status.HTTP_200_OK) + + def post(self, request): + # Verificar el tipo de contenido del request + serializer = ModalidadSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + response_data = {'data': serializer.data} + Mensaje.success(response_data, 'Modalidad creada con éxito.') + return Response(response_data, status=status.HTTP_201_CREATED) + else: + response_data = {'errors': serializer.errors} + Mensaje.warning(response_data, 'No se pudo crear la modalidad.') + Mensaje.error(response_data, serializer.errors) + return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + + def put(self, request, pk): + modalidad = Modalidad.objects.get(pk=pk) + serializer = ModalidadSerializer(modalidad, data=request.data, partial=True) + if serializer.is_valid(): + serializer.save() + response_data = {'data': serializer.data} + Mensaje.success(response_data, 'Modalidad modificada con éxito.') + return Response(response_data, status=status.HTTP_200_OK) + else: + response_data = {'errors': serializer.errors} + Mensaje.warning(response_data, 'No se pudo modificar la modalidad.') + Mensaje.error(response_data, serializer.errors) + return Response(response_data, status=status.HTTP_400_BAD_REQUEST) + + def delete(self, request, pk): + modalidad = Modalidad.objects.get(pk=pk) + modalidad.archivado = True + 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) diff --git a/cosiap_api/notificaciones/mensajes.py b/cosiap_api/notificaciones/mensajes.py index 4907bf36fc01b05b6dc2b8467b81e5e01678f509..d18652ba1352957727f6c30230d83182e2bb2622 100644 --- a/cosiap_api/notificaciones/mensajes.py +++ b/cosiap_api/notificaciones/mensajes.py @@ -16,18 +16,32 @@ class Mensaje: Añade un mensaje al diccionario de respuesta bajo el tipo especificado. Parámetros: - - response_data (dict): El diccionario de respuesta donde se añadirán los mensajes. + - data (dict): El diccionario de respuesta donde se añadirán los mensajes. - tag (str): El tipo de mensaje ('success', 'warning', 'error', 'info'). - - message (str): El mensaje a añadir. + - message (str, list, dict): El mensaje a añadir, que puede ser una cadena, lista o diccionario. """ if not isinstance(data, dict): raise TypeError("El argumento 'data' debe ser un diccionario.") - + if 'messages' not in data: data['messages'] = {} if tag not in data['messages']: data['messages'][tag] = [] - data['messages'][tag].append(message) + + def add_recursive(msg, prefix=""): + if isinstance(msg, str): + data['messages'][tag].append(prefix + msg) + elif isinstance(msg, list): + for item in msg: + add_recursive(item, prefix) + elif isinstance(msg, dict): + for key, value in msg.items(): + new_prefix = f"{prefix}{key}: " if prefix else f"{key}: " + add_recursive(value, new_prefix) + else: + raise TypeError("El argumento 'message' debe ser una cadena, lista o diccionario.") + + add_recursive(message) @staticmethod def success(response_data, message=''): diff --git a/cosiap_api/notificaciones/notificaciones.py b/cosiap_api/notificaciones/notificaciones.py new file mode 100644 index 0000000000000000000000000000000000000000..c6a1f90c3d9ff7428b88445c89383d8da7bc7bc6 --- /dev/null +++ b/cosiap_api/notificaciones/notificaciones.py @@ -0,0 +1,29 @@ +from .models import Notificacion +from django.urls import reverse + +# importar como: from mensajes import notificaciones as notif + +""" + Ejemplos: + solicitante = get_object_or_404(Solicitante, pk=request.user.id) + notif.nueva(solicitante, 'Mi mensaje', 'usuarios:perfil') + + notif.nueva(solicitante, titulo='MI TITULO', mensaje='Mi mensaje', url='solicitudes:convocatoria', urlArgs=[id]) +""" + +def nueva(solicitante, mensaje, url=None, urlArgs=None, titulo=Notificacion.TITULO_DEFAULT): + ''' + Funcion que sirve para crear una nueva notificacion facilmente desde una vista. + + Argumentos: + - *usuario*: Usuario al que va dirigida la notificación. + - *mensaje*: Mensaje de la notificación. + - *url* : Nombre de la URL asociada a la notificación. Puede estar en blanco o ser nulo. + - *urlArgs* : Argumentos de la URL en formato JSON. Puede estar en blanco o ser nulo. + - *titulo* : Título de la notificación. Puede estar en blanco o ser nulo. + + Retorna: + - Instancia de Notificacion creada. + ''' + return Notificacion.objects.create(solicitante=solicitante, titulo=titulo, mensaje=mensaje, urlName=url, urlArgs=urlArgs) + diff --git a/cosiap_api/notificaciones/tests.py b/cosiap_api/notificaciones/tests.py index 622e1ded6bc04ababf302d93211cd4bf03479454..bd82b6363d7901655985fa303bacb616ef1393d3 100644 --- a/cosiap_api/notificaciones/tests.py +++ b/cosiap_api/notificaciones/tests.py @@ -26,7 +26,7 @@ class MensajeTestCase(APITestCase): self.admin_token = self.get_tokens_for_user(self.admin_user) # URL para la vista usuario - self.url = reverse('users:usuario-lista-crear') + self.url = reverse('users:usuario_list_create') def get_tokens_for_user(self, user): refresh = RefreshToken.for_user(user) diff --git a/cosiap_api/users/permisos.py b/cosiap_api/users/permisos.py index b49e885314eb442eec18e1696504d2bbee5c1b0f..f606229c7427d7c3530db91c95a23f45f4b19f95 100644 --- a/cosiap_api/users/permisos.py +++ b/cosiap_api/users/permisos.py @@ -3,6 +3,7 @@ from .models import Solicitante # Funcionalidad para verificar que el usuario que realiza la eliminación sea un admin class es_admin(permissions.BasePermission): + message = 'El usuario no tiene los permisos para esta acción.' # método para determinar que un usuario tiene permisos def has_permission( self, request, view): return request.user and request.user.is_superuser diff --git a/cosiap_api/users/views.py b/cosiap_api/users/views.py index dd3ca08bf36a47ee72dd0c6a91a2b935c0527b93..abb1b72285d356a49a11a313aaadde97bda63e73 100644 --- a/cosiap_api/users/views.py +++ b/cosiap_api/users/views.py @@ -24,7 +24,7 @@ from rest_framework import status from django.conf import settings from datetime import datetime, timedelta from .permisos import es_admin, primer_login -from notificaciones.Mensajes import Mensaje +from notificaciones.mensajes import Mensaje from common.views import BasePermissionAPIView class CustomTokenObtainPairView(TokenObtainPairView): @@ -301,7 +301,7 @@ class NuevaPassword(APIView): # extraemos al usuario de la base de datos usuario = Usuario.objects.get(pk=uid) # en caso de que el usuario no se encuentre - except (TypeError, ValueError, OverflowError, User.DoesNotExist): + except (TypeError, ValueError, OverflowError, Usuario.DoesNotExist): # se setea a None usuario = None # verificamos que el usuario exista y que el token enviado pertenezca a el