I'm raising ValidationError in my model's clean() method
from django.core.exceptions import ValidationError
class PosDetail(models.Model, PosDetailChecksMixin):
self._errors = set()
...
def clean(self, *args, **kwargs):
if PosDetail.objects.filter(username=self.username).exists(): # example ValidationError
self._errors.add('check_duplicates')
raise ValidationError("This username is already in use.", code="invalid")
return super().clean(*args, **kwargs)
the ValidationError then is shown in the admin change view form after a POST request.
i want to show a button in the admin change view after form submission
i tried the save_model method but i dose not execute if a ValidationError is raised. i also tried to override the change_view method of the ModelAdmin but it executes before the Model clean method so it misses the validations.
@admin.register(PosDetail)
class BddDetailAdmin(admin.ModelAdmin):
...
def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
pos_detail = self.get_object(request, object_id)
if request.POST:
if pos_detail and 'check_duplicates' in pos_detail._errors:
extra_context['show_save_anyway'] = True
else:
extra_context['show_save_anyway'] = False
return super().change_view(request, object_id, form_url, extra_context)
Is there a ModelAdmin method that runs after Model.clean() ? so i can catch the ValidationError that accur in the Model.clean method.
You should utilize model forms for this.
Any validation errors raised during form validation will be added to the form's errors. For model forms, this includes errors raised from model validation. See:
To associate the error with a field, wrap the 'original' ValidationError in another ValidationError:
class PosDetail(models.Model, PosDetailChecksMixin):
def clean(self, *args, **kwargs):
if PosDetail.objects.filter(username=self.username).exists():
raise ValidationError(
{"username": ValidationError("This username is already in use.", code="duplicate")}
)
return super().clean(*args, **kwargs)
A form with 'duplicate' data will then be marked as invalid:
PosDetail.objects.create(username="foo")
PosDetailForm = modelform_factory(PosDetail, fields=["username"])
form = PosDetailForm(data={"username": "foo"})
print(f"{form.is_valid()=}, {repr(form.errors)}")
>>> form.is_valid()=False, {"username": "This username is already in use."}
In the admin you could then do something like this:
class BddDetailAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, form_url="", extra_context=None):
response = super().change_view(request, object_id, form_url, extra_context)
if response.context_data["adminform"].form.has_error("username", code="duplicate"):
response.context_data["show_save_anyway"] = True
return response