diff --git a/becas_cozcyt/settings.py b/becas_cozcyt/settings.py index 5ec2c38f3322299354f529bcc8cb9760ccf4106d..19d4570f93f42bc50c6b4e47f2dba7d18b26a1b4 100644 --- a/becas_cozcyt/settings.py +++ b/becas_cozcyt/settings.py @@ -28,6 +28,11 @@ SECRET_KEY = 'django-insecure-%l2!-7+%fqz_(bmjzm=@($a_ys5oq=-t#v%f2=^tt0(8s%1a0& # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True +if False: #if DEBUG: # + import socket # only if you haven't already imported this + hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) + INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + ["127.0.0.1", "10.0.2.2"] + ALLOWED_HOSTS = [] @@ -45,17 +50,19 @@ INSTALLED_APPS = [ 'modalidades', 'solicitudes', 'estudio_socio_economico', + 'debug_toolbar', ] MIDDLEWARE = [ + 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', - "django_htmx.middleware.HtmxMiddleware", + "django_htmx.middleware.HtmxMiddleware", ] ROOT_URLCONF = 'becas_cozcyt.urls' @@ -160,4 +167,6 @@ EMAIL_HOST_PASSWORD = 'osrruvryhaqpmegi' EMAIL_PORT = 587 EMAIL_USE_TLS = True -PASSWORD_RESET_TIMEOUT = 86400 #El usuario cuenta con 24 horas para confirmar su cuenta. \ No newline at end of file +PASSWORD_RESET_TIMEOUT = 86400 #El usuario cuenta con 24 horas para confirmar su cuenta. + +DATA_UPLOAD_MAX_NUMBER_FIELDS = 9000 \ No newline at end of file diff --git a/becas_cozcyt/urls.py b/becas_cozcyt/urls.py index 871add6094b938e54654eec0a721596ecfc866a6..22ba03f5d82f1e4ebb933118e136687259f19db6 100644 --- a/becas_cozcyt/urls.py +++ b/becas_cozcyt/urls.py @@ -23,9 +23,9 @@ urlpatterns = [ path('',include('usuarios.urls')), path('',include('modalidades.urls')), path('',include('solicitudes.urls')), - path('',include('estudio_socio_economico.urls')), + path('',include('estudio_socio_economico.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) -#if settings.DEBUG: -# urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) \ No newline at end of file +if False: # if settings.DEBUG:# + urlpatterns += path("__debug__/", include("debug_toolbar.urls")), \ No newline at end of file diff --git a/estudioSE.sql b/estudioSE.sql new file mode 100644 index 0000000000000000000000000000000000000000..6c9ac19f6e7c92b9b23c45694e6b29c3d243f673 --- /dev/null +++ b/estudioSE.sql @@ -0,0 +1,57 @@ +-- MySQL dump 10.13 Distrib 8.1.0, for Win64 (x86_64) +-- +-- Host: localhost Database: db-becas +-- ------------------------------------------------------ +-- Server version 5.5.5-10.11.2-MariaDB-1:10.11.2+maria~ubu2204 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Dumping data for table `estudio_socio_economico_elemento` +-- + +LOCK TABLES `estudio_socio_economico_elemento` WRITE; +/*!40000 ALTER TABLE `estudio_socio_economico_elemento` DISABLE KEYS */; +INSERT INTO `estudio_socio_economico_elemento` VALUES (58,'Ocupación',1,1,'texto_corto',43,2,1,10,0),(59,'Teléfono de trabajo',0,2,'numerico',43,2,1,10,10),(60,'Hora de entrada',0,4,'hora',43,2,1,10,0),(61,'Hora de salida',0,5,'hora',43,2,1,10,0),(62,'Sueldo mensual',0,3,'numerico',43,2,1,10,0),(63,'Actualmente Vives con:',1,1,'opcion_multiple',43,6,0,10,0),(64,'Años viviendo ahí',1,2,'numerico',43,6,1,10,0),(65,'Personas viviendo contigo',1,3,'numerico',43,6,1,10,0),(66,'Vivienda y transporte',1,1,'separador',43,4,1,10,0),(67,'La casa donde vives es:',1,1,'opcion_multiple',43,8,1,10,0),(68,'El material del piso es:',1,2,'opcion_multiple',43,8,1,10,0),(72,'¿Cuántas recámaras tiene?',1,3,'numerico',43,8,1,10,0),(73,'¿Cuántos baños tiene?',1,1,'numerico',43,10,1,10,0),(74,'¿Tiene sala?',1,2,'opcion_multiple',43,10,0,10,0),(75,'¿Tiene cocina independiente?',1,3,'opcion_multiple',43,10,0,10,0),(76,'¿Cuántos autos tiene?',1,4,'numerico',43,10,1,10,0),(77,'¿con que servicios Cuenta?',1,1,'casillas',43,12,0,10,0),(78,'En tu casa cuentas con:',1,1,'casillas',43,14,0,10,0),(79,'¿Cuentas con seguro de gastos Médicos?',1,1,'opcion_multiple',43,16,0,10,0),(80,'¿Qué transporte utilizas?',1,2,'opcion_multiple',43,16,1,10,0),(81,'Nombre completo',1,1,'texto_corto',44,2,1,10,0),(82,'Parentesco',1,2,'desplegable',44,2,1,10,0),(83,'Estado civil',1,3,'desplegable',44,2,0,10,0),(84,'Edad (años)',1,1,'numerico',44,4,1,3,0),(85,'sexo',1,2,'opcion_multiple',44,4,0,10,0),(86,'Escolaridad',1,3,'desplegable',44,4,0,10,0),(87,'¿Termino la carrera?',1,4,'opcion_multiple',44,4,0,10,0),(88,'¿Percibe algún ingreso?',1,1,'opcion_multiple',44,6,0,10,0),(89,'Ocupación',0,2,'desplegable',44,6,0,10,0),(90,'Lugar de Trabajo',0,3,'texto_corto',44,6,1,10,0),(91,'Ingreso mensual',0,4,'numerico',44,6,1,10,0); +/*!40000 ALTER TABLE `estudio_socio_economico_elemento` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Dumping data for table `estudio_socio_economico_opcion` +-- + +LOCK TABLES `estudio_socio_economico_opcion` WRITE; +/*!40000 ALTER TABLE `estudio_socio_economico_opcion` DISABLE KEYS */; +INSERT INTO `estudio_socio_economico_opcion` VALUES (1,'Otro',Null,1),(26,'Padres',63,2),(27,'Amigos',63,4),(28,'Familiares',63,3),(29,'Esposo(a)',63,5),(30,'Propia',67,100000),(31,'Rentada',67,100000),(32,'Casa de huéspedes',67,100000),(33,'Tierra',68,100000),(34,'Alfombra',68,100000),(35,'Madera',68,100000),(36,'Duela',68,100000),(37,'Cemento',68,100000),(39,'Mosaico',68,100000),(48,'Si',74,100000),(49,'No',74,100000),(50,'Si',75,100000),(51,'No',75,100000),(52,'Agua',77,100000),(53,'Luz',77,100000),(54,'Drenaje',77,100000),(55,'Pavimiento',77,100000),(56,'Télefono',77,100000),(57,'Gas',77,100000),(58,'TV por cable',77,100000),(59,'Internet',77,100000),(60,'DVD',78,100000),(61,'Televisión',78,100000),(62,'Estufa',78,100000),(63,'Licuadora',78,100000),(64,'Lavadora',78,100000),(65,'Estéreo',78,100000),(66,'Microondas',78,100000),(67,'Computadora',78,100000),(68,'Si',79,100000),(69,'No',79,100000),(70,'Auto propio',80,100000),(71,'Auto familiar',80,100000),(72,'Motocicleta',80,100000),(73,'Camión',80,100000),(74,'Taxi',80,100000),(75,'Caminando',80,100000),(76,'Padre',82,100000),(77,'Madre',82,100000),(78,'Hermano(a)',82,100000),(79,'Hijo(a)',82,100000),(80,'Abuelo(a)',82,100000),(81,'Tío(a)',82,100000),(82,'Tutor(a)',82,100000),(83,'Esposo(a)',82,100000),(84,'Soltero(a)',83,100000),(85,'Casado(a)',83,100000),(86,'Divorciado(a)',83,100000),(87,'Viudo(a)',83,100000),(88,'Hombre',85,100000),(89,'Mujer',85,100000),(90,'Ninguno',86,100000),(91,'Primaria',86,100000),(92,'Secuandaria',86,100000),(93,'Preparatoria',86,100000),(94,'Carrera técnica',86,100000),(95,'Licenciatura',86,100000),(96,'Maestria',86,100000),(97,'Posgrado',86,100000),(98,'Si',87,100000),(99,'No',87,100000),(100,'Si',88,100000),(101,'No',88,100000),(102,'Estudiante',89,100000),(103,'Hogar',89,100000),(104,'Comerciante',89,100000),(105,'Jubilado / Pensionado',89,100000),(106,'Obrero',89,100000),(107,'Técnico',89,100000),(108,'Profesionista',89,100000),(109,'Empleado',89,100000); +/*!40000 ALTER TABLE `estudio_socio_economico_opcion` ENABLE KEYS */; +UNLOCK TABLES; + +-- +-- Dumping data for table `estudio_socio_economico_seccion` +-- + +LOCK TABLES `estudio_socio_economico_seccion` WRITE; +/*!40000 ALTER TABLE `estudio_socio_economico_seccion` DISABLE KEYS */; +INSERT INTO `estudio_socio_economico_seccion` VALUES (43,'Datos Socioeconómicos','unico',2),(44,'Datos familiares (Deben ser todos con los que vives)','agregacion',100000); +/*!40000 ALTER TABLE `estudio_socio_economico_seccion` ENABLE KEYS */; +UNLOCK TABLES; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2023-08-27 20:34:27 diff --git a/estudio_socio_economico/admin.py b/estudio_socio_economico/admin.py index f5d3b9a4f9b68cbac665ccc9968cfe3174d21546..d6e12f78bfca2de0e1dbd8790cf3a65e822d97e1 100644 --- a/estudio_socio_economico/admin.py +++ b/estudio_socio_economico/admin.py @@ -1,16 +1,22 @@ from django.contrib import admin -from .models import Seccion, Opcion, Respuesta, RTextoCorto, RTextoParrafo, ROpcionMultiple, RCasillas, RDesplegable, Elemento +from .models import Seccion, Opcion, RAgregacion, Respuesta, RTextoCorto, RTextoParrafo, ROpcionMultiple, RCasillas, RDesplegable, RNumerico, RHora, RFecha , Elemento # Register your models here. class ElementoAdmin(admin.ModelAdmin): list_display = ('nombre', 'tipo', 'row', 'col') + + admin.site.register(Seccion) admin.site.register(Opcion) +admin.site.register(RAgregacion) admin.site.register(Respuesta) admin.site.register(RTextoCorto) admin.site.register(RTextoParrafo) admin.site.register(ROpcionMultiple) admin.site.register(RCasillas) admin.site.register(RDesplegable) +admin.site.register(RNumerico) +admin.site.register(RHora) +admin.site.register(RFecha) admin.site.register(Elemento, ElementoAdmin) \ No newline at end of file diff --git a/estudio_socio_economico/forms.py b/estudio_socio_economico/forms.py index b1d1d47d88f567cb135a15d7a3712cd8ac5f3699..bcb4ffbfa4018799b8a33fb50a0e3c5359ac90b1 100644 --- a/estudio_socio_economico/forms.py +++ b/estudio_socio_economico/forms.py @@ -1,7 +1,8 @@ from django import forms from .models import Seccion, Opcion, Elemento from django.forms import inlineformset_factory, modelformset_factory - +from .models import RNumerico, RTextoCorto, RTextoParrafo, RHora, RFecha, ROpcionMultiple, RCasillas, RDesplegable, Respuesta +from django.core.validators import MinLengthValidator, MaxLengthValidator class SeccionForm(forms.ModelForm): @@ -10,7 +11,7 @@ class SeccionForm(forms.ModelForm): fields = ['nombre', 'tipo', 'orden'] widgets = { 'nombre': forms.TextInput(attrs={'class': 'form-control form-control-lg font-semi-bold border-3'}), - 'tipo': forms.RadioSelect(attrs={'class': 'ms-3'}), + 'tipo': forms.RadioSelect(attrs={'class': 'form-check-input checkbox-principal'}), 'orden': forms.NumberInput(attrs={'class': 'form-control', 'type': 'hidden'}), } labels = { @@ -27,7 +28,7 @@ class OpcionForm(forms.ModelForm): fields = ['elemento', 'nombre', 'orden'] widgets = { 'elemento': forms.Select(attrs={'class': 'form-control'}), - 'nombre': forms.TextInput(attrs={'class': 'opcion-text form-control border-3 p-0 m-0', 'onkeyup': 'updateOpcionInputSize(this)', 'onkeydown': 'updateOpcionInputSize(this)'}), + 'nombre': forms.TextInput(attrs={'class': 'opcion-text form-control border-3 p-0 m-0', 'onkeyup': 'updateOpcionInputSize(this)', 'onkeydown': 'updateOpcionInputSize(this)', 'oninput': 'detectarTerminarEscritura(this)'}), 'orden': forms.NumberInput(attrs={'class': 'form-control', 'type': 'hidden'}), } labels = { @@ -41,12 +42,14 @@ class OpcionForm(forms.ModelForm): class ElementoForm(forms.ModelForm): class Meta: model = Elemento - fields = ['seccion', 'nombre', 'obligatorio', 'opcionOtro', 'row', 'col', 'tipo',] + fields = ['seccion', 'nombre', 'obligatorio', 'opcionOtro', 'numMin', 'numMax', 'row', 'col', 'tipo',] widgets = { 'seccion': forms.Select(attrs={'class': 'form-control form-control-sm border-3', 'placeholder': 'Selecciona una sección'}), 'nombre': forms.TextInput(attrs={'class': 'form-control form-control-sm font-semi-bold border-3 ', 'placeholder': 'Nombre de la pregunta'}), 'obligatorio': forms.CheckboxInput(attrs={'class': 'checkbox-principal form-check-input'}), 'opcionOtro': forms.CheckboxInput(attrs={'class': 'checkbox-principal form-check-input', 'title': 'Habilitar Opción Otro', 'data-bs-original-title': 'Habilitar Opción Otro'}), + 'numMin': forms.NumberInput(attrs={'class': 'opcion-digit form-control form-control-sm border-3' }), + 'numMax': forms.NumberInput(attrs={'class': 'opcion-digit form-control form-control-sm border-3' }), 'row': forms.NumberInput(attrs={'class': 'form-control form-control-sm border-3', 'type': 'hidden'}), 'col': forms.NumberInput(attrs={'class': 'form-control form-control-sm border-3', 'type': 'hidden'}), 'tipo': forms.Select(attrs={'class': 'form-select form-select-sm border-3 elem-tipo-select', 'onchange': 'toggleElementoOpciones(this)', 'onload': 'toggleElementoOpciones(this)', 'placeholder': 'Tipo de pregunta'}), @@ -56,6 +59,8 @@ class ElementoForm(forms.ModelForm): 'nombre': 'Nombre de la Pregunta', 'obligatorio': 'Obligatorio', 'opcionOtro': 'Opc. Otro', + 'numMin': 'Min. Dígitos', + 'numMax': 'Max. Dígitos', 'row': 'row', 'col': 'col', 'tipo': 'Tipo de Pregunta', @@ -65,3 +70,220 @@ SeccionFormSet = modelformset_factory(Seccion, form=SeccionForm, extra=1, can_de ElementoFormSet = inlineformset_factory(Seccion, Elemento, form=ElementoForm, extra=1, can_delete=True) OpcionFormSet = inlineformset_factory(Elemento, Opcion, form=OpcionForm, extra=1, can_delete=True) +class RespuestaForm(forms.ModelForm): + class Meta: + model = Respuesta + fields = [] + + def __init__(self, *args, **kwargs): + elemento = kwargs.pop("elemento", None) + solicitante = kwargs.pop("solicitante", None) + super().__init__(*args, **kwargs) + if elemento and solicitante: + self.instance.solicitante = solicitante + self.instance.elemento = elemento + + if self.instance and hasattr(self.instance, 'elemento') and self.fields: + elemento_nombre = self.instance.elemento.nombre + first_field_name = list(self.fields.keys())[0] + self.fields[first_field_name].widget.attrs['placeholder'] = elemento_nombre + + '''# Recorre los campos del formulario + for field_name, field in self.fields.items(): + # Verifica si el campo es una ManyToManyField o ForeignKey + if isinstance(field, forms.ModelMultipleChoiceField) or isinstance(field, forms.ModelChoiceField): + # Establece las opciones como una lista vacía + field.choices = [] #''' + + + """def add_prefix(self, field_name): + field_name = super(RespuestaForm, self).add_prefix(field_name) + return self.prefix """ + + +class RNumericoForm(RespuestaForm): + class Meta: + model = RNumerico + fields = ['valor'] + widgets = {'valor': forms.TextInput(attrs={'class': 'form-control border-3', 'onkeypress': "return isNumberPuntKey(event)"}),} + labels = {'valor': 'Respuesta numérica'} + + def __init__(self, *args, **kwargs): + super(RNumericoForm, self).__init__(*args, **kwargs) + + # Accede a la instancia de elemento y obtén los valores numMin y numMax + elemento = self.instance.elemento if self.instance else None + numMin = elemento.numMin if elemento else None + numMax = elemento.numMax if elemento else None + + # Configura validadores de longitud mínima y máxima en el campo 'valor' + if numMin is not None: + self.fields['valor'].validators.append(MinLengthValidator(numMin)) + self.fields['valor'].widget.attrs['minlength'] = numMin + if numMax is not None: + self.fields['valor'].validators.append(MaxLengthValidator(numMax)) + self.fields['valor'].widget.attrs['maxlength'] = numMax + + + def clean_valor(self): + obligatorio = self.instance.elemento.obligatorio + r = self.cleaned_data.get('valor') + if (not r or not r.strip()) and obligatorio: + raise forms.ValidationError("Este campo es Obligatorio.") + return r + + +class RTextoCortoForm(RespuestaForm): + class Meta: + model = RTextoCorto + fields = ['texto'] + widgets = {'texto': forms.TextInput(attrs={'class': 'form-control border-3'}),} + labels = {'texto': 'Respuesta texto corto'} + + def clean_texto(self): + obligatorio = self.instance.elemento.obligatorio + r = self.cleaned_data.get('texto') + if (not r or not r.strip()) and obligatorio: + raise forms.ValidationError("Este campo es Obligatorio.") + return r + + +class RTextoParrafoForm(RespuestaForm): + class Meta: + model = RTextoParrafo + fields = ['texto'] + widgets = {'texto': forms.Textarea(attrs={'class': 'form-control border-3'}),} + labels = {'texto': 'Respuesta párrafo'} + + def clean_texto(self): + obligatorio = self.instance.elemento.obligatorio + r = self.cleaned_data.get('texto') + if (not r or not r.strip()) and obligatorio: + raise forms.ValidationError("Este campo es Obligatorio.") + return r + + +class RHoraForm(RespuestaForm): + class Meta: + model = RHora + fields = ['hora'] + widgets = {'hora': forms.TimeInput(attrs={'class': 'form-control border-3', 'type': 'time'}),} + labels = {'hora': 'Hora'} + + def clean_hora(self): + obligatorio = self.instance.elemento.obligatorio + r = self.cleaned_data.get('hora') + if (not r) and obligatorio: + raise forms.ValidationError("Este campo es Obligatorio.") + return r + + +class RFechaForm(RespuestaForm): + class Meta: + model = RFecha + fields = ['fecha'] + widgets = {'fecha': forms.DateInput(format=('%Y-%m-%d'), attrs={'class': 'form-control border-3', 'type': 'date'}),} + labels = {'fecha': 'Fecha'} + + def clean_fecha(self): + obligatorio = self.instance.elemento.obligatorio + r = self.cleaned_data.get('fecha') + if (not r) and obligatorio: + raise forms.ValidationError("Este campo es Obligatorio.") + return r + + +class ROpcionMultipleForm(RespuestaForm): + class Meta: + model = ROpcionMultiple + fields = ['respuesta', 'otro'] + widgets = {'respuesta': forms.RadioSelect(attrs={'class': 'form-check-input checkbox-principal', 'onchange': 'toggleOtroCampo(this)'}), + 'otro': forms.TextInput(attrs={'class': 'form-control border-3', 'placeholder': 'Otro'}),} + labels = {'respuesta': 'Respuesta de opción múltiple', 'otro': 'Otro'} + + def clean(self): + cleaned_data = super().clean() + obligatorio = self.instance.elemento.obligatorio + opcOtro = self.instance.elemento.opcionOtro + respuesta = cleaned_data.get('respuesta') + otro = cleaned_data.get('otro') + if obligatorio: + if not ((opcOtro and (otro and otro.strip())) or not opcOtro) and (respuesta and respuesta.nombre == 'Otro'): + raise forms.ValidationError("Este campo es obligatorio") + if (otro and otro.strip()) and (respuesta and not respuesta.nombre == 'Otro'): + raise forms.ValidationError("No esta seleccionada opcion Otro") + if respuesta and (respuesta.nombre == 'Otro' and not( otro and otro.strip())): + raise forms.ValidationError("Si eliges 'otro', debes proporcionar más detalles en el campo 'otro'.") + return cleaned_data + + + def clean_respuesta(self): + respuesta = self.cleaned_data.get('respuesta') + obligatorio = self.instance.elemento.obligatorio + if (not respuesta) and obligatorio: + raise forms.ValidationError("Este campo es Obligatorio.") + return respuesta + + +class RCasillasForm(RespuestaForm): + class Meta: + model = RCasillas + fields = ['respuesta', 'otro'] + widgets = {'respuesta': forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input checkbox-principal form-check-inline-custom', 'style': 'display: inline-block;', 'onchange': 'toggleOtroCampo(this)'}), + 'otro': forms.TextInput(attrs={'class': 'form-control border-3', 'placeholder': 'Otro'}),} + labels = {'respuestas': 'Casillas', 'otro': 'Otro'} + + def clean(self): + cleaned_data = super().clean() + obligatorio = self.instance.elemento.obligatorio + opcOtro = self.instance.elemento.opcionOtro + respuesta = cleaned_data.get('respuesta') + otro = cleaned_data.get('otro') + if obligatorio: + if not ((opcOtro and (otro and otro.strip())) or not opcOtro) and (respuesta and respuesta.filter(nombre='Otro').exists()): + raise forms.ValidationError("Este campo es obligatorio") + if (otro and otro.strip()) and (respuesta and not respuesta.filter(nombre='Otro').exists()): + raise forms.ValidationError("No esta seleccionada opcion Otro") + if respuesta and (respuesta.filter(nombre='Otro').exists() and not( otro and otro.strip())): + raise forms.ValidationError("Si eliges 'otro', debes proporcionar más detalles en el campo 'otro'.") + return cleaned_data + + def clean_respuesta(self): + respuesta = self.cleaned_data.get('respuesta') + noRespuesta = respuesta.count() == 0 + obligatorio = self.instance.elemento.obligatorio + if (noRespuesta) and obligatorio: + raise forms.ValidationError("Este campo es Obligatorio.") + return respuesta + + +class RDesplegableForm(RespuestaForm): + class Meta: + model = RDesplegable + fields = ['respuesta', 'otro'] + widgets = {'respuesta': forms.Select(attrs={'class': 'form-control form-select border-3', 'onchange': 'toggleOtroCampo(this)'}), + 'otro': forms.TextInput(attrs={'class': 'form-control border-3', 'placeholder': 'Otro'}),} + labels = {'respuesta': 'Respuesta de desplegable', 'otro': 'Otro'} + + def clean(self): + cleaned_data = super().clean() + obligatorio = self.instance.elemento.obligatorio + opcOtro = self.instance.elemento.opcionOtro + respuesta = cleaned_data.get('respuesta') + otro = cleaned_data.get('otro') + if obligatorio: + if not ((opcOtro and (otro and otro.strip())) or not opcOtro) and (respuesta and respuesta.nombre == 'Otro'): + raise forms.ValidationError("Este campo es obligatorio") + if (otro and otro.strip()) and (respuesta and not respuesta.nombre == 'Otro'): + raise forms.ValidationError("No esta seleccionada opcion Otro") + if respuesta and (respuesta.nombre == 'Otro' and not( otro and otro.strip())): + raise forms.ValidationError("Si eliges 'otro', debes proporcionar más detalles en el campo 'otro'.") + return cleaned_data + + + def clean_respuesta(self): + respuesta = self.cleaned_data.get('respuesta') + obligatorio = self.instance.elemento.obligatorio + if (not respuesta) and obligatorio: + raise forms.ValidationError("Este campo es Obligatorio.") + return respuesta diff --git a/estudio_socio_economico/models.py b/estudio_socio_economico/models.py index 8285d4fd24dda04c767990e53e642237356c5ac6..417226774b10d4c617bafc89b3aeb2ba23483fe3 100644 --- a/estudio_socio_economico/models.py +++ b/estudio_socio_economico/models.py @@ -1,4 +1,4 @@ -from django.db import models +from django.db import models, IntegrityError from django.contrib.auth.models import User from django.core.validators import RegexValidator from django.core.validators import MinLengthValidator, MaxLengthValidator @@ -6,11 +6,12 @@ from django.db import models from model_utils.managers import InheritanceManager from usuarios.models import Solicitante import uuid +from django.core.exceptions import ValidationError class Seccion(models.Model): TIPOS_CHOICES = ( - ('único', 'Único'), - ('agregación', 'Agregación'), + ('unico', 'Único'), + ('agregacion', 'Agregación'), ) nombre = models.CharField(max_length=255) @@ -25,7 +26,7 @@ class Seccion(models.Model): class Opcion(models.Model): - elemento = models.ForeignKey('Elemento', on_delete=models.CASCADE) + elemento = models.ForeignKey('Elemento', on_delete=models.CASCADE, null=True, blank=True) nombre = models.CharField(max_length=255, verbose_name='Nombre Opción') orden = models.IntegerField(verbose_name='orden', default=100_000) @@ -40,7 +41,25 @@ class Opcion(models.Model): #----------Respuestas------------- +class CustomIntegerField(models.IntegerField): + def __init__(self, *args, min_digits=None, max_digits=None, **kwargs): + self.min_digits = min_digits + self.max_digits = max_digits + super().__init__(*args, **kwargs) + + def validate(self, value, model_instance): + super().validate(value, model_instance) + if self.min_digits is not None and len(str(value)) < self.min_digits: + raise ValidationError(f"El valor debe tener almenos {self.min_digits} digitos.") + if self.max_digits is not None and len(str(value)) > self.max_digits: + raise ValidationError(f"El valor debe tener a lo mucho {self.max_digits} digitos.") + +class RAgregacion(models.Model): + # El modelo solo contiene el campo de identificación (ID) de forma predeterminada + pass + class Respuesta(models.Model): + rAgregacion = models.ForeignKey(RAgregacion, on_delete=models.CASCADE, null=True, blank=True) elemento = models.ForeignKey('Elemento', on_delete=models.CASCADE) solicitante = models.ForeignKey(Solicitante, on_delete=models.CASCADE) otro = None @@ -48,46 +67,108 @@ class Respuesta(models.Model): objects = InheritanceManager() def __str__(self): - return f"Respuesta - Elemento: {self.elemento} - Solicitante: {self.solicitante}" + return f"Respuesta {type(self)} - Elemento: {self.elemento} - Solicitante: {self.solicitante_id}" + + def save(self, *args, **kwargs): + if self._state.adding: + # Solo realiza la verificación si estás creando una respuesta nueva + if self.elemento.seccion.tipo == 'unico': + # Verificar si ya existe una respuesta para esta combinación + if Respuesta.objects.filter(elemento=self.elemento, solicitante=self.solicitante).exists(): + raise IntegrityError('Ya existe una respuesta para este elemento y solicitante') + super().save(*args, **kwargs) - class Meta: + class Meta: verbose_name = 'Respuesta' - verbose_name_plural = 'Respuestas' - unique_together = ['elemento', 'solicitante'] + verbose_name_plural = 'Respuestas' + + def getStringValue(self): + return 'Respuesta no Implementado' + + +class RNumerico(Respuesta): + valor = models.CharField(max_length=255, null=True, blank=True) + + def getStringValue(self): + return str(self.valor) class RTextoCorto(Respuesta): - texto = models.CharField(max_length=255) + texto = models.CharField(max_length=255, null=True, blank=True) + + def getStringValue(self): + return str(self.texto) class RTextoParrafo(Respuesta): - texto = models.TextField() + texto = models.TextField(null=True, blank=True) + def getStringValue(self): + return str(self.texto) -class ROpcionMultiple(Respuesta): - respuesta = models.ForeignKey(Opcion, on_delete=models.CASCADE) - otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=False) +class RHora(Respuesta): + hora = models.TimeField(null=True, blank=True) -class RCasillas(Respuesta): - respuestas = models.ManyToManyField(Opcion) - otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=False) + def getStringValue(self): + return str(self.hora) -class RDesplegable(Respuesta): - respuesta = models.ForeignKey(Opcion, on_delete=models.CASCADE) - otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=False) +class RFecha(Respuesta): + fecha = models.DateField(null=True, blank=True) + def getStringValue(self): + return str(self.fecha) + +class ROpcionMultiple(Respuesta): + respuesta = models.ForeignKey(Opcion, on_delete=models.CASCADE, null=True, blank=True) + otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=True) + def getStringValue(self): + if self.respuesta and self.respuesta.nombre == 'Otro': + return str(self.respuesta)+': '+str(self.otro) + else : + return str(self.respuesta) + +class RCasillas(Respuesta): + respuesta = models.ManyToManyField(Opcion, blank=True) + otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=True) + + def getStringValue(self): + string = '' + objs = self.respuesta.all() + for i, obj in enumerate(objs): + string += str(obj) + if obj.nombre == 'Otro': + string += ': '+ str(self.otro) + if i < len(objs) - 1: + string += ', ' + + return string + + +class RDesplegable(Respuesta): + respuesta = models.ForeignKey(Opcion, on_delete=models.CASCADE, null=True, blank=True) + otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=True) + + def getStringValue(self): + if self.respuesta and self.respuesta.nombre == 'Otro': + return str(self.respuesta)+': '+str(self.otro) + else : + return str(self.respuesta) + #---------Elementos----------- class Elemento(models.Model): TIPO_CHOICES = ( ('separador', 'Separador'), + ('numerico', 'Numérico'), ('texto_corto', 'Texto Corto'), ('texto_parrafo', 'Texto Párrafo'), + ('hora', 'Hora'), + ('fecha', 'Fecha'), ('opcion_multiple', 'Opción Múltiple'), ('casillas', 'Casillas'), ('desplegable', 'Desplegable'), @@ -97,6 +178,8 @@ class Elemento(models.Model): nombre = models.CharField(max_length=255, verbose_name='Nombre') obligatorio = models.BooleanField(default=True, verbose_name='Obligatorio') opcionOtro = models.BooleanField(default=True, verbose_name='Opcion Otro') + numMin = models.PositiveIntegerField(verbose_name='numMin', default=0) + numMax = models.PositiveIntegerField(verbose_name='numMax', default=10) row = models.IntegerField(verbose_name='row', default=100_000) col = models.IntegerField(verbose_name='col', default=100_000) tipo = models.CharField(max_length=20, choices=TIPO_CHOICES, verbose_name='Tipo') @@ -115,9 +198,22 @@ class Elemento(models.Model): return RCasillas elif self.tipo == 'desplegable': return RDesplegable + elif self.tipo == 'numerico': + return RNumerico + elif self.tipo == 'hora': + return RHora + elif self.tipo == 'fecha': + return RFecha else: return None + def crearRespuesta(self, solicitante): + respuesta_model = self.getRespuestaModel() + if respuesta_model: + return respuesta_model.objects.create(elemento=self, solicitante=solicitante) + else : + return None + class Meta: verbose_name = 'Elemento' verbose_name_plural = 'Elementos' diff --git a/estudio_socio_economico/templates/admin/config_estudioSE.html b/estudio_socio_economico/templates/admin/config_estudioSE.html index a8c1fbecd8b85c75a2f6dcb13b4b330972b362bf..536527a5cf42cc57ea41df4c26bbbc3130ca4f30 100644 --- a/estudio_socio_economico/templates/admin/config_estudioSE.html +++ b/estudio_socio_economico/templates/admin/config_estudioSE.html @@ -6,831 +6,56 @@ {% block body %} {% load custom_filters %} - + {% include 'admin/config_nav.html' %}

