From eb53322882d60bdb3bea804a19430d7d51043503 Mon Sep 17 00:00:00 2001 From: RafaUC Date: Wed, 17 Jul 2024 15:00:46 -0600 Subject: [PATCH 1/2] creacion de apiview modalidad y tests --- cosiap_api/common/tests.py | 83 +++++++++++++++++++++++ cosiap_api/modalidades/serializers.py | 42 ++++++++++++ cosiap_api/modalidades/tests.py | 98 ++++++++++++++++++++++++++- cosiap_api/modalidades/views.py | 95 +++++++++++++++++++++++++- cosiap_api/notificaciones/mensajes.py | 22 ++++-- cosiap_api/users/views.py | 4 +- 6 files changed, 334 insertions(+), 10 deletions(-) create mode 100644 cosiap_api/modalidades/serializers.py diff --git a/cosiap_api/common/tests.py b/cosiap_api/common/tests.py index 5394338..8bc432f 100644 --- a/cosiap_api/common/tests.py +++ b/cosiap_api/common/tests.py @@ -9,6 +9,89 @@ from rest_framework.response import Response 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, view, method, token=None, user=None, data=None): + """ + Realiza una solicitud a la vista especificada utilizando el método HTTP indicado. + + Parámetros: + - view (View): (La vista de Django Rest Framework a la que se realizará la solicitud.) + - method (str): (El método HTTP a utilizar para la solicitud ('get', 'post', 'put', 'patch', 'delete', etc.).) + - token (dict, opcional): (Un diccionario con los tokens de autenticación ('refresh' y 'access'). Si se proporciona, se usará para autenticar la solicitud.) + - user (User, opcional): (El usuario al que pertenece el token. Necesario si se proporciona un token para la autenticación.) + + Retorna: + - Response: La respuesta de la vista a la solicitud realizada. + """ + request = getattr(self.factory, method)('/', data=data) + if token: + force_authenticate(request, user=user, token=token['access']) + response = view(request) + return response + + + class TestBasePermissionAPIView(BasePermissionAPIView): permission_classes_create = [AllowAny] permission_classes_delete = [es_admin] diff --git a/cosiap_api/modalidades/serializers.py b/cosiap_api/modalidades/serializers.py new file mode 100644 index 0000000..58f287a --- /dev/null +++ b/cosiap_api/modalidades/serializers.py @@ -0,0 +1,42 @@ +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 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 ultimo_monto and 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 7ce503c..5b9e2f3 100644 --- a/cosiap_api/modalidades/tests.py +++ b/cosiap_api/modalidades/tests.py @@ -1,3 +1,97 @@ -from django.test import TestCase +from rest_framework import status +from modalidades.models import Modalidad +from modalidades.views import ModalidadAPIView +from common.tests import BasePerUserTestCase -# Create your tests here. +class ModalidadTests(BasePerUserTestCase): + def test_get_modalidades_as_anonymous(self): + view = ModalidadAPIView.as_view() + response = self.perform_request(view, 'get') + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_get_modalidades_as_user(self): + view = ModalidadAPIView.as_view() + response = self.perform_request(view, 'get', token=self.user_token, user=self.user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_modalidades_as_admin(self): + view = ModalidadAPIView.as_view() + response = self.perform_request(view, 'get', token=self.admin_token, user=self.admin_user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_get_modalidades_as_solicitante(self): + view = ModalidadAPIView.as_view() + response = self.perform_request(view, 'get', token=self.solicitante_token, user=self.solicitante_user) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_post_modalidad_as_admin(self): + view = ModalidadAPIView.as_view() + data = { + 'nombre': 'Nueva Modalidad', + 'descripcion': 'Descripción de la nueva modalidad', + 'monto': 100.0 + } + response = self.perform_request(view, 'post', token=self.admin_token, user=self.admin_user, data=data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Modalidad.objects.count(), 1) + + def test_post_modalidad_as_solicitante(self): + view = ModalidadAPIView.as_view() + data = { + 'nombre': 'Nueva Modalidad', + 'descripcion': 'Descripción de la nueva modalidad', + 'monto': 100.0 + } + response = self.perform_request(view, 'post', token=self.solicitante_token, user=self.solicitante_user, data=data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual(Modalidad.objects.count(), 0) + + def test_put_modalidad_as_admin(self): + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) + view = ModalidadAPIView.as_view() + data = { + 'pk': modalidad.pk, + 'nombre': 'Modalidad Actualizada', + 'descripcion': 'Descripción actualizada', + 'monto': 200.0 + } + response = self.perform_request(view, 'put', token=self.admin_token, user=self.admin_user, data=data) + self.assertEqual(response.status_code, status.HTTP_200_OK) + modalidad.refresh_from_db() + self.assertEqual(modalidad.nombre, 'Modalidad Actualizada') + + def test_put_modalidad_as_solicitante(self): + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) + view = ModalidadAPIView.as_view() + data = { + 'pk': modalidad.pk, + 'nombre': 'Modalidad Actualizada', + 'descripcion': 'Descripción actualizada', + 'monto': 200.0 + } + response = self.perform_request(view, 'put', token=self.solicitante_token, user=self.solicitante_user, data=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): + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) + view = ModalidadAPIView.as_view() + data = { + 'pk': modalidad.pk, + } + response = self.perform_request(view, 'delete', token=self.admin_token, user=self.admin_user, data=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): + modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) + view = ModalidadAPIView.as_view() + data = { + 'pk': modalidad.pk, + } + response = self.perform_request(view, 'delete', token=self.solicitante_token, user=self.solicitante_user, data=data) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + modalidad.refresh_from_db() + self.assertFalse(modalidad.archivado) diff --git a/cosiap_api/modalidades/views.py b/cosiap_api/modalidades/views.py index 91ea44a..0971ec8 100644 --- a/cosiap_api/modalidades/views.py +++ b/cosiap_api/modalidades/views.py @@ -1,3 +1,94 @@ -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 +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, IsAdminUser] + permission_classes_update = [IsAuthenticated, IsAdminUser] + + 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): + 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 4907bf3..d18652b 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/users/views.py b/cosiap_api/users/views.py index dd3ca08..abb1b72 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 -- GitLab From 23815ebb954c9c79ac627cc83e0bdb3042bc88fa Mon Sep 17 00:00:00 2001 From: RafaUC Date: Fri, 19 Jul 2024 13:58:53 -0600 Subject: [PATCH 2/2] implementada funcionalidad, endpoint y permisos para la funcionalidad de modalidades y MontosModalidad --- cosiap_api/common/nombres_archivos.py | 4 +- cosiap_api/common/tests.py | 55 ++++-- cosiap_api/modalidades/models.py | 22 ++- cosiap_api/modalidades/serializers.py | 13 +- cosiap_api/modalidades/tests.py | 178 +++++++++++++++----- cosiap_api/modalidades/urls.py | 7 +- cosiap_api/modalidades/views.py | 20 ++- cosiap_api/notificaciones/notificaciones.py | 29 ++++ cosiap_api/notificaciones/tests.py | 2 +- cosiap_api/users/permisos.py | 1 + 10 files changed, 260 insertions(+), 71 deletions(-) create mode 100644 cosiap_api/notificaciones/notificaciones.py diff --git a/cosiap_api/common/nombres_archivos.py b/cosiap_api/common/nombres_archivos.py index 73df422..21e0033 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 8bc432f..bc31319 100644 --- a/cosiap_api/common/tests.py +++ b/cosiap_api/common/tests.py @@ -1,10 +1,11 @@ -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 @@ -71,23 +72,47 @@ class BasePerUserTestCase(APITestCase): 'access': str(refresh.access_token), } - def perform_request(self, view, method, token=None, user=None, data=None): + 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. - - Parámetros: - - view (View): (La vista de Django Rest Framework a la que se realizará la solicitud.) - - method (str): (El método HTTP a utilizar para la solicitud ('get', 'post', 'put', 'patch', 'delete', etc.).) - - token (dict, opcional): (Un diccionario con los tokens de autenticación ('refresh' y 'access'). Si se proporciona, se usará para autenticar la solicitud.) - - user (User, opcional): (El usuario al que pertenece el token. Necesario si se proporciona un token para la autenticación.) - - Retorna: - - Response: La respuesta de la vista a la solicitud realizada. + 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. """ - request = getattr(self.factory, method)('/', data=data) + client = APIClient() if token: - force_authenticate(request, user=user, token=token['access']) - response = view(request) + 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 diff --git a/cosiap_api/modalidades/models.py b/cosiap_api/modalidades/models.py index d43d3fd..f50cdef 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 index 58f287a..f0b45a4 100644 --- a/cosiap_api/modalidades/serializers.py +++ b/cosiap_api/modalidades/serializers.py @@ -14,6 +14,17 @@ 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 get_monto(self, obj): ultimo_monto = MontoModalidad.objects.filter(modalidad=obj).order_by('-fecha_inicio').first() if ultimo_monto: @@ -32,7 +43,7 @@ class ModalidadSerializer(serializers.ModelSerializer): if monto is not None: ultimo_monto = MontoModalidad.objects.filter(modalidad=modalidad).order_by('-fecha_inicio').first() - if ultimo_monto and ultimo_monto.monto != monto: + 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 5b9e2f3..ec62749 100644 --- a/cosiap_api/modalidades/tests.py +++ b/cosiap_api/modalidades/tests.py @@ -1,97 +1,195 @@ from rest_framework import status -from modalidades.models import Modalidad +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): - view = ModalidadAPIView.as_view() - response = self.perform_request(view, 'get') - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + 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): - view = ModalidadAPIView.as_view() - response = self.perform_request(view, 'get', token=self.user_token, user=self.user) + 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): - view = ModalidadAPIView.as_view() - response = self.perform_request(view, 'get', token=self.admin_token, user=self.admin_user) + 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): - view = ModalidadAPIView.as_view() - response = self.perform_request(view, 'get', token=self.solicitante_token, user=self.solicitante_user) + 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): - view = ModalidadAPIView.as_view() + 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(view, 'post', token=self.admin_token, user=self.admin_user, data=data) + 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(), 1) + 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): - view = ModalidadAPIView.as_view() + 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(view, 'post', token=self.solicitante_token, user=self.solicitante_user, data=data) + 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(), 0) + self.assertEqual(Modalidad.objects.count(), 2) def test_put_modalidad_as_admin(self): - modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) - view = ModalidadAPIView.as_view() - data = { - 'pk': modalidad.pk, + 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(view, 'put', token=self.admin_token, user=self.admin_user, data=data) + 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, 'Modalidad Actualizada') + 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): - modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) - view = ModalidadAPIView.as_view() - data = { - 'pk': modalidad.pk, + 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(view, 'put', token=self.solicitante_token, user=self.solicitante_user, data=data) + 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): - modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) - view = ModalidadAPIView.as_view() - data = { - 'pk': modalidad.pk, - } - response = self.perform_request(view, 'delete', token=self.admin_token, user=self.admin_user, data=data) + 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): - modalidad = Modalidad.objects.create(nombre='Modalidad Existente', descripcion='Descripción', mostrar=True, archivado=False) - view = ModalidadAPIView.as_view() - data = { - 'pk': modalidad.pk, - } - response = self.perform_request(view, 'delete', token=self.solicitante_token, user=self.solicitante_user, data=data) + 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) + diff --git a/cosiap_api/modalidades/urls.py b/cosiap_api/modalidades/urls.py index b5a1ac3..b391b8e 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 0971ec8..85a830a 100644 --- a/cosiap_api/modalidades/views.py +++ b/cosiap_api/modalidades/views.py @@ -1,7 +1,8 @@ from common.views import BasePermissionAPIView from rest_framework import response from rest_framework import status -from rest_framework.permissions import IsAuthenticated, IsAdminUser +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 @@ -20,8 +21,10 @@ class ModalidadAPIView(BasePermissionAPIView): - DELETE (Si bien no se permiten eliminar las modalidades, este método la archivará en su lugar) ''' - permission_classes_delete = [IsAuthenticated, IsAdminUser] - permission_classes_update = [IsAuthenticated, IsAdminUser] + 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: @@ -33,17 +36,18 @@ class ModalidadAPIView(BasePermissionAPIView): response_data = {'data': serializer.data} return Response(response_data, status=status.HTTP_200_OK) - def post(self, request): - serializer = ModalidadSerializer(data=request.data) - if serializer.is_valid(): + 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.') + 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) + Mensaje.error(response_data, serializer.errors) return Response(response_data, status=status.HTTP_400_BAD_REQUEST) def put(self, request, pk): diff --git a/cosiap_api/notificaciones/notificaciones.py b/cosiap_api/notificaciones/notificaciones.py new file mode 100644 index 0000000..c6a1f90 --- /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 622e1de..bd82b63 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 b49e885..f606229 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 -- GitLab