33
I managed to fix this without modifying the view by adding a clean method to my form:
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
The only thing I need to do now in the view is to add a problem property to the form before executing is_valid
.
40
I solved this same problem by overriding the validate_unique()
method of the 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)
Now I just always make sure that the attribute not provided on the form is still available, e.g. instance=Solution(problem=some_problem)
on the initializer.
- [Django]-Django access the length of a list within a template
- [Django]-Logging in Django and gunicorn
- [Django]-Using the reserved word "class" as field name in Django and Django REST Framework
32
As Felix says, ModelForms are supposed to check the unique_together
constraint in their validation.
However, in your case you are actually excluding one element of that constraint from your form. I imagine this is your problem β how is the form going to check the constraint, if half of it is not even on the form?
- [Django]-Django admin, hide a model
- [Django]-How to filter objects for count annotation in Django?
- [Django]-How to detect Browser type in Django?
11
the solution from @sttwister is right but can be simplified.
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
As a bonus you do not retreive the object in case of duplicate but only check if it exists in the database saving a little bit of performances.
- [Django]-How to output Django queryset as JSON?
- [Django]-Django test runner not finding tests
- [Django]-Django β Clean permission table
1
With the help of Jarmoβs answer, the following seems to work nicely for me (in Django 1.3), but itβs possible Iβve broken some corner case (there are a lot of tickets surrounding _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
Iβm not sure, but this seems like a Django bug to meβ¦ but Iβd have to look around the previously-reported issues.
Edit: I spoke too soon. Maybe what I wrote above will work in some situations, but not in mine; I ended up using Jarmoβs answer directly.
- [Django]-How do I create a slug in Django?
- [Django]-How do I migrate a model out of one django app and into a new one?
- [Django]-Django β How to set default value for DecimalField in django 1.3?
0
You will need to do something like this:
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
- [Django]-Django migrate βfake and βfake-initial explained
- [Django]-Django FileField with upload_to determined at runtime
- [Django]-How to access request body when using Django Rest Framework and avoid getting RawPostDataException
0
If you want the error message to be a associated with the name
field (and appear next to it):
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
- [Django]-Django query filter with variable column
- [Django]-Where does pip install its packages?
- [Django]-Django: Adding "NULLS LAST" to query
0
My solution is based off Django 2.1
Leave SolutionForm alone, have a save() method in 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()
Calling full_clean() in save() does not work as the ValidationError will be unhandled
- [Django]-How to get GET request values in Django?
- [Django]-Django error message "Add a related_name argument to the definition"
- [Django]-How to configure where to redirect after a log out in Django?
0
I needed to exclude the company
field in my case and add it in the viewβs form_valid
function. I ended up doing the following (taking inspiration from different answers).
In my CreateView
def form_valid(self, form):
cleaned_data = form.cleaned_data
user_company = self.request.user.profile.company
if UnitCategory.objects.filter(code=cleaned_data['code'],
company=user_company).exists():
form.add_error('code', _(
'A UnitCategory with this Code already exists for this company.'))
return super(UnitCategoryCreateView, self).form_invalid(form)
if UnitCategory.objects.filter(color=cleaned_data['color'],
company=user_company).exists():
form.add_error('color', _(
'A UnitCategory with this Color already exists for this company.'))
return super(UnitCategoryCreateView, self).form_invalid(form)
form.instance.company = user_company
return super(UnitCategoryCreateView, self).form_valid(form)
In my UpdateView
I had to exclude the current instance of the object in checking if the query exist using exclude(pk=self.kwargs['pk'])
def form_valid(self, form):
cleaned_data = form.cleaned_data
user_company = self.request.user.profile.company
if UnitCategory.objects.filter(code=cleaned_data['code'],
company=user_company).exclude(pk=self.kwargs['pk']).exists():
form.add_error(
'code', _('A UnitCategory with this Code already exists for this company.'))
return super(UnitCategoryUpdateView, self).form_invalid(form)
if UnitCategory.objects.filter(color=cleaned_data['color'],
company=user_company).exclude(pk=self.kwargs['pk']).exists():
form.add_error('color', _(
'A UnitCategory with this Color already exists for this company.'))
return super(UnitCategoryUpdateView, self).form_invalid(form)
# Return form_valid if no errors raised
# Add logged-in user's company as form's company field
form.instance.company = user_company
return super(UnitCategoryUpdateView, self).form_valid(form)
Not the cleanest solution I was hoping for, but thought it might benefit someone.
- [Django]-Django models: get list of id
- [Django]-Differences between STATICFILES_DIR, STATIC_ROOT and MEDIA_ROOT
- [Django]-Composite primary key in django
0
If you always want to check the uniqueness constraint (i.e. on every ModelForm you create), you can make sure the uniqueness constraint is always validated on the model:
# Always check name-problem uniqueness
def validate_unique(self, exclude=None):
if exclude is None:
exclude = []
try:
exclude.remove("problem")
except ValueError:
pass
return super().validate_unique(exclude)
This works because the modelβs validate_unique
method is called during form validation.
- [Django]-Easiest way to rename a model using Django/South?
- [Django]-Difference between auto_now and auto_now_add
- [Django]-Django get the static files URL in view