Modificar Estudio Socioeconomico

-
- {% csrf_token %} - -
- - {{seccionFormset.management_form}} - {% for seccion in seccionFormset %} - {% with pDiv=seccion.prefix|splitPop:'-' %} -
- - -
- -
-
- {% for hidden in seccion.hidden_fields %} - {{ hidden }} -
- {{ hidden.errors }} -
- {% endfor %} -
- {{ seccion.nombre.label }} - {{ seccion.nombre }} -
- {{ seccion.nombre.errors }} - {{ seccion.non_field_errors }} -
-
-
-
- {{ seccion.tipo.label }} -
-
- {% for choice in seccion.tipo %} - {% if forloop.counter0 > 0 %} - {{ choice }} - {% else %} -
- {{ choice }} -
- {% endif %} - {% endfor %} -
-
- {{ seccion.tipo.errors }} -
-
-
- -
-
- - - {% with elementoFormSet=dictElemForm|hashD:seccion.prefix %} - {% if elementoFormSet %} - {{ elementoFormSet.management_form}} - - {% with first=elementoFormSet.0 %} - {% if first %} -
- - - {% for elemento in elementoFormSet%} - {% with eID=forloop.counter0 %} - -
- -
-
- -
- {% for hidden in elemento.hidden_fields %} - {{ hidden }} -
- {{ hidden.errors }} -
- {% endfor %} -
-
- {{ elemento.nombre }} -
- {{ elemento.nombre.errors }} - {{ elemento.non_field_errors }} -
-
-
- -
-
-
-
- {{ elemento.tipo }} -
- {{ elemento.tipo.errors }} -
-
-
- {{ elemento.obligatorio.label }} -
- {% for choice in elemento.obligatorio %} - {{ choice }} - {% endfor %} -
-
- {{ elemento.obligatorio.errors }} -
-
-
- {{ elemento.opcionOtro.label }} -
- {% for choice in elemento.opcionOtro %} - {{ choice }} - {% endfor %} -
-
- {{ elemento.opcionOtro.errors }} -
-
-
- -
-
-
-

