#33832: Support M2M validation using signals
-------------------------------------+-------------------------------------
     Reporter:  ldeluigi             |                    Owner:  nobody
         Type:  New feature          |                   Status:  closed
    Component:  contrib.admin        |                  Version:  4.0
     Severity:  Normal               |               Resolution:  invalid
     Keywords:  validation m2m       |             Triage Stage:
  signal manytomany                  |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------

Comment (by ldeluigi):

 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]:

 {{{
 from django.contrib.admin.options import *
 from django.contrib.admin.exceptions import DisallowedModelAdminToField
 from django.core.exceptions import (
     PermissionDenied,
     ValidationError,
 )
 from django.contrib.admin.utils import (
     flatten_fieldsets,
     unquote,
 )
 from django.forms.formsets import all_valid
 from django.contrib.admin import helpers
 from django.utils.translation import gettext as _

 class NewModelAdmin(ModelAdmin):
     def _changeform_view(self, request, object_id, form_url,
 extra_context):
         to_field = request.POST.get(TO_FIELD_VAR,
 request.GET.get(TO_FIELD_VAR))
         if to_field and not self.to_field_allowed(request, to_field):
             raise DisallowedModelAdminToField(
                 "The field %s cannot be referenced." % to_field
             )

         model = self.model
         opts = model._meta

         if request.method == "POST" and "_saveasnew" in request.POST:
             object_id = None

         add = object_id is None

         if add:
             if not self.has_add_permission(request):
                 raise PermissionDenied
             obj = None

         else:
             obj = self.get_object(request, unquote(object_id), to_field)

             if request.method == "POST":
                 if not self.has_change_permission(request, obj):
                     raise PermissionDenied
             else:
                 if not self.has_view_or_change_permission(request, obj):
                     raise PermissionDenied

             if obj is None:
                 return self._get_obj_does_not_exist_redirect(request,
 opts, object_id)

         fieldsets = self.get_fieldsets(request, obj)
         ModelForm = self.get_form(
             request, obj, change=not add,
 fields=flatten_fieldsets(fieldsets)
         )
         if request.method == "POST":
             form = ModelForm(request.POST, request.FILES, instance=obj)
             formsets, inline_instances = self._create_formsets(
                 request,
                 form.instance if add else obj,
                 change=not add,
             )
             form_validated = form.is_valid()
             if form_validated:
                 new_object = self.save_form(request, form, change=not add)
             else:
                 new_object = form.instance
             if all_valid(formsets) and form_validated:
                 try:
                     self.save_model(request, new_object, form, not add)
                     self.save_related(request, form, formsets, not add)
                     change_message = self.construct_change_message(
                     request, form, formsets, add
                     )
                     if add:
                         self.log_addition(request, new_object,
 change_message)
                         return self.response_add(request, new_object)
                     else:
                         self.log_change(request, new_object,
 change_message)
                         return self.response_change(request, new_object)
                 except ValidationError as e:
                     form_validated = False
                     form._update_errors([e])
             else:
                 form_validated = False
         else:
             if add:
                 initial = self.get_changeform_initial_data(request)
                 form = ModelForm(initial=initial)
                 formsets, inline_instances = self._create_formsets(
                     request, form.instance, change=False
                 )
             else:
                 form = ModelForm(instance=obj)
                 formsets, inline_instances = self._create_formsets(
                     request, obj, change=True
                 )

         if not add and not self.has_change_permission(request, obj):
             readonly_fields = flatten_fieldsets(fieldsets)
         else:
             readonly_fields = self.get_readonly_fields(request, obj)
         adminForm = helpers.AdminForm(
             form,
             list(fieldsets),
             # Clear prepopulated fields on a view-only form to avoid a
 crash.
             self.get_prepopulated_fields(request, obj)
             if add or self.has_change_permission(request, obj)
             else {},
             readonly_fields,
             model_admin=self,
         )
         media = self.media + adminForm.media

         inline_formsets = self.get_inline_formsets(
             request, formsets, inline_instances, obj
         )
         for inline_formset in inline_formsets:
             media = media + inline_formset.media

         if add:
             title = _("Add %s")
         elif self.has_change_permission(request, obj):
             title = _("Change %s")
         else:
             title = _("View %s")
         context = {
             **self.admin_site.each_context(request),
             "title": title % opts.verbose_name,
             "subtitle": str(obj) if obj else None,
             "adminform": adminForm,
             "object_id": object_id,
             "original": obj,
             "is_popup": IS_POPUP_VAR in request.POST or IS_POPUP_VAR in
 request.GET,
             "to_field": to_field,
             "media": media,
             "inline_admin_formsets": inline_formsets,
             "errors": helpers.AdminErrorList(form, formsets),
             "preserved_filters": self.get_preserved_filters(request),
         }

         # Hide the "Save" and "Save and continue" buttons if "Save as New"
 was
         # previously chosen to prevent the interface from getting
 confusing.
         if (
             request.method == "POST"
             and not form_validated
             and "_saveasnew" in request.POST
         ):
             context["show_save"] = False
             context["show_save_and_continue"] = False
             # Use the change template instead of the add template.
             add = False

         context.update(extra_context or {})

         return self.render_change_form(
             request, context, add=add, change=not add, obj=obj,
 form_url=form_url
         )
 }}}


 using this modified ModelAdmin called NewModelAdmin will catch
 ValidationError(s) during the save process and properly handle those.


 My code overrides the entire method but changes only these lines:
 
https://github.com/django/django/blob/eb3699ea775548a22e0407ad12bf8cbdeaf95ff5/django/contrib/admin/options.py#L1796-L1807

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33832#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/01070181decd5730-75cada27-a4e6-4e2d-a09a-522887c71ea7-000000%40eu-central-1.amazonses.com.

Reply via email to