8👍
@twil — Thanks for your help. You put me on the right track. Really appreciate your help. However, the solution didn’t work out of box. Didn’t actually show errors in my test case or work with change_view. Here’s want I ended up working with.
from django.contrib.admin import ModelAdmin
from django.db import DatabaseError, IntegrityError
from django.contrib import messages
class ShowValidationAdmin(ModelAdmin):
def add_view(self, request, form_url='', extra_context=None):
try:
return super(ShowValidationAdmin, self).add_view(request, form_url, extra_context)
except (IntegrityError, DatabaseError) as e:
request.method = 'GET'
messages.error(request, e.message)
return super(ShowValidationAdmin, self).add_view(request, form_url, extra_context)
def change_view(self, request, object_id, form_url='', extra_context=None):
try:
return super(ShowValidationAdmin, self).change_view(request, object_id, form_url, extra_context)
except (IntegrityError, DatabaseError) as e:
request.method = 'GET'
messages.error(request, e.message)
return super(ShowValidationAdmin, self).change_view(request, object_id, form_url, extra_context)
Note: That this version also seems to work cross version (django 1.3 – 1.6). Let me know if anyone has a better approach. I’ll wait to award bounty.
7👍
You need to slightly change your logic if possible. What you need is custom AdminModel.form
. All validation should be done there. See the note for save_model()
:
ModelAdmin.save_model() and ModelAdmin.delete_model() must save/delete
the object, they are not for veto purposes, rather they allow you to
perform extra operations.
But if your circumstances are so that you can’t do all validation inside the form I’d subclass ModelAdmin
and override def add_view()
, def change_view()
and def changelist_view()
like so:
from django.contrib import admin
from django import forms
from django.contrib.admin import helpers
from django.contrib.admin.options import csrf_protect_m, IS_POPUP_VAR
from django.utils.translation import ugettext as _
from django.utils.encoding import force_text
# for nonfield errors to show correctly
from django.forms.forms import NON_FIELD_ERRORS
from .models import TestModel
class TestModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
raise Exception('test exception')
@csrf_protect_m
def add_view(self, request, form_url='', extra_context=None):
try:
return super(TestModelAdmin, self).add_view(request, form_url, extra_context)
except Exception as e:
pass
# mimic parent class on error
model = self.model
opts = model._meta
ModelForm = self.get_form(request)
formsets = []
inline_instances = self.get_inline_instances(request, None)
form = ModelForm(request.POST, request.FILES)
form.is_valid()
# make faked nonfield error
# see http://stackoverflow.com/questions/8598247/how-to-append-error-message-to-form-non-field-errors-in-django
form._errors[NON_FIELD_ERRORS] = form.error_class([e.message])
# We may handle exception here (just to save indentation)
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
self.get_prepopulated_fields(request),
self.get_readonly_fields(request),
model_admin=self)
media = self.media + adminForm.media
inline_admin_formsets = []
for inline, formset in zip(inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request))
readonly = list(inline.get_readonly_fields(request))
prepopulated = dict(inline.get_prepopulated_fields(request))
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
fieldsets, prepopulated, readonly, model_admin=self)
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
context = {
'title': _('Add %s') % force_text(opts.verbose_name),
'adminform': adminForm,
'is_popup': IS_POPUP_VAR in request.REQUEST,
'media': media,
'inline_admin_formsets': inline_admin_formsets,
'errors': helpers.AdminErrorList(form, formsets),
'app_label': opts.app_label,
'preserved_filters': self.get_preserved_filters(request),
}
context.update(extra_context or {})
return self.render_change_form(request, context, form_url=form_url, add=True)
admin.site.register(TestModel, TestModelAdmin)
My models.py
:
from django.db import models
class TestModel(models.Model):
text = models.TextField()
You see, there’s no easy way of hooking inside save_model()
so you’ll have to copy-paste part of form preparation code.
- Django.core.paginator Ajax pagination with jQuery
- Django success url using kwargs
- Django ORM leaks connections when using ThreadPoolExecutor
0👍
Try this:
from django.core.exceptions import ValidationError
def save_model(self, request, obj, form, change):
try:
obj.save()
except DatabaseError as e:
raise ValidationError(e)
- Django-allauth: Only allow users from a specific google apps domain
- Django conditional Subquery aggregate
0👍
Old question, but nobody mentioned ModelAdmin.message_user() yet, which also uses the messages framework.
For example:
from django.contrib import admin, messages
class MyModelAdmin(admin.ModelAdmin):
...
def save_model(self, request, *args, **kwargs):
try:
super().save_model(request, *args, **kwargs)
except DatabaseError as e:
messages.set_level(request=request, level=messages.ERROR)
self.message_user(request=request, message=e, level=messages.ERROR)
This would show your message as follows (assuming that’s the error string in e
):
The set_level
call suppresses the default success message. Also see e.g. here and here.
- Remove line breaks from Django template
- Is it possible to serve a static html page at the root of a django project?
- Django query how to write: WHERE field LIKE '10__8__0__'?
- No matching distribution found for django
0👍
In save_model(), you can handle DatabaseError exception separately and differently for adding or changing an object as shown below:
@admin.register(Person)
class PersonAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change): # Here
last_part_of_path = request.path.split('/')[-2]
if last_part_of_path == "add":
try: # ↓ I intentionally raise an exception
raise DatabaseError("Add Error")
obj.save()
except DatabaseError as e:
messages.set_level(request, messages.ERROR)
messages.error(request, e)
if last_part_of_path == "change":
try: # ↓ I intentionally raise an exception
raise DatabaseError("Change Error")
obj.save()
except DatabaseError as e:
messages.set_level(request, messages.ERROR)
messages.error(request, e)