Opciones

-
- {% with opcionFormSet=dictOpcionForm|hashD:elemento.prefix %} - {% if opcionFormSet %} - - {{ opcionFormSet.management_form}} - {% for opcion in opcionFormSet%} - {% with oID=forloop.counter0 %} -
-
- -
- {% for hidden in opcion.hidden_fields %} - {{ hidden }} -
- {{ hidden.errors }} -
- {% endfor %} - {{ opcion.nombre }} -
- {{ opcion.nombre.errors }} - {{ opcion.non_field_errors }} -
-
- -
-
- {% endwith %} - {% endfor %} - {% endif %} - {% endwith %} -
- -
-
- -
- {% endwith %} - {% with c=elementoFormSet|hashD:forloop.counter %} - {% with cb=elementoFormSet|hashD:forloop.counter0 %} - {% if c.row.value != cb.row.value and c is not None %} -
-
- - {% endif %} - {% endwith %} - {% endwith %} - - {% endfor %} -
- {% with ultimo=elementoFormSet|last %} - - {% endwith %} - -
- -
- {% else %} -
- -
- {% endif %} - {% endwith %} - {% endif %} - {% endwith %} -
- {% endwith%} - {% endfor %} -
- -
+
+
+
+
+ Cargando...
- - - - {% endblock body %} \ No newline at end of file diff --git a/estudio_socio_economico/templates/admin/config_estudioSEForm.html b/estudio_socio_economico/templates/admin/config_estudioSEForm.html new file mode 100644 index 0000000000000000000000000000000000000000..5dd1feca24fe26801e86155f799e2440989078f2 --- /dev/null +++ b/estudio_socio_economico/templates/admin/config_estudioSEForm.html @@ -0,0 +1,897 @@ +{% load custom_filters %} + + + +
+ {% csrf_token %} + +
+ + {{seccionFormset.management_form}} + {% for seccion in seccionFormset %} + {% with pDiv=seccion.prefix|splitPop:'-' %} +
+ + +
+ +
+
+ {% for hidden in seccion.hidden_fields %} + {{ hidden }} +
+ {{ hidden.errors }} +
+ {% endfor %} +
+ {{ seccion.nombre.label }} + {{ seccion.nombre }} +
+ {{ seccion.nombre.errors }} + {{ seccion.non_field_errors }} +
+
+
+
+ {{ seccion.tipo.label }} +
+
+ {% for choice in seccion.tipo %} + {% if forloop.counter0 > 0 %} + {{ choice }} + {% else %} +
+ {{ choice }} +
+ {% endif %} + {% endfor %} +
+
+ {{ seccion.tipo.errors }} +
+
+
+ +
+
+ + + {% with elementoFormSet=dictElemForm|hashD:seccion.prefix %} + {% if elementoFormSet %} + {{ elementoFormSet.management_form}} + + {% with first=elementoFormSet.0 %} + {% if first %} +
+ + + {% for elemento in elementoFormSet%} + {% with eID=forloop.counter0 %} + +
+ +
+
+ +
+ {% for hidden in elemento.hidden_fields %} + {{ hidden }} +
+ {{ hidden.errors }} +
+ {% endfor %} +
+
+ {{ elemento.nombre }} +
+ {{ elemento.nombre.errors }} + {{ elemento.non_field_errors }} +
+
+
+ +
+
+
+
+ {{ elemento.tipo }} +
+ {{ elemento.tipo.errors }} +
+
+
+
+
+ {{ elemento.numMin.label }} + {{ elemento.numMin }} + {{ elemento.numMin.errors }} +
+
+ {{ elemento.numMax.label }} + {{ elemento.numMax }} + {{ elemento.numMax.errors }} +
+
+
+
+ {{ elemento.obligatorio.label }} +
+ {% for choice in elemento.obligatorio %} + {{ choice }} + {% endfor %} +
+
+ {{ elemento.obligatorio.errors }} +
+
+
+ {{ elemento.opcionOtro.label }} +
+ {% for choice in elemento.opcionOtro %} + {{ choice }} + {% endfor %} +
+
+ {{ elemento.opcionOtro.errors }} +
+
+
+ +
+
+
+

