From cb482970561730a62a65a497924d355a75806058 Mon Sep 17 00:00:00 2001 From: RafaUC Date: Tue, 9 Jul 2024 12:32:40 -0600 Subject: [PATCH 1/3] creacion de los modelos de dynamic_formats, modalidades, minuta y convenio --- cosiap_api/common/models.py | 25 ++++++ cosiap_api/common/nombres_archivos.py | 28 ++++-- cosiap_api/common/validadores_campos.py | 18 ++++ cosiap_api/cosiap_api/consumers.py | 27 ++++++ cosiap_api/cosiap_api/settings.py | 5 +- cosiap_api/cosiap_api/wsgi.py | 22 ++++- ...namic_formats__modalidades__solicitudes.py | 28 ++++++ cosiap_api/dynamic_formats/models.py | 21 ++++- cosiap_api/entrypoint.sh | 3 +- ...namic_formats__modalidades__solicitudes.py | 45 ++++++++++ cosiap_api/modalidades/models.py | 61 ++++++++++++- .../commands/delete_old_notifications.py | 16 ++++ ...namic_formats__modalidades__solicitudes.py | 46 ++++++++++ cosiap_api/notificaciones/models.py | 71 +++++++++++++++- ...namic_formats__modalidades__solicitudes.py | 40 +++++++++ cosiap_api/solicitudes/models.py | 85 ++++++++++++++++++- cosiap_api/solicitudes/signals.py | 4 + cosiap_api/users/permisos.py | 1 + cosiap_api/users/serializers.py | 6 +- cosiap_api/users/views.py | 10 +-- 20 files changed, 541 insertions(+), 21 deletions(-) create mode 100644 cosiap_api/common/validadores_campos.py create mode 100644 cosiap_api/cosiap_api/consumers.py create mode 100644 cosiap_api/dynamic_formats/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py create mode 100644 cosiap_api/modalidades/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py create mode 100644 cosiap_api/notificaciones/commands/delete_old_notifications.py create mode 100644 cosiap_api/notificaciones/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py create mode 100644 cosiap_api/solicitudes/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py create mode 100644 cosiap_api/solicitudes/signals.py diff --git a/cosiap_api/common/models.py b/cosiap_api/common/models.py index 71a8362..63e0c5a 100644 --- a/cosiap_api/common/models.py +++ b/cosiap_api/common/models.py @@ -1,3 +1,28 @@ from django.db import models # Create your models here. +class SingletonModel(models.Model): + ''' + Modelo Singleton para objetos que solo necesitan una instancia + ''' + + class Meta: + abstract = True + + def save(self, *args, **kwargs): + """ + Guarda el objeto en la base de datos y elimina los dema si existen + """ + self.__class__.objects.exclude(id=self.id).delete() + super(SingletonModel, self).save(*args, **kwargs) + + @classmethod + def get_object(cls): + """ + Carga el objeto de la base de datos. Si no existe ningun objeto + se crea uno nuevo sin guardar y lo devuelve. + """ + try: + return cls.objects.get() + except cls.DoesNotExist: + return cls() diff --git a/cosiap_api/common/nombres_archivos.py b/cosiap_api/common/nombres_archivos.py index 3fa5e3c..73df422 100644 --- a/cosiap_api/common/nombres_archivos.py +++ b/cosiap_api/common/nombres_archivos.py @@ -1,11 +1,17 @@ -import uuid +from uuid import uuid4 +from django.conf import settings import os # Función para generar nombre único de archivo -def generar_nombre_archivo(nombre_archivo, path): +def generar_nombre_archivo(nombre_archivo, path, protected=True): + ''' + Genera la ruta de un archivo de media con un nombre unico aleatorio. + ''' ext = nombre_archivo.split('.')[-1] - nombre_unico = f"{uuid.uuid4().hex}.{ext}" - return os.path.join(path, nombre_unico) + nombre_unico = f"{uuid4().hex}.{ext}" + if protected: + os.path.join('protected_uploads/', path) + return os.path.join(settings.MEDIA_ROOT, path, nombre_unico) # Funciones para nombre de archivo específico para cada campo de FileField def nombre_archivo_estado_cuenta(instance, filename): @@ -15,4 +21,16 @@ def nombre_archivo_sat(instance, filename): return generar_nombre_archivo(filename, 'constancia_sat_files/') def nombre_archivo_ine(instance, filename): - return generar_nombre_archivo(filename, 'INE_files/') \ No newline at end of file + return generar_nombre_archivo(filename, 'INE_files/') + +def nombre_archivo_minuta(instance, filename): + return generar_nombre_archivo(filename, 'minutas/',) + +def nombre_archivo_convenio(instance, filename): + return generar_nombre_archivo(filename, 'convenios/',) + +def nombre_archivo_modalidad(instance, filename): + return generar_nombre_archivo(filename, 'modalidades/', protected=False) + +def nombre_archivo_formato(instance, filename): + return generar_nombre_archivo(filename, 'formatos/', protected=False) \ No newline at end of file diff --git a/cosiap_api/common/validadores_campos.py b/cosiap_api/common/validadores_campos.py new file mode 100644 index 0000000..e4bd9ec --- /dev/null +++ b/cosiap_api/common/validadores_campos.py @@ -0,0 +1,18 @@ +import os +from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ + +def validador_pdf(value): + ''' + Validador para campos de archivo que verifica que su extencion sea un pdf + ''' + ext = os.path.splitext(value.name)[1] # Obtener la extensión del archivo + valid_extensions = ['.pdf'] # Lista de extensiones permitidas + if ext.lower() not in valid_extensions: + raise ValidationError(_('Sólo se permiten archivos en formato PDF.')) + +def validador_archivo_1MB(value): + '''Validador que valida que un archivo subido no sobrepase el tamaño de 1MB''' + filesize = value.size + if filesize > 1048576: # 1MB + raise ValidationError("El archivo es demasiado grande. El tamaño máximo permitido es 1MB.") \ No newline at end of file diff --git a/cosiap_api/cosiap_api/consumers.py b/cosiap_api/cosiap_api/consumers.py new file mode 100644 index 0000000..b9648d5 --- /dev/null +++ b/cosiap_api/cosiap_api/consumers.py @@ -0,0 +1,27 @@ +import json +from channels.generic.websocket import AsyncWebsocketConsumer + +class NotificacionConsumer(AsyncWebsocketConsumer): + async def connect(self): + await self.accept() + # Asociar la conexión WebSocket con el usuario + self.user = self.scope["user"] + await self.channel_layer.group_add( + f"user_{self.user.id}", + self.channel_name + ) + + async def disconnect(self, close_code): + # Eliminar la asociación de la conexión WebSocket cuando se desconecta + await self.channel_layer.group_discard( + f"user_{self.user.id}", + self.channel_name + ) + + async def notificar_notificacion(self, event): + mensaje = event['mensaje'] + + # Envía el mensaje al cliente + await self.send(text_data=json.dumps({ + 'mensaje': mensaje + })) diff --git a/cosiap_api/cosiap_api/settings.py b/cosiap_api/cosiap_api/settings.py index aa384da..3341e2c 100644 --- a/cosiap_api/cosiap_api/settings.py +++ b/cosiap_api/cosiap_api/settings.py @@ -148,7 +148,7 @@ EMAIL_USE_TLS = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles/') STATICFILES_DIRS = ( @@ -181,7 +181,8 @@ REST_FRAMEWORK = { ), 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema', 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated' + 'rest_framework.permissions.IsAuthenticated', + 'users.permisos.primer_login' #'rest_framework.permissions.AllowAny' ], } diff --git a/cosiap_api/cosiap_api/wsgi.py b/cosiap_api/cosiap_api/wsgi.py index 809e51c..05a5542 100644 --- a/cosiap_api/cosiap_api/wsgi.py +++ b/cosiap_api/cosiap_api/wsgi.py @@ -10,7 +10,27 @@ https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/ import os from django.core.wsgi import get_wsgi_application +from django.core.asgi import get_asgi_application +from channels.routing import ProtocolTypeRouter, URLRouter +from django.core.asgi import get_asgi_application + +from channels.auth import AuthMiddlewareStack +from django.urls import re_path +from .consumers import NotificacionConsumer os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cosiap_api.settings') -application = get_wsgi_application() +django_asgi_app = get_asgi_application() + +websocket_urlpatterns = [ + re_path(r'ws/notificaciones/$', NotificacionConsumer.as_asgi()), +] + +application = ProtocolTypeRouter({ + "http": django_asgi_app, + "websocket": AuthMiddlewareStack( + URLRouter( + websocket_urlpatterns + ) + ), +}) diff --git a/cosiap_api/dynamic_formats/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py b/cosiap_api/dynamic_formats/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py new file mode 100644 index 0000000..6a57583 --- /dev/null +++ b/cosiap_api/dynamic_formats/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.6 on 2024-07-09 16:29 + +import common.nombres_archivos +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='DynamicFormat', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nombre', models.CharField(max_length=255, verbose_name='Nombre')), + ('template', models.FileField(upload_to=common.nombres_archivos.nombre_archivo_formato, verbose_name='Plantilla')), + ], + options={ + 'verbose_name': 'Formato Dinámico', + 'verbose_name_plural': 'Formatos Dinámicos', + 'ordering': ['nombre'], + }, + ), + ] diff --git a/cosiap_api/dynamic_formats/models.py b/cosiap_api/dynamic_formats/models.py index 71a8362..222fae0 100644 --- a/cosiap_api/dynamic_formats/models.py +++ b/cosiap_api/dynamic_formats/models.py @@ -1,3 +1,22 @@ from django.db import models +from common.nombres_archivos import nombre_archivo_formato -# Create your models here. +class DynamicFormat(models.Model): + ''' + Modelo que contiene la información de los formatos dinámicos. + + Campos: + - **nombre**: Nombre del formato. + - **template**: Archivo de plantilla asociado al formato. + ''' + + nombre = models.CharField(max_length=255, verbose_name="Nombre") + template = models.FileField(upload_to=nombre_archivo_formato, verbose_name="Plantilla") + + def __str__(self): + return self.nombre + + class Meta: + verbose_name = "Formato Dinámico" + verbose_name_plural = "Formatos Dinámicos" + ordering = ['nombre'] diff --git a/cosiap_api/entrypoint.sh b/cosiap_api/entrypoint.sh index 7f6735d..914602a 100644 --- a/cosiap_api/entrypoint.sh +++ b/cosiap_api/entrypoint.sh @@ -16,7 +16,8 @@ done #Django commands -#python3 manage.py makemigrations +>&2 echo "Ejecutando Migraciones" +#python3 manage.py makemigrations --name python3 manage.py migrate #python3 manage.py collectstatic --no-input diff --git a/cosiap_api/modalidades/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py b/cosiap_api/modalidades/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py new file mode 100644 index 0000000..8728738 --- /dev/null +++ b/cosiap_api/modalidades/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py @@ -0,0 +1,45 @@ +# Generated by Django 5.0.6 on 2024-07-09 16:29 + +import common.nombres_archivos +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Modalidad', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('nombre', models.CharField(max_length=255, verbose_name='Nombre')), + ('imagen', models.ImageField(upload_to=common.nombres_archivos.nombre_archivo_modalidad, verbose_name='Imagen')), + ('descripcion', models.TextField(verbose_name='Descripción')), + ('mostrar', models.BooleanField(default=True)), + ('archivado', models.BooleanField(default=False)), + ], + options={ + 'ordering': ['nombre'], + }, + ), + migrations.CreateModel( + name='MontoModalidad', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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(blank=True, null=True, verbose_name='Fecha de Fin')), + ('modalidad', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='modalidades.modalidad', verbose_name='Modalidad')), + ], + options={ + 'verbose_name': 'Monto de Modalidad', + 'verbose_name_plural': 'Montos de Modalidades', + 'ordering': ['modalidad', '-fecha_inicio'], + }, + ), + ] diff --git a/cosiap_api/modalidades/models.py b/cosiap_api/modalidades/models.py index 71a8362..d43d3fd 100644 --- a/cosiap_api/modalidades/models.py +++ b/cosiap_api/modalidades/models.py @@ -1,3 +1,62 @@ 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 -# Create your models here. + +class Modalidad(models.Model): + ''' + Modelo que contiene la informacion de una modalidad. + + Campos: + - nombre: Nombre de la modalidad. Es un campo de texto con un máximo de 255 caracteres. + - imagen: Imagen asociada a la modalidad. + - descripcion: Descripción de la modalidad. Es un campo de texto. + - mostrar: Booleano que indica si la modalidad se debe mostrar o no. Por defecto es True. + - archivado: Booleano que indica si la modalidad está archivada. Por defecto es False. + - dynamic_form: Campo reservado para formularios dinámicos (actualmente no se usa). + ''' + 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) + mostrar = models.BooleanField(default=True) + archivado = models.BooleanField(default=False) + dynamic_form = None + + def __str__(self): + return f'{self.nombre} ({self.tipo})' + + class Meta: + ordering = ['nombre'] + +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(MontoModalidad, self.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/notificaciones/commands/delete_old_notifications.py b/cosiap_api/notificaciones/commands/delete_old_notifications.py new file mode 100644 index 0000000..26b5b72 --- /dev/null +++ b/cosiap_api/notificaciones/commands/delete_old_notifications.py @@ -0,0 +1,16 @@ +from django.core.management.base import BaseCommand +from datetime import timedelta +from django.utils import timezone +from mensajes.models import Notificacion + +class Command(BaseCommand): + help = 'Elimina registros más antiguos de 3 meses' + + def handle(self, *args, **options): + borrar_notificaciones_viejas() + self.stdout.write(self.style.SUCCESS('Registros eliminados con éxito')) + +def borrar_notificaciones_viejas(): + print('Borrando notificaciones mas antiguas a 3 meses') + three_months_ago = timezone.now() - timedelta(days=90) + Notificacion.objects.filter(timestamp__lt=three_months_ago).delete() diff --git a/cosiap_api/notificaciones/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py b/cosiap_api/notificaciones/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py new file mode 100644 index 0000000..67c313b --- /dev/null +++ b/cosiap_api/notificaciones/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py @@ -0,0 +1,46 @@ +# Generated by Django 5.0.6 on 2024-07-09 16:29 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Notificacion', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('leido', models.BooleanField(default=False)), + ('titulo', models.CharField(blank=True, default='Sistema de Apoyos COZCYT', max_length=255, null=True)), + ('mensaje', models.TextField()), + ('urlName', models.CharField(blank=True, max_length=255, null=True)), + ('urlArgs', models.JSONField(blank=True, null=True)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('usuario', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Usuario')), + ], + options={ + 'ordering': ['usuario', '-timestamp'], + }, + ), + migrations.CreateModel( + name='NotifInboxLastOpened', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timestamp', models.DateTimeField(auto_now=True, verbose_name='Fecha y hora de última apertura')), + ('usuario', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Usuario')), + ], + options={ + 'verbose_name': 'Última apertura de bandeja de entrada', + 'verbose_name_plural': 'Últimas aperturas de bandeja de entrada', + 'ordering': ['-timestamp'], + }, + ), + ] diff --git a/cosiap_api/notificaciones/models.py b/cosiap_api/notificaciones/models.py index 71a8362..c8f173b 100644 --- a/cosiap_api/notificaciones/models.py +++ b/cosiap_api/notificaciones/models.py @@ -1,3 +1,72 @@ from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver -# Create your models here. +from asgiref.sync import async_to_sync +from channels.layers import get_channel_layer +from django.urls import reverse +from django.conf import settings + + +class NotifInboxLastOpened(models.Model): + ''' + Modelo que registra la última vez que un usuario abrió su bandeja de entrada de notificaciones. + + Campos: + - **usuario**: Usuario al que pertenece el registro. Debe ser único. + - **timestamp**: Fecha y hora en que el usuario abrió su bandeja de entrada por última vez. Se autogenera cuando se crea o actualiza el registro. + ''' + + usuario = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, unique=True, verbose_name="Usuario") + timestamp = models.DateTimeField(auto_now=True, verbose_name="Fecha y hora de última apertura") + + def __str__(self): + return f'Última apertura de {self.usuario.username} en {self.timestamp}' + + class Meta: + verbose_name = "Última apertura de bandeja de entrada" + verbose_name_plural = "Últimas aperturas de bandeja de entrada" + ordering = ['-timestamp'] + +class Notificacion(models.Model): + ''' + Modelo que contiene la información de las notificaciones. + + Campos: + - *usuario*: Usuario al que va dirigida la notificación. + - *leido*: Booleano que indica si el mensaje ya ha sido abierto. Por defecto es False. + - *titulo*: Título de la notificación. Por defecto es "Sistema de Apoyos COZCYT". Puede estar en blanco o ser nulo. + - *mensaje*: Mensaje de la notificación. + - *urlName*: 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. + - *timestamp*: Fecha y hora en que la notificación fue creada. Se autogenera cuando se crea la notificación. + ''' + TITULO_DEFAULT = 'Sistema de Apoyos COZCYT' + + usuario = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="Usuario") + leido = models.BooleanField(default=False) + titulo = models.CharField(max_length=255, blank=True, null=True, default=TITULO_DEFAULT) + mensaje = models.TextField() + urlName = models.CharField(max_length=255, blank=True, null=True) + urlArgs = models.JSONField(blank=True, null=True) + timestamp = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['usuario', '-timestamp'] + + def __str__(self): + return f'Notificación de {self.usuario} - {self.timestamp}' + +@receiver(post_save, sender=Notificacion) +def notificar_nueva_notificacion(sender, instance, created, **kwargs): + if created: + if instance.usuario.is_authenticated: + # Lógica para enviar la notificación al usuario + channel_layer = get_channel_layer() + async_to_sync(channel_layer.group_send)( + f'user_{instance.usuario.id}', + { + 'type': 'notificar_notificacion', + 'mensaje': 'nuevaNotificacion', + } + ) \ No newline at end of file diff --git a/cosiap_api/solicitudes/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py b/cosiap_api/solicitudes/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py new file mode 100644 index 0000000..e6b15ac --- /dev/null +++ b/cosiap_api/solicitudes/migrations/0001_creacion_inicial_modulos_dynamic_formats__modalidades__solicitudes.py @@ -0,0 +1,40 @@ +# Generated by Django 5.0.6 on 2024-07-09 16:29 + +import common.nombres_archivos +import common.validadores_campos +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Convenio', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('archivo', models.FileField(upload_to=common.nombres_archivos.nombre_archivo_convenio, validators=[common.validadores_campos.validador_archivo_1MB, common.validadores_campos.validador_pdf], verbose_name='Archivo')), + ], + options={ + 'verbose_name': 'Minuta', + 'verbose_name_plural': 'Minutas', + 'ordering': ['pk'], + }, + ), + migrations.CreateModel( + name='Minuta', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('archivo', models.FileField(upload_to=common.nombres_archivos.nombre_archivo_minuta, validators=[common.validadores_campos.validador_archivo_1MB, common.validadores_campos.validador_pdf], verbose_name='Archivo')), + ], + options={ + 'verbose_name': 'Minuta', + 'verbose_name_plural': 'Minutas', + 'ordering': ['pk'], + }, + ), + ] diff --git a/cosiap_api/solicitudes/models.py b/cosiap_api/solicitudes/models.py index 71a8362..10c6637 100644 --- a/cosiap_api/solicitudes/models.py +++ b/cosiap_api/solicitudes/models.py @@ -1,3 +1,86 @@ from django.db import models +from common.nombres_archivos import nombre_archivo_minuta, nombre_archivo_convenio +from common.validadores_campos import validador_archivo_1MB, validador_pdf +from dynamic_formats.models import DynamicFormat -# Create your models here. +class Minuta(models.Model): + ''' + Modelo que contiene la información de las minutas. + + Campos: + - *archivo*: Archivo de la minuta. + + Métodos: + - *get_formato()*: Devuelve el formato común de todas las instancias de Minuta. + - *set_formato(value)*: Establece el formato común para todas las instancias de Minuta. + ''' + archivo = models.FileField(upload_to=nombre_archivo_minuta, validators=[validador_archivo_1MB, validador_pdf], verbose_name="Archivo") + # Atributo de clase común a todas las instancias + _formato = None + + @classmethod + def get_formato(cls): + ''' + Devuelve el formato común de todas las instancias de Minuta. + ''' + if cls._formato is None: + formato_minuta = DynamicFormat.objects.filter(nombre="formato_minuta_defult").first() + cls._formato = formato_minuta + return cls._formato + + @classmethod + def set_formato(cls, value): + '''Establece el formato común para todas las instancias de Minuta. + Args: + - value: El nuevo valor del formato. + ''' + cls._formato = value + + def __str__(self): + return f'Minuta {self.pk}' + + class Meta: + verbose_name = "Minuta" + verbose_name_plural = "Minutas" + ordering = ['pk'] + +class Convenio(models.Model): + ''' + Modelo que contiene la información de los Convenios. + + Campos: + - *archivo*: Archivo del convenio. + + Métodos: + - *get_formato()*: Devuelve el formato común de todas las instancias de Minuta. + - *set_formato(value)*: Establece el formato común para todas las instancias de Minuta. + ''' + archivo = models.FileField(upload_to=nombre_archivo_convenio, validators=[validador_archivo_1MB, validador_pdf], verbose_name="Archivo") + # Atributo de clase común a todas las instancias + _formato = None + + @classmethod + def get_formato(cls): + ''' + Devuelve el formato común de todas las instancias de Convenio. + ''' + if cls._formato is None: + formato_convenio = DynamicFormat.objects.filter(nombre="formato_convenio_defult").first() + cls._formato = formato_convenio + return cls._formato + + @classmethod + def set_formato(cls, value): + '''Establece el formato común para todas las instancias de Minuta. + Args: + - value: El nuevo valor del formato. + ''' + cls._formato = value + + def __str__(self): + return f'Minuta {self.pk}' + + class Meta: + verbose_name = "Minuta" + verbose_name_plural = "Minutas" + ordering = ['pk'] \ No newline at end of file diff --git a/cosiap_api/solicitudes/signals.py b/cosiap_api/solicitudes/signals.py new file mode 100644 index 0000000..a9c4e43 --- /dev/null +++ b/cosiap_api/solicitudes/signals.py @@ -0,0 +1,4 @@ +from django.dispatch import receiver +from dynamic_formats.models import DynamicFormat +from solicitudes.models import Minuta, Convenio + diff --git a/cosiap_api/users/permisos.py b/cosiap_api/users/permisos.py index 6382d66..b49e885 100644 --- a/cosiap_api/users/permisos.py +++ b/cosiap_api/users/permisos.py @@ -9,6 +9,7 @@ class es_admin(permissions.BasePermission): # Clase para verificar que el usuario que va a ingresar tenga sus datos completos class primer_login(permissions.BasePermission): + message = 'El usuario requiere completar su información.' # método para determinar si se requiere o no un primer login def has_permission(self, request, view): # obtenemos al usuario de la request diff --git a/cosiap_api/users/serializers.py b/cosiap_api/users/serializers.py index 4b47cac..65375a0 100644 --- a/cosiap_api/users/serializers.py +++ b/cosiap_api/users/serializers.py @@ -3,7 +3,7 @@ # Versión: 1.0 from rest_framework import serializers -from .models import Usuario, Solicitante, DatosBancarios +from .models import Usuario, Usuario, DatosBancarios # serializer para el usuario solicitante class usuario_serializer(serializers.ModelSerializer): @@ -56,7 +56,7 @@ class datos_bancarios_serializer(serializers.ModelSerializer): class solicitante_serializer(serializers.ModelSerializer): class Meta: # indicamos el modelo a utilziar - model = Solicitante + model = Usuario # 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 @@ -68,7 +68,7 @@ class solicitante_serializer(serializers.ModelSerializer): # Definimos una función para crear al Solicitante def create(self, validated_data): user = self.context['request'].user - solicitante = Solicitante.objects.create( + solicitante = Usuario.objects.create( curp=user.curp, nombre=user.nombre, email=user.email, diff --git a/cosiap_api/users/views.py b/cosiap_api/users/views.py index d5b7da6..5926cf1 100644 --- a/cosiap_api/users/views.py +++ b/cosiap_api/users/views.py @@ -7,7 +7,7 @@ from rest_framework import status, permissions from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import AllowAny, IsAuthenticated -from .models import Usuario, Solicitante +from .models import Usuario, Usuario from django.contrib import messages from .serializers import usuario_serializer, solicitante_serializer from django.contrib.auth import authenticate @@ -190,7 +190,7 @@ class solicitante(BasePermissionAPIView): if 'pk' in kwargs: self.check_object_permissions(request, request.user) # obtenemos la instancia del solicitante - instance = get_object_or_404(Solicitante, pk=kwargs['pk']) + instance = get_object_or_404(Usuario, pk=kwargs['pk']) self.check_object_permissions(request, instance) # indicamos el serializer a utilizar y enviamos la instancia serializer = solicitante_serializer(instance) @@ -199,7 +199,7 @@ class solicitante(BasePermissionAPIView): # si se desea ver la lista completa else: # indicamos el query set de todos los usuarios - queryset = Solicitante.objects.all() + queryset = Usuario.objects.all() # indicamos el serializer a utilizar y enviamos el queryset serializer = solicitante_serializer(queryset, many=True) # retornamos la lista de usuarios @@ -210,7 +210,7 @@ class solicitante(BasePermissionAPIView): # obtenemos al usuario del request usuario = request.user # creamos el solicitante - solicitante, created = Solicitante.objects.get_or_create( + solicitante, created = Usuario.objects.get_or_create( id=usuario.id, defaults={ 'curp': usuario.curp, @@ -237,7 +237,7 @@ class solicitante(BasePermissionAPIView): # Solicitud put para actualizar los datos del solicitante def put(self, request, *args, **kwargs): # recuperamos el solicitante de la base de datos, enviando un error si no se encuentra - solicitante = get_object_or_404(Solicitante, id=request.user.id) + solicitante = get_object_or_404(Usuario, id=request.user.id) # inicializamos el serializer con los datos precargados serializer = solicitante_serializer(instance=solicitante, data=request.data) # si el serilizer es valido -- GitLab From 09986458196f0ede5a1d35b57f54c113de93e17f Mon Sep 17 00:00:00 2001 From: AdalbertoCV <34152734@uaz.edu.mx> Date: Wed, 10 Jul 2024 11:30:20 -0600 Subject: [PATCH 2/3] =?UTF-8?q?Errores=20solucionados,=20creaci=C3=B3n=20d?= =?UTF-8?q?e=20modelo=20DynamicTableReport?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cosiap_api/dynamic_tables/DynamicTable.py | 0 .../0001_creacion_tabla_dynamictablereport.py | 28 ++++++++++++++++++ cosiap_api/dynamic_tables/models.py | 29 ++++++++++++++++++- cosiap_api/users/admin.py | 5 ++-- cosiap_api/users/admin_views.py | 2 +- cosiap_api/users/serializers.py | 6 ++-- cosiap_api/users/urls.py | 12 ++++---- cosiap_api/users/views.py | 12 ++++---- 8 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 cosiap_api/dynamic_tables/DynamicTable.py create mode 100644 cosiap_api/dynamic_tables/migrations/0001_creacion_tabla_dynamictablereport.py diff --git a/cosiap_api/dynamic_tables/DynamicTable.py b/cosiap_api/dynamic_tables/DynamicTable.py new file mode 100644 index 0000000..e69de29 diff --git a/cosiap_api/dynamic_tables/migrations/0001_creacion_tabla_dynamictablereport.py b/cosiap_api/dynamic_tables/migrations/0001_creacion_tabla_dynamictablereport.py new file mode 100644 index 0000000..8e6c9b1 --- /dev/null +++ b/cosiap_api/dynamic_tables/migrations/0001_creacion_tabla_dynamictablereport.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.6 on 2024-07-10 16:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='DynamicTableReport', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('model_name', models.CharField(max_length=100)), + ('columns', models.JSONField()), + ('exclude_columns', models.JSONField(blank=True, null=True)), + ('search_query', models.CharField(blank=True, max_length=100, null=True)), + ('filters', models.JSONField(blank=True, null=True)), + ], + options={ + 'unique_together': {('model_name', 'columns', 'exclude_columns', 'search_query', 'filters')}, + }, + ), + ] diff --git a/cosiap_api/dynamic_tables/models.py b/cosiap_api/dynamic_tables/models.py index 71a8362..cbd4ac8 100644 --- a/cosiap_api/dynamic_tables/models.py +++ b/cosiap_api/dynamic_tables/models.py @@ -1,3 +1,30 @@ from django.db import models -# Create your models here. +class DynamicTableReport(models.Model): + ''' + Clase para manejar y guardar las configuraciones de las tablas dinámicas y sus reportes generados + Columnas: + - model_name (Nombre del modelo que va a manejar) + - columns (Lista de columnas del modelo) + - exclude_columns (Lista de las columnas que no se van a incluir en el reporte) + - search_query (Cadena de búsqueda para la obtención de los datos del modelo y sus relaciones) + - filters (Filtros a aplicar recibidos desde el front) + ''' + model_name = models.CharField(max_length=100) + columns = models.JSONField() + # Campos opcionales + exclude_columns = models.JSONField(blank=True, null=True) + search_query = models.CharField(max_length=100, blank=True, null=True) + filters = models.JSONField(blank=True, null=True) + + # Incluimos la logica de unique para no guardar configuraciones que sean exactamente iguales + class Meta: + unique_together = ('model_name', 'columns', 'exclude_columns', 'search_query', 'filters') + + + def __str__(self): + return 'Tabla Dinámica: ' + self.model_name + + + + diff --git a/cosiap_api/users/admin.py b/cosiap_api/users/admin.py index a218f14..7a5bef6 100644 --- a/cosiap_api/users/admin.py +++ b/cosiap_api/users/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin -from users.models import Usuario, Solicitante, Municipio, Estado +from users.models import Usuario, Solicitante, Municipio, Estado, DatosBancarios # Register your models here. @@ -33,4 +33,5 @@ class UserAdmin(UserAdmin): admin.site.register(Usuario, UserAdmin) admin.site.register(Solicitante) admin.site.register(Estado) -admin.site.register(Municipio) \ No newline at end of file +admin.site.register(Municipio) +admin.site.register(DatosBancarios) \ No newline at end of file diff --git a/cosiap_api/users/admin_views.py b/cosiap_api/users/admin_views.py index 95ada56..5a93d66 100644 --- a/cosiap_api/users/admin_views.py +++ b/cosiap_api/users/admin_views.py @@ -13,7 +13,7 @@ from .tokens import account_activation_token from django.utils.encoding import force_bytes # clase de APIView con el método post para la creación de el administrador -class administrador(APIView): +class AdminAPIView(APIView): # permitimos que unicamente un administrador pueda crear otras cuentas de admin permission_classes = [es_admin] diff --git a/cosiap_api/users/serializers.py b/cosiap_api/users/serializers.py index 84a0e25..67dbc99 100644 --- a/cosiap_api/users/serializers.py +++ b/cosiap_api/users/serializers.py @@ -3,7 +3,7 @@ # Versión: 1.0 from rest_framework import serializers -from .models import Usuario, Usuario, DatosBancarios +from .models import Usuario, Solicitante, DatosBancarios class AdminSerializer(serializers.ModelSerializer): @@ -89,7 +89,7 @@ class DatosBancariosSerializer(serializers.ModelSerializer): class SolicitanteSerializer(serializers.ModelSerializer): class Meta: # indicamos el modelo a utilziar - model = Usuario + model = Solicitante # 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 @@ -101,7 +101,7 @@ class SolicitanteSerializer(serializers.ModelSerializer): # Definimos una función para crear al Solicitante def create(self, validated_data): user = self.context['request'].user - solicitante = Usuario.objects.create( + solicitante = Solicitante.objects.create( curp=user.curp, nombre=user.nombre, email=user.email, diff --git a/cosiap_api/users/urls.py b/cosiap_api/users/urls.py index 0e773a5..d58e6e4 100644 --- a/cosiap_api/users/urls.py +++ b/cosiap_api/users/urls.py @@ -2,7 +2,7 @@ from . import views from django.urls import path from django.contrib.auth import views as auth_views from .views import CustomTokenObtainPairView, CustomTokenRefreshView -from .admin_views import administrador +from .admin_views import AdminAPIView app_name = 'users' @@ -10,12 +10,12 @@ urlpatterns = [ path('token/', CustomTokenObtainPairView.as_view(), name='token_obtain'), path('token/refresh/', CustomTokenRefreshView.as_view(), name='token_refresh'), - path('', views.Usuario.as_view(), name = 'usuario_list_create'), - path('/', views.Usuario.as_view(), name = 'usuario_get_delete'), - path('solicitantes/', views.Solicitante.as_view(), name = 'solicitante_list_create'), - path('solicitantes/', views.Solicitante.as_view(), name = 'solicitante_get'), + path('', views.UsuarioAPIView.as_view(), name = 'usuario_list_create'), + path('/', views.UsuarioAPIView.as_view(), name = 'usuario_get_delete'), + path('solicitantes/', views.SolicitanteAPIView.as_view(), name = 'solicitante_list_create'), + path('solicitantes/', views.SolicitanteAPIView.as_view(), name = 'solicitante_get'), path('verificar-correo///', views.VerificarCorreo.as_view(), name='verificar_correo'), path('restablecer-password/', views.ResetPassword.as_view(), name='reset_password'), path('nueva-password///', views.NuevaPassword.as_view(), name='nueva_password'), - path('administradores/', administrador.as_view() , name = 'administrador_list_create'), + path('administradores/', AdminAPIView.as_view() , name = 'administrador_list_create'), ] \ No newline at end of file diff --git a/cosiap_api/users/views.py b/cosiap_api/users/views.py index c2a45e7..acbdc43 100644 --- a/cosiap_api/users/views.py +++ b/cosiap_api/users/views.py @@ -7,7 +7,7 @@ from rest_framework import status, permissions from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import AllowAny, IsAuthenticated -from .models import Usuario, Usuario +from .models import Usuario, Solicitante from django.contrib import messages from .serializers import UsuarioSerializer, SolicitanteSerializer from django.contrib.auth import authenticate @@ -113,7 +113,7 @@ class BasePermissionAPIView(APIView): # Funcionalidad para crear un usuario en el sistema, ver sus datos o eliminarlo -class Usuario(BasePermissionAPIView): +class UsuarioAPIView(BasePermissionAPIView): """ Clase Usuario para manejar las solicitudes de los usuarios básicos @@ -201,7 +201,7 @@ class Usuario(BasePermissionAPIView): return Response({"message": {'success': 'Eliminación exitosa'}}, status=status.HTTP_204_NO_CONTENT) -class Solicitante(BasePermissionAPIView): +class SolicitanteAPIView(BasePermissionAPIView): """ Clase Solicitante @@ -223,7 +223,7 @@ class Solicitante(BasePermissionAPIView): if 'pk' in kwargs: self.check_object_permissions(request, request.user) # obtenemos la instancia del solicitante - instance = get_object_or_404(Usuario, pk=kwargs['pk']) + instance = get_object_or_404(Solicitante, pk=kwargs['pk']) self.check_object_permissions(request, instance) # indicamos el serializer a utilizar y enviamos la instancia serializer = SolicitanteSerializer(instance) @@ -232,7 +232,7 @@ class Solicitante(BasePermissionAPIView): # si se desea ver la lista completa else: # indicamos el query set de todos los usuarios - queryset = Usuario.objects.all() + queryset = Solicitante.objects.all() # indicamos el serializer a utilizar y enviamos el queryset serializer = SolicitanteSerializer(queryset, many=True) # retornamos la lista de usuarios @@ -243,7 +243,7 @@ class Solicitante(BasePermissionAPIView): # obtenemos al usuario del request usuario = request.user # creamos el solicitante - solicitante, created = Usuario.objects.get_or_create( + solicitante, created = Solicitante.objects.get_or_create( id=usuario.id, defaults={ 'curp': usuario.curp, -- GitLab From 39bc2aae2a5ec957c54bbc3277857cd41b29a62d Mon Sep 17 00:00:00 2001 From: AdalbertoCV <34152734@uaz.edu.mx> Date: Wed, 10 Jul 2024 12:27:56 -0600 Subject: [PATCH 3/3] =?UTF-8?q?Primera=20implementaci=C3=B3n=20de=20las=20?= =?UTF-8?q?tablas=20din=C3=A1micas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cosiap_api/cosiap_api/urls.py | 3 +- cosiap_api/dynamic_tables/DynamicTable.py | 90 +++++++++++++++++++++++ cosiap_api/dynamic_tables/admin.py | 3 +- cosiap_api/dynamic_tables/urls.py | 7 ++ cosiap_api/dynamic_tables/views.py | 20 ++++- 5 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 cosiap_api/dynamic_tables/urls.py diff --git a/cosiap_api/cosiap_api/urls.py b/cosiap_api/cosiap_api/urls.py index d9305b7..a08789b 100644 --- a/cosiap_api/cosiap_api/urls.py +++ b/cosiap_api/cosiap_api/urls.py @@ -27,8 +27,9 @@ urlpatterns = [ path('api/modalidades/',include('modalidades.urls')), path('api/notificaciones/',include('notificaciones.urls')), path('api/solicitudes/',include('solicitudes.urls')), + path('api/dynamic-tables/',include('dynamic_tables.urls')), - # API Doc UI: + # API Doc UI: path('api/schema/', SpectacularAPIView.as_view(), name='schema'), path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), diff --git a/cosiap_api/dynamic_tables/DynamicTable.py b/cosiap_api/dynamic_tables/DynamicTable.py index e69de29..250b935 100644 --- a/cosiap_api/dynamic_tables/DynamicTable.py +++ b/cosiap_api/dynamic_tables/DynamicTable.py @@ -0,0 +1,90 @@ +# Archivo con la lógica del manejo de las tablas dinámicas +# Autores: Adalberto Cerrillo Vázquez +# Versión: 1.0 + +from rest_framework import serializers +from django.apps import apps +from django.db.models import Q, Prefetch +from .models import DynamicTableReport + +class DynamicTable(serializers.ModelSerializer): + ''' + Clase equivalente a un serializer con la lógica del manejo de las tablas dinámicas + ''' + data = serializers.SerializerMethodField() + + class Meta: + model = DynamicTableReport + fields = '__all__' + + def get_data(self, obj): + ''' + Método que se encargará de recuperar los datos correspondientes del modelo enviado + + parámetros: + - obj: Configuración de reporte recibida, la cuál indica que datos se van a recuperar + ''' + + # Primero tenemos que buscar el modelo en las aplicaciones registradas en el sistema + # Para asegurarnos de que el modelo sea único y sea el deseado. + for app_config in apps.get_app_configs(): + try: + model = app_config.get_model(obj.model_name) + break + except LookupError: + continue + else: + # Si no encontramos el modelo enviamos un error + raise serializers.ValidationError(f"Model {obj.model_name} not found.") + + columns = obj.columns + exclude_columns = obj.exclude_columns or [] # Si es nulo, convertimos a una lista vacía para el mejor manejo + search_query = obj.search_query + filters = obj.filters or {} # si es nulo, convertimos a dict vacío para mejor manejo + + queryset = model.objects.all() + + # En este for, aplicamos los filtros envíados sobre el queryset + for key, value in filters.items(): + queryset = queryset.filter(**{key: value}) + + # Aplicamos el searchquery enviado y extraemos la información de los campos + if search_query: + search_fields = [field for field in model._meta.fields if field.name in columns] + search_criteria = Q() + for field in search_fields: + search_criteria |= Q(**{f"{field.name}__icontains": search_query}) + queryset = queryset.filter(search_criteria) + + # Seleccionamos las columnas a incluir y las columnas a excluir + queryset = queryset.values(*[col for col in columns if col not in exclude_columns]) + + # Finalmente incluimos todos los campos que estén relacionados a los modelos por las llaves foráneas + for field in model._meta.get_fields(): + if (field.is_relation and + (field.name not in exclude_columns) and + (field.related_model is not None)): + related_queryset = field.related_model.objects.all() + queryset = queryset.prefetch_related(Prefetch(field.name, queryset=related_queryset)) + + # devolvemos el nuevo queryset con filtros y exclusiones realizadas + return list(queryset) + + + def create(self, validated_data): + ''' + Método que se encargará de guardar en la base de datos una nueva configuración para la tabla dinámica + + parámetros: + - validated_data: Datos validados según la configuración de la base de datos + ''' + + # Aquí usamos el get_or_create para asegurarnos de no guardar dos configuraciones que sean exactamente iguales + instance, created = DynamicTableReport.objects.get_or_create( + model_name=validated_data['model_name'], + columns=validated_data['columns'], + exclude_columns=validated_data.get('exclude_columns', None), + search_query=validated_data.get('search_query', None), + filters=validated_data.get('filters', None) + ) + return instance diff --git a/cosiap_api/dynamic_tables/admin.py b/cosiap_api/dynamic_tables/admin.py index 8c38f3f..d163cdc 100644 --- a/cosiap_api/dynamic_tables/admin.py +++ b/cosiap_api/dynamic_tables/admin.py @@ -1,3 +1,4 @@ from django.contrib import admin +from .models import DynamicTableReport -# Register your models here. +admin.site.register(DynamicTableReport) \ No newline at end of file diff --git a/cosiap_api/dynamic_tables/urls.py b/cosiap_api/dynamic_tables/urls.py new file mode 100644 index 0000000..c337d86 --- /dev/null +++ b/cosiap_api/dynamic_tables/urls.py @@ -0,0 +1,7 @@ +from django.urls import path +from .views import DynamicTableView + +app_name = 'dynamic-tables' +urlpatterns = [ + path('', DynamicTableView.as_view(), name='dynamic_table'), +] \ No newline at end of file diff --git a/cosiap_api/dynamic_tables/views.py b/cosiap_api/dynamic_tables/views.py index 91ea44a..b73cc09 100644 --- a/cosiap_api/dynamic_tables/views.py +++ b/cosiap_api/dynamic_tables/views.py @@ -1,3 +1,19 @@ -from django.shortcuts import render +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status +from .DynamicTable import DynamicTable +from users.views import BasePermissionAPIView -# Create your views here. + +class DynamicTableView(BasePermissionAPIView): + ''' + Clase de APIView que heréda los permisos de la clase base + para el manejo de las tablas dinámicas y sus configuraciones + ''' + + def post(self, request): + serializer = DynamicTable(data=request.data) + if serializer.is_valid(): + instance = serializer.save() + return Response(serializer.data, status=status.HTTP_200_OK) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) \ No newline at end of file -- GitLab