J'ai un modèle Django qui ressemble à ceci.
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")
J'utilise un formulaire pour ajouter des modèles qui ressemble à ceci:
class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = ['problem']
Mon problème est que le SolutionForm
ne valide pas le Solution
unique_together
contrainte et donc, elle renvoie un IntegrityError
lors de la tentative d'enregistrement du formulaire. Je sais que je pourrais utiliser validate_unique
pour vérifier manuellement cela, mais je me demandais s'il y avait un moyen d'attraper cela dans la validation du formulaire et de renvoyer automatiquement une erreur de formulaire.
Merci.
J'ai réussi à résoudre ce problème sans modifier la vue en ajoutant une méthode propre à mon formulaire:
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
La seule chose que je dois faire maintenant dans la vue est d'ajouter une propriété de problème au formulaire avant d'exécuter is_valid
.
J'ai résolu ce même problème en remplaçant la méthode validate_unique()
du 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)
Maintenant, je m'assure toujours que l'attribut non fourni sur le formulaire est toujours disponible, par exemple instance=Solution(problem=some_problem)
sur l'initialiseur.
Comme le dit Felix, les ModelForms sont censés vérifier le unique_together
contrainte dans leur validation.
Cependant, dans votre cas, vous excluez en fait un élément de cette contrainte de votre formulaire. J'imagine que c'est votre problème - comment le formulaire va-t-il vérifier la contrainte, si la moitié n'est même pas sur le formulaire?
la solution de @sttwister est correcte mais peut être simplifiée.
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
En bonus, vous ne récupérez pas l'objet en cas de doublon mais vérifiez seulement s'il existe dans la base de données en économisant un peu de performances.
Avec l'aide de la réponse de Jarmo, ce qui suit semble bien fonctionner pour moi (dans Django 1.3), mais il est possible que j'ai cassé un cas de coin (il y a beaucoup de tickets entourant _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
Je ne suis pas sûr, mais cela semble être un bug Django pour moi ... mais je devrais regarder autour des problèmes signalés précédemment.
Edit: j'ai parlé trop tôt. Peut-être que ce que j'ai écrit ci-dessus fonctionnera dans certaines situations, mais pas dans le mien; J'ai fini par utiliser directement la réponse de Jarmo.
Ma solution est basée sur Django 2.1
Laissez SolutionForm tranquille, ayez une méthode save () dans Solution
class Solution(models.Model):
...
def save(self, *args, **kwargs):
self.clean()
return super(Solution, self).save(*args, **kwargs)
def clean():
# have your custom model field checks here
# They can raise Validation Error
# Now this is the key to enforcing unique constraint
self.validate_unique()
L'appel de full_clean () dans save () ne fonctionne pas car la ValidationError ne sera pas gérée
Vous devrez faire quelque chose comme ceci:
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
Si vous souhaitez que le message d'erreur soit associé au champ name
(et apparaisse à côté):
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