Opciones

+
+ {% with opcionFormSet=dictOpcionForm|hashD:elemento.prefix %} + {% if opcionFormSet %} + + {{ opcionFormSet.management_form}} + {% for opcion in opcionFormSet%} + {% with oID=forloop.counter0 %} +
+
+ +
+ {% for hidden in opcion.hidden_fields %} + {{ hidden }} +
+ {{ hidden.errors }} +
+ {% endfor %} + {{ opcion.nombre }} +
+ {{ opcion.nombre.errors }} + {{ opcion.non_field_errors }} +
+
+ +
+
+ {% endwith %} + {% endfor %} + {% endif %} + {% endwith %} +
+ +
+
+ +
+ {% endwith %} + {% with c=elementoFormSet|hashD:forloop.counter %} + {% with cb=elementoFormSet|hashD:forloop.counter0 %} + {% if c.row.value != cb.row.value and c is not None %} +
+
+ + {% endif %} + {% endwith %} + {% endwith %} + + {% endfor %} +
+ {% with ultimo=elementoFormSet|last %} + + {% endwith %} + +
+ +
+ {% else %} +
+ +
+ {% endif %} + {% endwith %} + {% endif %} + {% endwith %} +
+ {% endwith%} + {% endfor %} +
+ +
+
+
+ + \ No newline at end of file diff --git a/estudio_socio_economico/templates/solicitante/estudioSE.html b/estudio_socio_economico/templates/solicitante/estudioSE.html new file mode 100644 index 0000000000000000000000000000000000000000..212fc5bc679d085a53ed62d6507bc1c11ed8bd2a --- /dev/null +++ b/estudio_socio_economico/templates/solicitante/estudioSE.html @@ -0,0 +1,125 @@ +{% extends 'base.html' %} +{% load custom_filters %} + +{% block body %} + + + +
+
+

