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 |