From 2d4d7931c016562185c01e998fd142a2f791f5f0 Mon Sep 17 00:00:00 2001 From: Clifford Gama Date: Tue, 29 Oct 2024 09:03:54 +0200 Subject: [PATCH] Fix #26220 -- Allowed ModelFormMixin.get_model() to retrieve the model from form_class's Meta. --- AUTHORS | 1 + django/views/generic/edit.py | 9 ++++++++- docs/ref/class-based-views/flattened-index.txt | 8 ++++---- docs/ref/class-based-views/mixins-editing.txt | 15 ++++++++++++--- docs/releases/5.2.txt | 5 +++++ tests/generic_views/test_edit.py | 4 ++-- tests/generic_views/views.py | 4 ---- 7 files changed, 32 insertions(+), 14 deletions(-) diff --git a/AUTHORS b/AUTHORS index 573a030ea1..35c9089108 100644 --- a/AUTHORS +++ b/AUTHORS @@ -233,6 +233,7 @@ answer newbie questions, and generally made Django that much better: Chris Wilson Ciaran McCormick Claude Paroz + Clifford Gama Clint Ecker colin@owlfish.com Colin Wood diff --git a/django/views/generic/edit.py b/django/views/generic/edit.py index 79c844ea45..4b29f636fb 100644 --- a/django/views/generic/edit.py +++ b/django/views/generic/edit.py @@ -1,5 +1,5 @@ from django.core.exceptions import ImproperlyConfigured -from django.forms import Form +from django.forms import Form, ModelForm from django.forms import models as model_forms from django.http import HttpResponseRedirect from django.views.generic.base import ContextMixin, TemplateResponseMixin, View @@ -113,6 +113,13 @@ class ModelFormMixin(FormMixin, SingleObjectMixin): kwargs.update({"instance": self.object}) return kwargs + def get_model(self): + model = super().get_model() + if model is None and self.form_class is not None: + if issubclass(self.form_class, ModelForm): + model = self.form_class.Meta.model + return model + def get_success_url(self): """Return the URL to redirect to after processing a valid form.""" if self.success_url: diff --git a/docs/ref/class-based-views/flattened-index.txt b/docs/ref/class-based-views/flattened-index.txt index 99026d4bf7..016712d752 100644 --- a/docs/ref/class-based-views/flattened-index.txt +++ b/docs/ref/class-based-views/flattened-index.txt @@ -212,7 +212,7 @@ Editing views * :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.ModelFormMixin.get_form_class`] * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] -* :attr:`~django.views.generic.detail.SingleObjectMixin.model` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_model`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.model` [:meth:`~django.views.generic.edit.ModelFormMixin.get_model`] * :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` * :attr:`~django.views.generic.edit.FormMixin.prefix` [:meth:`~django.views.generic.edit.FormMixin.get_prefix`] * :attr:`~django.views.generic.detail.SingleObjectMixin.query_pk_and_slug` @@ -236,7 +236,7 @@ Editing views * :meth:`~django.views.generic.edit.FormMixin.get_context_data` * :meth:`~django.views.generic.edit.FormMixin.get_form` * :meth:`~django.views.generic.edit.ModelFormMixin.get_form_kwargs` -* :meth:`~django.views.generic.detail.SingleObjectMixin.get_model` +* :meth:`~django.views.generic.edit.ModelFormMixin.get_model` * :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` * ``head()`` * :meth:`~django.views.generic.base.View.http_method_not_allowed` @@ -259,7 +259,7 @@ Editing views * :attr:`~django.views.generic.edit.FormMixin.form_class` [:meth:`~django.views.generic.edit.ModelFormMixin.get_form_class`] * :attr:`~django.views.generic.base.View.http_method_names` * :attr:`~django.views.generic.edit.FormMixin.initial` [:meth:`~django.views.generic.edit.FormMixin.get_initial`] -* :attr:`~django.views.generic.detail.SingleObjectMixin.model` [:meth:`~django.views.generic.detail.SingleObjectMixin.get_model`] +* :attr:`~django.views.generic.detail.SingleObjectMixin.model` [:meth:`~django.views.generic.edit.ModelFormMixin.get_model`] * :attr:`~django.views.generic.detail.SingleObjectMixin.pk_url_kwarg` * :attr:`~django.views.generic.edit.FormMixin.prefix` [:meth:`~django.views.generic.edit.FormMixin.get_prefix`] * :attr:`~django.views.generic.detail.SingleObjectMixin.query_pk_and_slug` @@ -283,7 +283,7 @@ Editing views * :meth:`~django.views.generic.edit.FormMixin.get_context_data` * :meth:`~django.views.generic.edit.FormMixin.get_form` * :meth:`~django.views.generic.edit.ModelFormMixin.get_form_kwargs` -* :meth:`~django.views.generic.detail.SingleObjectMixin.get_model` +* :meth:`~django.views.generic.edit.ModelFormMixin.get_model` * :meth:`~django.views.generic.detail.SingleObjectMixin.get_object` * ``head()`` * :meth:`~django.views.generic.base.View.http_method_not_allowed` diff --git a/docs/ref/class-based-views/mixins-editing.txt b/docs/ref/class-based-views/mixins-editing.txt index aebbae04ae..fb8a922538 100644 --- a/docs/ref/class-based-views/mixins-editing.txt +++ b/docs/ref/class-based-views/mixins-editing.txt @@ -121,9 +121,10 @@ The following mixins are used to construct Django's editing views: .. attribute:: model - A model class. Can be explicitly provided, otherwise will be determined - by examining ``self.object`` or - :attr:`~django.views.generic.detail.SingleObjectMixin.queryset`. + A model class. It can be explicitly provided; otherwise, it will be + determined by examining ``self.object``, + :attr:`~django.views.generic.detail.SingleObjectMixin.queryset`, or the + value returned by :meth:`~django.views.generic.edit.ModelFormMixin.get_model`. .. attribute:: fields @@ -159,6 +160,14 @@ The following mixins are used to construct Django's editing views: Add the current instance (``self.object``) to the standard :meth:`~django.views.generic.edit.FormMixin.get_form_kwargs`. + .. method:: get_model() + + .. versionadded:: 5.2 + + Return ``self.model`` if specified, or the model defined in the + ``form_class``'s ``Meta`` attribute if ``form_class`` is provided as + a ``ModelForm``. + .. method:: get_success_url() Determine the URL to redirect to when the form is successfully diff --git a/docs/releases/5.2.txt b/docs/releases/5.2.txt index ec84f3748f..113a73c42d 100644 --- a/docs/releases/5.2.txt +++ b/docs/releases/5.2.txt @@ -231,6 +231,11 @@ Generic Views overridden. If ``queryset`` is provided, it takes precedence as the source of objects. +* :meth:`~django.views.generic.edit.ModelFormMixin.get_model` is overridden to + return the model from the ``ModelForm``'s ``Meta`` class when the view's + ``model`` attribute is not defined, allowing for simplified form-based edit + views using only ``form_class``. + Internationalization ~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py index 09d887ae92..56784bdaab 100644 --- a/tests/generic_views/test_edit.py +++ b/tests/generic_views/test_edit.py @@ -164,7 +164,7 @@ class CreateViewTests(TestCase): self.assertIsInstance(res.context["form"], views.AuthorForm) self.assertNotIn("object", res.context) self.assertNotIn("author", res.context) - self.assertTemplateUsed(res, "generic_views/form.html") + self.assertTemplateUsed(res, "generic_views/author_form.html") res = self.client.post( "/edit/authors/create/special/", @@ -324,7 +324,7 @@ class UpdateViewTests(TestCase): self.assertEqual(res.context["object"], self.author) self.assertEqual(res.context["thingy"], self.author) self.assertNotIn("author", res.context) - self.assertTemplateUsed(res, "generic_views/form.html") + self.assertTemplateUsed(res, "generic_views/author_form.html") res = self.client.post( "/edit/author/%d/update/special/" % self.author.pk, diff --git a/tests/generic_views/views.py b/tests/generic_views/views.py index baf741f9dc..3360bb2b0d 100644 --- a/tests/generic_views/views.py +++ b/tests/generic_views/views.py @@ -144,9 +144,7 @@ class AuthorCreate(generic.CreateView): class SpecializedAuthorCreate(generic.CreateView): - model = Author form_class = AuthorForm - template_name = "generic_views/form.html" context_object_name = "thingy" def get_success_url(self): @@ -187,9 +185,7 @@ class OneAuthorUpdate(generic.UpdateView): class SpecializedAuthorUpdate(generic.UpdateView): - model = Author form_class = AuthorForm - template_name = "generic_views/form.html" context_object_name = "thingy" def get_success_url(self):