Estudio Socioeconómico

+
+ +
+
+ {% csrf_token %} + + {% include 'solicitante/formularioBase.html' %} + +
+ + + +{% endblock body %} \ No newline at end of file diff --git a/estudio_socio_economico/templates/solicitante/formularioBase.html b/estudio_socio_economico/templates/solicitante/formularioBase.html new file mode 100644 index 0000000000000000000000000000000000000000..c6892949a6790ba93591e2012d62e13c5f4ce37e --- /dev/null +++ b/estudio_socio_economico/templates/solicitante/formularioBase.html @@ -0,0 +1,146 @@ +{% load custom_filters %} + +{% if mensajes %} + {% include mensajes %} +{% endif %} + +{% for seccion in preguntasEstudio %} + {% if seccion.tipo == 'agregacion' %} +
+ {% endif %} +
+

{{ seccion.nombre }}

+
+ {% for elemento in seccion.elemento_set.all %} + {% if elemento.tipo == 'separador' %} +
+
+ {{ elemento.nombre }} +
+
+ {% else %} + {% with forms|hashD:elemento.id as form %} +
+
+ {{ elemento.nombre }} + {% if elemento.obligatorio %} + * + {% endif %} +
+ {% for field in form %} + + {% if elemento.opcionOtro and field.label == 'Otro' %} + + {% elif field.label == 'Otro' %} + + {% else %} + {% if elemento.tipo == 'opcion_multiple' or elemento.tipo == 'casillas' %} +
+
+ {% for choice in field %} +
+ {{ choice }} +
+ {% endfor %} +
+
+ {% else %} +
+ {{ field }} +
+ {% endif %} + {% endif %} + +
+ {% if field.errors %} + {% for error in field.errors %} + {{ error }} + {% endfor %} + {% endif %} +
+ + {% endfor %} + +
+ {% for error in form.non_field_errors %} + {{ error }} + {% endfor %} +
+
+ + {% endwith %} + {% endif %} + {% with c=seccion.elemento_set.all|hashD:forloop.counter %} + {% with cb=elemento %} + {% if c.row != cb.row and c is not None %} +
+
+ {% endif %} + {% endwith %} + {% endwith %} + {% endfor %} +
+ + {% if seccion.tipo == 'agregacion' %} + {% with registrosA|hashD:seccion.id as registros %} +
+ +
+

