validations - modelform widgets django
La validaciĆ³n de unique_together ModelForm de Django (7)
Tengo un modelo de Django que se ve así.
class Solution(models.Model):
''''''
Represents a solution to a specific problem.
''''''
name = models.CharField(max_length=50)
problem = models.ForeignKey(Problem)
description = models.TextField(blank=True)
date = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ("name", "problem")
Utilizo un formulario para agregar modelos que se ve así:
class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = [''problem'']
Mi problema es que SolutionForm
no valida la restricción unique_together
Solution
y, por lo tanto, devuelve un IntegrityError
al intentar guardar el formulario. Sé que podría usar validate_unique
para verificar manualmente esto, pero me preguntaba si hay alguna forma de detectar esto en la validación del formulario y devolver un error de forma automáticamente.
Gracias.
Como dice Felix, se supone que unique_together
verifica la restricción unique_together
en su validación.
Sin embargo, en su caso, está excluyendo un elemento de esa restricción de su formulario. Me imagino que este es su problema: ¿cómo va a verificar el formulario la restricción, si la mitad de ella ni siquiera está en el formulario?
Con la ayuda de la respuesta de Jarmo, lo siguiente parece funcionar bien para mí (en Django 1.3), pero es posible que haya roto algún caso de esquina (hay muchas entradas en torno a _get_validation_exclusions
):
class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = [''problem'']
def _get_validation_exclusions(self):
exclude = super(SolutionForm, self)._get_validation_exclusions()
exclude.remove(''problem'')
return exclude
No estoy seguro, pero esto me parece un error de Django ... pero tendría que analizar los problemas informados anteriormente.
Editar: hablé demasiado pronto. Tal vez lo que escribí arriba funcionará en algunas situaciones, pero no en la mía; Terminé usando la respuesta de Jarmo directamente.
Deberás hacer algo como esto:
def your_view(request):
if request.method == ''GET'':
form = SolutionForm()
elif request.method == ''POST'':
problem = ... # logic to find the problem instance
solution = Solution(problem=problem) # or solution.problem = problem
form = SolutionForm(request.POST, instance=solution)
# the form will validate because the problem has been provided on solution instance
if form.is_valid():
solution = form.save()
# redirect or return other response
# show the form
Logré solucionar esto sin modificar la vista agregando un método de limpieza a mi formulario:
class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = [''problem'']
def clean(self):
cleaned_data = self.cleaned_data
try:
Solution.objects.get(name=cleaned_data[''name''], problem=self.problem)
except Solution.DoesNotExist:
pass
else:
raise ValidationError(''Solution with this Name already exists for this problem'')
# Always return cleaned_data
return cleaned_data
Lo único que tengo que hacer ahora en la vista es agregar una propiedad problemática al formulario antes de ejecutar is_valid
.
Resolví este mismo problema anulando el método validate_unique()
de ModelForm:
def validate_unique(self):
exclude = self._get_validation_exclusions()
exclude.remove(''problem'') # allow checking against the missing attribute
try:
self.instance.validate_unique(exclude=exclude)
except ValidationError, e:
self._update_errors(e.message_dict)
Ahora siempre me aseguro de que el atributo no proporcionado en el formulario aún esté disponible, por instance=Solution(problem=some_problem)
, instance=Solution(problem=some_problem)
en el inicializador.
Si desea que el mensaje de error esté asociado con el campo de name
(y que aparece junto a él):
def clean(self):
cleaned_data = super().clean()
name_field = ''name''
name = cleaned_data.get(name_field)
if name:
if Solution.objects.filter(name=name, problem=self.problem).exists():
cleaned_data.pop(name_field) # is also done by add_error
self.add_error(name_field, _(''There is already a solution with this name.''))
return cleaned_data
la solución de @sttwister es correcta, pero se puede simplificar.
class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = [''problem'']
def clean(self):
cleaned_data = self.cleaned_data
if Solution.objects.filter(name=cleaned_data[''name''],
problem=self.problem).exists():
raise ValidationError(
''Solution with this Name already exists for this problem'')
# Always return cleaned_data
return cleaned_data
Como extra, no recuperas el objeto en caso de duplicación, sino que solo verifica si existe en la base de datos, lo que ahorra un poco de rendimiento.