diff --git a/estudio_socio_economico/admin.py b/estudio_socio_economico/admin.py index f5d3b9a4f9b68cbac665ccc9968cfe3174d21546..ed65a63c804cb0d81744e861621cb6bc2fbae2d0 100644 --- a/estudio_socio_economico/admin.py +++ b/estudio_socio_economico/admin.py @@ -1,10 +1,12 @@ from django.contrib import admin -from .models import Seccion, Opcion, Respuesta, RTextoCorto, RTextoParrafo, ROpcionMultiple, RCasillas, RDesplegable, Elemento +from .models import Seccion, Opcion, 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(Respuesta) @@ -13,4 +15,7 @@ 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 a4ec287d89bfeb1db7eedf90550b7d9a687cc4a4..305b33632efd76dbd8c1e30f5bdc19c1161933f0 100644 --- a/estudio_socio_economico/forms.py +++ b/estudio_socio_economico/forms.py @@ -1,8 +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 - +from .models import RNumerico, RTextoCorto, RTextoParrafo, RHora, RFecha, ROpcionMultiple, RCasillas, RDesplegable, Respuesta +from django.core.validators import MinLengthValidator, MaxLengthValidator class SeccionForm(forms.ModelForm): @@ -11,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 = { @@ -71,19 +71,59 @@ ElementoFormSet = inlineformset_factory(Seccion, Elemento, form=ElementoForm, ex 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") - solicitante = kwargs.pop("solicitante") + elemento = kwargs.pop("elemento", None) + solicitante = kwargs.pop("solicitante", None) super(RespuestaForm, self).__init__(*args, **kwargs) - self.instance.solicitante = solicitante - self.instance.elemento = elemento + 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 + + """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 = ['texto'] - widgets = {'texto': forms.NumberInput(attrs={'class': 'form-control'}),} - labels = {'texto': 'Respuesta numérica'} + 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: @@ -92,6 +132,14 @@ class RTextoCortoForm(RespuestaForm): widgets = {'texto': forms.TextInput(attrs={'class': 'form-control'}),} 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 @@ -99,40 +147,135 @@ class RTextoParrafoForm(RespuestaForm): widgets = {'texto': forms.Textarea(attrs={'class': 'form-control'}),} 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'}),} + widgets = {'hora': forms.TimeInput(attrs={'class': 'form-control', '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(attrs={'class': 'form-control'}),} + 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.Select(attrs={'class': 'form-control'}), + widgets = {'respuesta': forms.RadioSelect(attrs={'class': 'form-check-input checkbox-principal'}), 'otro': forms.TextInput(attrs={'class': 'form-control'}),} 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'}), + widgets = {'respuesta': forms.CheckboxSelectMultiple(attrs={'class': 'form-check-input checkbox-principal form-check-inline-custom', 'style': 'display: inline-block;'}), 'otro': forms.TextInput(attrs={'class': 'form-control'}),} 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'}), + widgets = {'respuesta': forms.Select(attrs={'class': 'form-control form-select border-3'}), 'otro': forms.TextInput(attrs={'class': 'form-control'}),} - labels = {'respuesta': 'Respuesta de desplegable', 'otro': 'Otro'} \ No newline at end of file + 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 56f75d49a6807752ba0af3fd461b06dcefdd6a38..bd6ebfa9c3ee82f23d731c7d0a9781c764a282eb 100644 --- a/estudio_socio_economico/models.py +++ b/estudio_socio_economico/models.py @@ -26,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) @@ -69,65 +69,39 @@ class Respuesta(models.Model): verbose_name_plural = 'Respuestas' unique_together = ['elemento', 'solicitante'] - def clean(self): - if self.elemento.obligatorio and self.is_blank(): - raise ValidationError(f"La respuesta para '{self.elemento}' es obligatoria.") - super().clean() - - def is_blank(self): - raise NotImplementedError("Subclases de Respuesta deben implementar este método.") - class RNumerico(Respuesta): - texto = models.CharField(max_length=255) + valor = models.CharField(max_length=255, null=True, blank=True) + - def is_blank(self): - return not self.texto.strip() class RTextoCorto(Respuesta): - texto = models.CharField(max_length=255) - - def is_blank(self): - return not self.texto.strip() + texto = models.CharField(max_length=255, null=True, blank=True) + class RTextoParrafo(Respuesta): - texto = models.TextField() + texto = models.TextField(null=True, blank=True) - def is_blank(self): - return not self.texto.strip() class RHora(Respuesta): - hora = models.TimeField() + hora = models.TimeField(null=True, blank=True) - def is_blank(self): - return self.hora is None class RFecha(Respuesta): - fecha = models.DateField() + fecha = models.DateField(null=True, blank=True) - def is_blank(self): - return self.fecha is None 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=False) - - def is_blank(self): - return self.respuesta is None and not ((self.elemento.opcionOtro and self.otro.strip()) or not self.elemento.opcionOtro ) + otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=True) class RCasillas(Respuesta): - respuesta = models.ManyToManyField(Opcion) - otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=False) - - def is_blank(self): - return self.respuesta is None and not ((self.elemento.opcionOtro and self.otro.strip()) or not self.elemento.opcionOtro ) + respuesta = models.ManyToManyField(Opcion, blank=True) + otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=True) class RDesplegable(Respuesta): - respuesta = models.ForeignKey(Opcion, on_delete=models.CASCADE) - otro = models.CharField(max_length=255, verbose_name="Otro", null=True, blank=False) - - def is_blank(self): - return self.respuesta is None and not ((self.elemento.opcionOtro and self.otro.strip()) or not self.elemento.opcionOtro ) + 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) #---------Elementos----------- diff --git a/estudio_socio_economico/templates/solicitante/estudioSE.html b/estudio_socio_economico/templates/solicitante/estudioSE.html index 561ee30b9c944cf305af2aa3612aee490bb9f021..46bc7e9da7ac2cc397910bc2e7f0bf8429833487 100644 --- a/estudio_socio_economico/templates/solicitante/estudioSE.html +++ b/estudio_socio_economico/templates/solicitante/estudioSE.html @@ -1,14 +1,83 @@ {% extends 'base.html' %} +{% load custom_filters %} {% block body %} -
Pagina Estudio Socio Economico (implementacion pendiente)
+Estudio Socioeconómico
diff --git a/estudio_socio_economico/views.py b/estudio_socio_economico/views.py index 57243754c58cc4bc4398ada87eed501a6e135e09..b61e42c6411992194af083012a9b9432fa3c8d94 100644 --- a/estudio_socio_economico/views.py +++ b/estudio_socio_economico/views.py @@ -21,45 +21,81 @@ def estudioSE(request): solicitante = get_object_or_404(Solicitante, pk=request.user.id) #obtener los formularios del estudio SE preguntasEstudio = Seccion.objects.prefetch_related('elemento_set__opcion_set').all() - + 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) + respuestasExistentes = Respuesta.objects.filter(solicitante = solicitante).select_subclasses() for respuesta in respuestasExistentes: respuestas[respuesta.elemento_id] = respuesta #generando Forms #se genera un diccionario con los tipos de forms formModels = {} - formModelsConOpcion = [ROpcionMultipleForm, RCasillasForm, RDesplegableForm] - forms = {} + formModelsConOpcion = [ROpcionMultipleForm, RCasillasForm, RDesplegableForm] for formtype in [RNumericoForm, RTextoCortoForm, RTextoParrafoForm, RHoraForm, RFechaForm, ROpcionMultipleForm, RCasillasForm, RDesplegableForm]: formModels[formtype.Meta.model] = formtype - 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()] - #si ya existe una respuesta de esta pregunta - if elemento.id in respuestas: - forms[elemento.id] = formModel(instance=respuestas[elemento.id]) - #si no existe una respuesta de esta pregunta - else: - forms[elemento.id] = formModel(elemento=elemento, solicitante=solicitante) - #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, ("", "---------")) - forms[elemento.id].fields['respuesta'].choices = choices - #forms[elemento.id].fields['respuesta'].queryset = elemento.opcion_set.all() - + + if request.method == 'GET': + 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()] + #si ya existe una respuesta de esta pregunta + if elemento.id in respuestas: + forms[elemento.id] = formModel(instance=respuestas[elemento.id], prefix=f'respuesta_{elemento.id}') + #si no existe una respuesta de esta pregunta + else: + forms[elemento.id] = formModel(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 + #forms[elemento.id].fields['respuesta'].queryset = elemento.opcion_set.all() + + if request.method == 'POST': + print(request.POST) + 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()] + #si ya existe una respuesta de esta pregunta + if elemento.id in respuestas: + forms[elemento.id] = formModel(request.POST, instance=respuestas[elemento.id], prefix=f'respuesta_{elemento.id}') + #si no existe una respuesta de esta pregunta + else: + forms[elemento.id] = formModel(request.POST, 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 + #forms[elemento.id].fields['respuesta'].queryset = elemento.opcion_set.all() + + for form in forms.values(): + form.is_valid() + print(form.instance.elemento_id) + print(form.errors) + print('aa') + - context = { + context = { 'forms': forms, + 'preguntasEstudio': preguntasEstudio, } diff --git a/static/css/main.css b/static/css/main.css index b281012802d7d5b19c6e61d21ade570b42bf80c6..c55e67e97bf3fcb8bcd22bbea2a6cc81561459fe 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -486,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