Registros

+
+ + + + {% for elemento in seccion.elemento_set.all %} + + {% endfor %} + + + + + {% if registros %} + {% for registro in registros %} + + {% for respuest in registro%} + {% with respuest.getStringValue as valor %} + + {% endwith %} + {% endfor %} + + + {% endfor %} + {% endif %} + +
{{elemento.nombre}}
+ {{ valor }} + + +
+
+ {% endwith %} +
+
+ {% else %} +
+ {% endif %} +{% endfor %} + + \ No newline at end of file diff --git a/estudio_socio_economico/urls.py b/estudio_socio_economico/urls.py index 565565cfb1af4f42ce5f2b049bc27a84cc5a0f48..b0940b4c68bfef66087168884f5df7c63d740fa1 100644 --- a/estudio_socio_economico/urls.py +++ b/estudio_socio_economico/urls.py @@ -19,4 +19,8 @@ from . import views, viewsAdmin app_name = 'estudioSE' urlpatterns = [ path('administracion/config/estudio', viewsAdmin.configEstudio, name='AConfigEstudio'), + path('administracion/config/getStudioForm', viewsAdmin.configEstudioGetForm, name='AConfigGetEstudioForm'), + path('estudioSE/',views.estudioSE, name='estudioSE'), + path('agregarRegistroSE//',views.agregarR, name='AgregarR'), + path('eliminarRegistroSE///',views.eliminarR, name='EliminarR'), ] \ No newline at end of file diff --git a/estudio_socio_economico/views.py b/estudio_socio_economico/views.py index 27cdb63bf2c6062ddf98320c2178a012da2b8bfb..9dce496895da724c9b4a50c76c969e85a98b7d5b 100644 --- a/estudio_socio_economico/views.py +++ b/estudio_socio_economico/views.py @@ -1,4 +1,230 @@ -from django.shortcuts import render +from django.shortcuts import get_object_or_404, redirect, render +from django.contrib import messages +from django.contrib.auth import authenticate, login +from django.apps import apps +from django.contrib.auth.decorators import login_required +from django.conf import settings +from django import forms as FForms +from django.db.models import Prefetch +from usuarios.views import verificarRedirect +from usuarios.models import Usuario, Solicitante +from .models import Seccion, Elemento, Opcion, Respuesta, RAgregacion +from .forms import RNumericoForm, RTextoCortoForm, RTextoParrafoForm, RHoraForm, RFechaForm, ROpcionMultipleForm, RCasillasForm, RDesplegableForm -# Create your views here. +#logica para crear las instancias de las forms +def crearForms(forms, opcOtro, requestPost, preguntasEstudio, formModels, formModelsConOpcion, respuestas, solicitante): + for seccion in preguntasEstudio: + for elemento in seccion.elemento_set.all(): + #si la pregunta no es de una opcion de las que se tiene formulario se salta esta iteracion + if elemento.getRespuestaModel() is None: + continue + formModel = formModels[elemento.getRespuestaModel()] + esTipoUnico = seccion.tipo == Seccion.TIPOS_CHOICES[0][0] + #si ya existe una respuesta de esta pregunta + if (esTipoUnico and elemento.id in respuestas) and isinstance(respuestas[elemento.id], formModel.Meta.model): + forms[elemento.id] = formModel(requestPost, instance=respuestas[elemento.id], prefix=f'respuesta_{elemento.id}') + #si no existe una respuesta de esta pregunta + else: + #si existe una respuesta pero no coincide con el modelo de la pregunta esta se elimina para crear una nueva + if (esTipoUnico and elemento.id in respuestas): + respuestas[elemento.id].delete() + forms[elemento.id] = formModel(requestPost, elemento=elemento, solicitante=solicitante, prefix=f'respuesta_{elemento.id}') + #si son formularios de opcion se asignan las opciones + if formModel in formModelsConOpcion: + choices = [(opcion.id, opcion.nombre) for opcion in elemento.opcion_set.all()] + if isinstance(forms[elemento.id].fields['respuesta'].widget, FForms.Select): + # Agrega la opción en blanco al comienzo solo si el campo es un 'select' + choices.insert(0, ("", "---------")) + if elemento.opcionOtro : + choices.append((opcOtro.id, opcOtro.nombre) ) + forms[elemento.id].fields['respuesta'].choices = choices +@login_required +def estudioSE(request): + url = verificarRedirect(request.user) + if url: #Verifica si el usuario ha llenaodo su informacion personal por primera vez y tiene los permisos necesarios + return redirect(url) + + solicitante = get_object_or_404(Solicitante, pk=request.user.id) + #obtener los formularios del estudio SE # filter(tipo='unico', nombre='Prueba') + preguntasEstudio = Seccion.objects.all().prefetch_related('elemento_set__opcion_set') + forms = {} + opcOtro = get_object_or_404(Opcion, nombre = "Otro") + #se obtienen e indexan las respuestas existentes del usuario + respuestas = {} + respuestasExistentes = Respuesta.objects.filter(solicitante = solicitante).select_subclasses().select_related('elemento__seccion') + for respuesta in respuestasExistentes: + respuestas[respuesta.elemento_id] = respuesta + + rAgregacion =RAgregacion.objects.filter(respuesta__solicitante_id=solicitante)\ + .distinct()\ + .prefetch_related('respuesta_set') + + registrosA = {} + raSeccion = -1 + for ra in rAgregacion: + registros = ra.respuesta_set.all().select_subclasses().select_related('elemento__seccion') + first = registros.first() + if first: + sID = first.elemento.seccion_id + if sID != raSeccion: + raSeccion = sID + registrosA[raSeccion] = [] + registrosA[raSeccion].append(registros) + + ''' + for clave, lista in registrosA.items(): + print(f"Clave: {clave}") + print("Elementos de la lista:") + for elemento in lista: + print(elemento) + print() #''' + + #generando Forms + #se genera un diccionario con los tipos de forms + formModels = {} + formModelsConOpcion = [ROpcionMultipleForm, RCasillasForm, RDesplegableForm] + for formtype in [RNumericoForm, RTextoCortoForm, RTextoParrafoForm, RHoraForm, RFechaForm, ROpcionMultipleForm, RCasillasForm, RDesplegableForm]: + formModels[formtype.Meta.model] = formtype + + if request.method == 'GET': + crearForms(forms, opcOtro, None, preguntasEstudio, formModels, formModelsConOpcion, respuestas, solicitante) + + + if request.method == 'POST': + crearForms(forms, opcOtro, request.POST, preguntasEstudio, formModels, formModelsConOpcion, respuestas, solicitante) + + + #Solo se validan y guardan las respuestas de secciones de tipo 'unico', las de 'agregacion' se omiten + todoValido = True + for form in forms.values(): + tipo = form.instance.elemento.seccion.tipo + if tipo == Seccion.TIPOS_CHOICES[0][0]: + if not form.is_valid(): + todoValido = False + elif tipo == Seccion.TIPOS_CHOICES[1][0]: + form.errors.clear() + + if todoValido: + for form in forms.values(): + if form.instance.elemento.seccion.tipo == Seccion.TIPOS_CHOICES[0][0]: + form.save() + messages.success(request, 'Estudio Socioeconómico guardado con éxito') + #return redirect('estudioSE:estudioSE') + else: + for i, seccion in enumerate(preguntasEstudio): + for j, elemento in enumerate(seccion.elemento_set.all()): + if elemento.id in forms and forms[elemento.id].errors: + for error in forms[elemento.id].errors.values(): + messages.error(request, [f'Seccion {seccion.nombre}: Pregunta {elemento.nombre}', error]) + messages.warning(request, 'No se pudieron guardar los cambios: Formularios no validos') + + + context = { + 'opcOtro': opcOtro, + 'forms': forms, + 'preguntasEstudio': preguntasEstudio, + 'registrosA': registrosA, + } + + + return render(request, 'solicitante/estudioSE.html', context) + + + +@login_required +def agregarR(request, seccionID): + url = verificarRedirect(request.user) + if url: #Verifica si el usuario ha llenaodo su informacion personal por primera vez y tiene los permisos necesarios + return redirect(url) + + solicitante = get_object_or_404(Solicitante, pk=request.user.id) + #obtener los formularios del estudio SE # filter(tipo='unico', nombre='Prueba') + preguntasEstudio = Seccion.objects.filter(id = seccionID).prefetch_related('elemento_set__opcion_set') + forms = {} + opcOtro = get_object_or_404(Opcion, nombre = "Otro") + #se obtienen e indexan las respuestas existentes del usuario + respuestas = {} + + rAgregacion =RAgregacion.objects.filter(respuesta__solicitante_id=solicitante, respuesta__elemento__seccion_id=seccionID)\ + .distinct()\ + .prefetch_related('respuesta_set') + + registrosA = {} + raSeccion = -1 + for ra in rAgregacion: + registros = ra.respuesta_set.all().select_subclasses().select_related('elemento__seccion') + first = registros.first() + if first: + sID = first.elemento.seccion_id + if sID != raSeccion: + raSeccion = sID + registrosA[raSeccion] = [] + registrosA[raSeccion].append(registros) + + #generando Forms + #se genera un diccionario con los tipos de forms + formModels = {} + formModelsConOpcion = [ROpcionMultipleForm, RCasillasForm, RDesplegableForm] + for formtype in [RNumericoForm, RTextoCortoForm, RTextoParrafoForm, RHoraForm, RFechaForm, ROpcionMultipleForm, RCasillasForm, RDesplegableForm]: + formModels[formtype.Meta.model] = formtype + + if request.method == 'GET': + crearForms(forms, opcOtro, None, preguntasEstudio, formModels, formModelsConOpcion, respuestas, solicitante) + + if request.method == 'POST': + crearForms(forms, opcOtro, request.POST, preguntasEstudio, formModels, formModelsConOpcion, respuestas, solicitante) + + + #Solo se validan y guardan las respuestas de secciones de tipo 'unico', las de 'agregacion' se omiten + todoValido = True + for form in forms.values(): + if not form.is_valid(): + todoValido = False + + if todoValido: + rAgregacionInstance = RAgregacion.objects.create() + for form in forms.values(): + form.instance.rAgregacion = rAgregacionInstance + form.save() + messages.success(request, 'Registro agregado con éxito') + return redirect('estudioSE:AgregarR', seccionID) + else: + for i, seccion in enumerate(preguntasEstudio): + for j, elemento in enumerate(seccion.elemento_set.all()): + if elemento.id in forms and forms[elemento.id].errors: + for error in forms[elemento.id].errors.values(): + messages.error(request, [f'Seccion {seccion.nombre}: Pregunta {elemento.nombre}', error]) + messages.warning(request, 'No se pudo guardar el registro: Formularios no validos') + + + context = { + 'mensajes' : 'mensajes.html', + 'opcOtro': opcOtro, + 'forms': forms, + 'preguntasEstudio': preguntasEstudio, + 'registrosA': registrosA, + } + + + return render(request, 'solicitante/formularioBase.html', context) + + + +@login_required +def eliminarR(request, seccionID, registroID): + url = verificarRedirect(request.user) + if url: #Verifica si el usuario ha llenaodo su informacion personal por primera vez y tiene los permisos necesarios + return redirect(url) + + solicitante = get_object_or_404(Solicitante, pk=request.user.id) + registroAgregacion = get_object_or_404(RAgregacion, pk=registroID) + respuestas = Respuesta.objects.filter(rAgregacion=registroAgregacion, solicitante=solicitante).select_subclasses() + + if respuestas.exists() and respuestas.first().elemento.seccion_id == seccionID: + print(f'eliminando {seccionID} - {registroID} - {respuestas.first().getStringValue()}') + registroAgregacion.delete() + messages.success(request, 'Registro eliminado con exito') + return redirect('estudioSE:AgregarR', seccionID) + else: + messages.error(request, 'No se pudo eliminar el Registro') diff --git a/estudio_socio_economico/viewsAdmin.py b/estudio_socio_economico/viewsAdmin.py index 5e1d86b0b4a89c3bb743a08a2fb0a088a4cd0c4b..ccb07ed8921507c07b9059d34e73aa125c3d8ddc 100644 --- a/estudio_socio_economico/viewsAdmin.py +++ b/estudio_socio_economico/viewsAdmin.py @@ -1,4 +1,5 @@ from django.shortcuts import get_object_or_404, redirect, render +from django.http import HttpResponse from usuarios.views import verificarRedirect from usuarios.models import Usuario from .models import Seccion, Elemento, Opcion @@ -6,6 +7,38 @@ from.forms import SeccionFormSet, ElementoFormSet, OpcionFormSet from django.contrib import messages # Create your views here. +def configEstudioGetForm(request): + url = verificarRedirect(request.user, 'permiso_administrador') + if url: #Verifica si el usuario ha llenaodo su informacion personal por primera vez y tiene los permisos necesarios + return HttpResponse("", status=401) + + context = {} + + if request.method == 'GET': + secciones = Seccion.objects.all() + seccionFormset = SeccionFormSet(queryset=secciones, prefix='seccion_formset') + dictElemForm = {} + dictOpcionForm = {} + for seccionform in seccionFormset: + seccionInstancia = seccionform.instance + seccionFormsetId = (seccionform.prefix.split('-').pop()) + dictElemForm[seccionform.prefix] = ElementoFormSet(instance=seccionInstancia, prefix='elemento_formset-%s' % seccionFormsetId) + for elementoform in dictElemForm[seccionform.prefix]: + elemInstancia = elementoform.instance + elemFormsetId = elementoform.prefix.split('-') + newId = elemFormsetId[1] + '-' + elemFormsetId[2] + dictOpcionForm[elementoform.prefix] = OpcionFormSet(instance=elemInstancia, prefix='opcion_formset-%s' % newId) + + context = { + 'seccionFormset': seccionFormset, + 'dictElemForm': dictElemForm, + 'dictOpcionForm': dictOpcionForm, + } + + return render(request, 'admin/config_estudioSEForm.html', context) + + + # Crear el inline formset para Elemento # Donde "ElementoForm" es el ModelForm personalizado para el modelo Elemento que definiste anteriormente. def configEstudio(request): @@ -14,20 +47,7 @@ def configEstudio(request): if url: #Verifica si el usuario ha llenaodo su informacion personal por primera vez y tiene los permisos necesarios return redirect(url) - secciones = Seccion.objects.all() - seccionFormset = SeccionFormSet(queryset=secciones, prefix='seccion_formset') - dictElemForm = {} - dictOpcionForm = {} - for seccionform in seccionFormset: - seccionInstancia = seccionform.instance - seccionFormsetId = (seccionform.prefix.split('-').pop()) - dictElemForm[seccionform.prefix] = ElementoFormSet(instance=seccionInstancia, prefix='elemento_formset-%s' % seccionFormsetId) - for elementoform in dictElemForm[seccionform.prefix]: - elemInstancia = elementoform.instance - elemFormsetId = elementoform.prefix.split('-') - newId = elemFormsetId[1] + '-' + elemFormsetId[2] - dictOpcionForm[elementoform.prefix] = OpcionFormSet(instance=elemInstancia, prefix='opcion_formset-%s' % newId) - + context = {} if request.method == 'POST': todoValido = True @@ -161,11 +181,11 @@ def configEstudio(request): - context = { - 'seccionFormset': seccionFormset, - 'dictElemForm': dictElemForm, - 'dictOpcionForm': dictOpcionForm, - } + context = { + 'seccionFormset': seccionFormset, + 'dictElemForm': dictElemForm, + 'dictOpcionForm': dictOpcionForm, + } diff --git a/generarCatalogos.py b/generarCatalogos.py index 680db4e5021d914cb00f22274b48c10b9ff6c857..1b1e253bf77fa8b85f9c26fc3bc0654bc395732c 100644 --- a/generarCatalogos.py +++ b/generarCatalogos.py @@ -4,14 +4,15 @@ from django.db import migrations, models, connection os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'becas_cozcyt.settings') django.setup() -def load_data_from_sql(): - file_path = os.path.join(os.path.dirname(__file__), 'catalogos.sql') +def load_data_from_sql(sqlFile): + file_path = os.path.join(os.path.dirname(__file__), sqlFile) sql_statement = open(file_path).read() print(sql_statement) with connection.cursor() as c: c.execute(sql_statement) -load_data_from_sql() +load_data_from_sql('estudioSE.sql') +load_data_from_sql('catalogos.sql') """" class Migration(migrations.Migration): diff --git a/requirements.txt b/requirements.txt index e508f42f67757765d8c2c12467d474f40f25f42b..9bbfaf3e413f436766ed075ee28b7e3ef54d3e82 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ mysqlclient==2.1.0 sqlparse ==0.4.2 django-filter==21.1 django-model-utils==4.3.1 -django-htmx \ No newline at end of file +django-htmx==1.16.0 +django-debug-toolbar==4.2.0 \ No newline at end of file diff --git a/static/css/main.css b/static/css/main.css index c2641ea7ff362096b1c6350e47d1572f9f73e991..c55e67e97bf3fcb8bcd22bbea2a6cc81561459fe 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -177,7 +177,11 @@ body { } .conte-sort { - min-height: 15px; + min-height: 0px; +} + +.altura-min-conte-sort-edge{ + min-height: 3px; } /* Login */ @@ -428,13 +432,21 @@ a { .hidden-ready { - display:none; + opacity: 0; + transition: opacity 0.3s ease-in-out; /* Animación de transición suave */ } .opcion-text { text-align: center; } +.opcion-digit { + max-width: 3.5rem; +} + +.elem-tipo-select { + min-width: 6rem; +} table { width: 100%; @@ -474,4 +486,8 @@ th { padding: 20px; border-radius: 5px; text-align: center; +} + +.form-check-inline-custom{ + display: inline-block; } \ No newline at end of file diff --git a/usuarios/forms.py b/usuarios/forms.py index 41a249cf51108a05e0df946e90bb2bdcc1146b23..229d61a0a9f2581653353273866430b5f2a06654 100644 --- a/usuarios/forms.py +++ b/usuarios/forms.py @@ -86,7 +86,7 @@ class SolicitanteForm(forms.ModelForm): 'tel_cel': forms.TextInput(attrs={'class': 'form-control border-3', 'onkeypress': "return isNumberKey(event)", 'placeholder': 'Ingrese el teléfono celular'}), 'tel_fijo': forms.TextInput(attrs={'class': 'form-control border-3', 'onkeypress': "return isNumberKey(event)", 'placeholder': 'Ingrese el teléfono fijo'}), 'grado': forms.Select(attrs={'class': 'form-control border-3 form-select'}), - 'promedio': forms.NumberInput(attrs={'class': 'form-control border-3', 'onkeypress': "return isNumberFloatKey(event)", 'placeholder': 'Ingrese el promedio'}), + 'promedio': forms.NumberInput(attrs={'class': 'form-control border-3', 'onkeypress': "return isNumberPuntKey(event)", 'placeholder': 'Ingrese el promedio'}), 'carrera': forms.Select(attrs={'class': 'form-control border-3 form-select'}), } labels = { diff --git a/usuarios/templates/Solicitante/estudioSE.html b/usuarios/templates/Solicitante/estudioSE.html deleted file mode 100644 index 32b64579b925cdd34d2a3256e2bc25444f706c8d..0000000000000000000000000000000000000000 --- a/usuarios/templates/Solicitante/estudioSE.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends 'base.html' %} - -{% block body %} -

