| 1 | | If someone stumbles upon this I managed to fix my issue with this monkeypatch of [https://github.com/django/django/blob/eb3699ea775548a22e0407ad12bf8cbdeaf95ff5/django/contrib/admin/options.py#L1748]: |
| 2 | | |
| 3 | | {{{ |
| 4 | | from django.contrib.admin.options import * |
| 5 | | from django.contrib.admin.exceptions import DisallowedModelAdminToField |
| 6 | | from django.core.exceptions import ( |
| 7 | | PermissionDenied, |
| 8 | | ValidationError, |
| 9 | | ) |
| 10 | | from django.contrib.admin.utils import ( |
| 11 | | flatten_fieldsets, |
| 12 | | unquote, |
| 13 | | ) |
| 14 | | from django.forms.formsets import all_valid |
| 15 | | from django.contrib.admin import helpers |
| 16 | | from django.utils.translation import gettext as _ |
| 17 | | |
| 18 | | class NewModelAdmin(ModelAdmin): |
| 19 | | def _changeform_view(self, request, object_id, form_url, extra_context): |
| 20 | | to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR)) |
| 21 | | if to_field and not self.to_field_allowed(request, to_field): |
| 22 | | raise DisallowedModelAdminToField( |
| 23 | | "The field %s cannot be referenced." % to_field |
| 24 | | ) |
| 25 | | |
| 26 | | model = self.model |
| 27 | | opts = model._meta |
| 28 | | |
| 29 | | if request.method == "POST" and "_saveasnew" in request.POST: |
| 30 | | object_id = None |
| 31 | | |
| 32 | | add = object_id is None |
| 33 | | |
| 34 | | if add: |
| 35 | | if not self.has_add_permission(request): |
| 36 | | raise PermissionDenied |
| 37 | | obj = None |
| 38 | | |
| 39 | | else: |
| 40 | | obj = self.get_object(request, unquote(object_id), to_field) |
| 41 | | |
| 42 | | if request.method == "POST": |
| 43 | | if not self.has_change_permission(request, obj): |
| 44 | | raise PermissionDenied |
| 45 | | else: |
| 46 | | if not self.has_view_or_change_permission(request, obj): |
| 47 | | raise PermissionDenied |
| 48 | | |
| 49 | | if obj is None: |
| 50 | | return self._get_obj_does_not_exist_redirect(request, opts, object_id) |
| 51 | | |
| 52 | | fieldsets = self.get_fieldsets(request, obj) |
| 53 | | ModelForm = self.get_form( |
| 54 | | request, obj, change=not add, fields=flatten_fieldsets(fieldsets) |
| 55 | | ) |
| 56 | | if request.method == "POST": |
| 57 | | form = ModelForm(request.POST, request.FILES, instance=obj) |
| 58 | | formsets, inline_instances = self._create_formsets( |
| 59 | | request, |
| 60 | | form.instance if add else obj, |
| 61 | | change=not add, |
| 62 | | ) |
| 63 | | form_validated = form.is_valid() |
| 64 | | if form_validated: |
| 65 | | new_object = self.save_form(request, form, change=not add) |
| 66 | | else: |
| 67 | | new_object = form.instance |
| 68 | | if all_valid(formsets) and form_validated: |
| 69 | | try: |
| 70 | | self.save_model(request, new_object, form, not add) |
| 71 | | self.save_related(request, form, formsets, not add) |
| 72 | | change_message = self.construct_change_message( |
| 73 | | request, form, formsets, add |
| 74 | | ) |
| 75 | | if add: |
| 76 | | self.log_addition(request, new_object, change_message) |
| 77 | | return self.response_add(request, new_object) |
| 78 | | else: |
| 79 | | self.log_change(request, new_object, change_message) |
| 80 | | return self.response_change(request, new_object) |
| 81 | | except ValidationError as e: |
| 82 | | form_validated = False |
| 83 | | form._update_errors([e]) |
| 84 | | else: |
| 85 | | form_validated = False |
| 86 | | else: |
| 87 | | if add: |
| 88 | | initial = self.get_changeform_initial_data(request) |
| 89 | | form = ModelForm(initial=initial) |
| 90 | | formsets, inline_instances = self._create_formsets( |
| 91 | | request, form.instance, change=False |
| 92 | | ) |
| 93 | | else: |
| 94 | | form = ModelForm(instance=obj) |
| 95 | | formsets, inline_instances = self._create_formsets( |
| 96 | | request, obj, change=True |
| 97 | | ) |
| 98 | | |
| 99 | | if not add and not self.has_change_permission(request, obj): |
| 100 | | readonly_fields = flatten_fieldsets(fieldsets) |
| 101 | | else: |
| 102 | | readonly_fields = self.get_readonly_fields(request, obj) |
| 103 | | adminForm = helpers.AdminForm( |
| 104 | | form, |
| 105 | | list(fieldsets), |
| 106 | | # Clear prepopulated fields on a view-only form to avoid a crash. |
| 107 | | self.get_prepopulated_fields(request, obj) |
| 108 | | if add or self.has_change_permission(request, obj) |
| 109 | | else {}, |
| 110 | | readonly_fields, |
| 111 | | model_admin=self, |
| 112 | | ) |
| 113 | | media = self.media + adminForm.media |
| 114 | | |
| 115 | | inline_formsets = self.get_inline_formsets( |
| 116 | | request, formsets, inline_instances, obj |
| 117 | | ) |
| 118 | | for inline_formset in inline_formsets: |
| 119 | | media = media + inline_formset.media |
| 120 | | |
| 121 | | if add: |
| 122 | | title = _("Add %s") |
| 123 | | elif self.has_change_permission(request, obj): |
| 124 | | title = _("Change %s") |
| 125 | | else: |
| 126 | | title = _("View %s") |
| 127 | | context = { |
| 128 | | **self.admin_site.each_context(request), |
| 129 | | "title": title % opts.verbose_name, |
| 130 | | "subtitle": str(obj) if obj else None, |
| 131 | | "adminform": adminForm, |
| 132 | | "object_id": object_id, |
| 133 | | "original": obj, |
| 134 | | "is_popup": IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET, |
| 135 | | "to_field": to_field, |
| 136 | | "media": media, |
| 137 | | "inline_admin_formsets": inline_formsets, |
| 138 | | "errors": helpers.AdminErrorList(form, formsets), |
| 139 | | "preserved_filters": self.get_preserved_filters(request), |
| 140 | | } |
| 141 | | |
| 142 | | # Hide the "Save" and "Save and continue" buttons if "Save as New" was |
| 143 | | # previously chosen to prevent the interface from getting confusing. |
| 144 | | if ( |
| 145 | | request.method == "POST" |
| 146 | | and not form_validated |
| 147 | | and "_saveasnew" in request.POST |
| 148 | | ): |
| 149 | | context["show_save"] = False |
| 150 | | context["show_save_and_continue"] = False |
| 151 | | # Use the change template instead of the add template. |
| 152 | | add = False |
| 153 | | |
| 154 | | context.update(extra_context or {}) |
| 155 | | |
| 156 | | return self.render_change_form( |
| 157 | | request, context, add=add, change=not add, obj=obj, form_url=form_url |
| 158 | | ) |
| 159 | | }}} |
| 160 | | |
| 161 | | |
| 162 | | using this modified ModelAdmin called NewModelAdmin will catch ValidationError(s) during the save process and properly handle those. |
| 163 | | |
| 164 | | |
| 165 | | My code overrides the entire method but changes only these lines: |
| 166 | | https://github.com/django/django/blob/eb3699ea775548a22e0407ad12bf8cbdeaf95ff5/django/contrib/admin/options.py#L1796-L1807 |
| | 1 | If someone stumbles upon this I managed to fix my issue with this: |
| | 2 | https://forum.djangoproject.com/t/many-to-many-field-validation-in-django/14701/11 |