Pagina Estudio Socio Economico (implementacion pendiente)

-{% endblock body %} \ No newline at end of file diff --git a/usuarios/templates/base.html b/usuarios/templates/base.html index 7b2464c973948bd6544606b8b8664611aaf153eb..77419e23d918bc5aa15c9c9c42bb6d0e8291d998 100644 --- a/usuarios/templates/base.html +++ b/usuarios/templates/base.html @@ -105,7 +105,7 @@
- - - - - - + + {% include 'base_custom_js.html' %} \ No newline at end of file diff --git a/usuarios/templates/base_blank.html b/usuarios/templates/base_blank.html index ac2308ef211a30874925a7c65b33ec7d4edaf645..e10535a8151b1506cd2624258953056a99bd477e 100644 --- a/usuarios/templates/base_blank.html +++ b/usuarios/templates/base_blank.html @@ -49,32 +49,7 @@ - + {% include 'base_custom_js.html' %} \ No newline at end of file diff --git a/usuarios/templates/base_custom_js.html b/usuarios/templates/base_custom_js.html new file mode 100644 index 0000000000000000000000000000000000000000..57427c4246f7759de661ec334ff8ae956b82faf6 --- /dev/null +++ b/usuarios/templates/base_custom_js.html @@ -0,0 +1,31 @@ + \ No newline at end of file diff --git a/usuarios/templates/custom_confirm.html b/usuarios/templates/custom_confirm.html index 660fe0df1113a73ca3116e17742fb5fc15229a1f..22fde0200a0395420344f0cb85a09f84b70893c1 100644 --- a/usuarios/templates/custom_confirm.html +++ b/usuarios/templates/custom_confirm.html @@ -2,11 +2,11 @@