1
0
mirror of https://github.com/django/django.git synced 2025-03-29 02:30:48 +00:00

Refs #26220 -- Added SingleObjectMixin._get_model().

This commit is contained in:
Clifford Gama 2024-12-20 13:49:44 +02:00
parent 43287cbb87
commit 9f716580c4
No known key found for this signature in database
GPG Key ID: BF895AA45E520E21
7 changed files with 96 additions and 20 deletions

View File

@ -233,6 +233,7 @@ answer newbie questions, and generally made Django that much better:
Chris Wilson <chris+github@qwirx.com>
Ciaran McCormick <ciaran@ciaranmccormick.com>
Claude Paroz <claude@2xlibre.net>
Clifford Gama <cliffygamy@gmail.com>
Clint Ecker
colin@owlfish.com
Colin Wood <cwood06@gmail.com>

View File

@ -66,8 +66,8 @@ class SingleObjectMixin(ContextMixin):
may not be called if get_object() is overridden.
"""
if self.queryset is None:
if self.model:
return self.model._default_manager.all()
if (model := self._get_model()) is not None:
return model._default_manager.all()
else:
raise ImproperlyConfigured(
"%(cls)s is missing a QuerySet. Define "
@ -76,6 +76,13 @@ class SingleObjectMixin(ContextMixin):
)
return self.queryset.all()
def _get_model(self):
"""Return the model of the object the view is displaying."""
model = self.model
if model is None and getattr(self, "object", None) is not None:
return self.object.__class__
return model
def get_slug_field(self):
"""Get the name of a slug field to be used to look up by slug."""
return self.slug_field
@ -155,14 +162,14 @@ class SingleObjectTemplateResponseMixin(TemplateResponseMixin):
self.template_name_suffix,
)
)
elif getattr(self, "model", None) is not None and issubclass(
self.model, models.Model
elif (model := getattr(self, "_get_model", lambda: None)()) and issubclass(
model, models.Model
):
names.append(
"%s/%s%s.html"
% (
self.model._meta.app_label,
self.model._meta.model_name,
model._meta.app_label,
model._meta.model_name,
self.template_name_suffix,
)
)

View File

@ -87,18 +87,7 @@ class ModelFormMixin(FormMixin, SingleObjectMixin):
if self.form_class:
return self.form_class
else:
if self.model is not None:
# If a model has been explicitly provided, use it
model = self.model
elif getattr(self, "object", None) is not None:
# If this view is operating on a single object, use
# the class of that object
model = self.object.__class__
else:
# Try to get a queryset and extract the model class
# from that
model = self.get_queryset().model
model = self._get_model() or self.get_queryset().model
if self.fields is None:
raise ImproperlyConfigured(
"Using ModelFormMixin (base class of %s) without "

View File

@ -226,7 +226,10 @@ Forms
Generic Views
~~~~~~~~~~~~~
* ...
* The new :meth:`~django.views.generic.detail.SingleObjectMixin._get_model`
method returns the model defined for the view, or a custom model if
overridden. If ``queryset`` is provided, it takes precedence as the source
of objects.
Internationalization
~~~~~~~~~~~~~~~~~~~~

View File

@ -219,3 +219,48 @@ class DetailViewTest(TestCase):
res = self.client.get("/detail/nonmodel/1/")
self.assertEqual(res.status_code, 200)
self.assertEqual(res.context["object"].id, "non_model_1")
def test_get_model_override(self):
res = self.client.get("/detail/author/getmodel/%s/" % self.author1.pk)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.context["object"], self.author1)
self.assertTemplateUsed(res, "generic_views/author_detail.html")
def test_invalid_model_configuration(self):
msg = (
"MissingModelView is missing a QuerySet. Define MissingModelView.model, "
"MissingModelView.queryset, or override MissingModelView.get_queryset()."
)
with self.assertRaisesMessage(ImproperlyConfigured, msg):
self.client.get("/missing-model/")
def test_conflicting_model_and_get_model(self):
"""
`_get_model` takes precedence over model.
"""
url = "/detail/conflicting-model-and-getmodel/%s/" % self.author1.pk
res = self.client.get(url)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.context["object"], self.author1)
self.assertTemplateUsed(res, "generic_views/author_detail.html")
def test_model_and_queryset_ignores_model(self):
"""
Queryset takes precedence over model as source of objects.
"""
url = "/detail/author/conflicting-model-and-queryset/%s/" % self.author1.pk
res = self.client.get(url)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.context["object"], self.author1)
self.assertTemplateUsed(res, "generic_views/author_detail.html")
def test_queryset_and_get_model_ignores_get_model(self):
"""
When queryset is defined, it takes precedence over `_get_model` as
source of model objects.
"""
url = "/detail/conflicting-queryset-and-getmodel/%s/" % self.artist1.pk
res = self.client.get(url)
self.assertEqual(res.status_code, 200)
self.assertEqual(res.context["object"], self.artist1)
self.assertTemplateUsed(res, "generic_views/artist_detail.html")

View File

@ -5,7 +5,7 @@ from django.views.decorators.cache import cache_page
from django.views.generic import TemplateView, dates
from . import views
from .models import Book
from .models import Artist, Book
urlpatterns = [
# TemplateView
@ -75,6 +75,23 @@ urlpatterns = [
path("detail/author/invalid/qs/", views.AuthorDetail.as_view(queryset=None)),
path("detail/nonmodel/1/", views.NonModelDetail.as_view()),
path("detail/doesnotexist/<pk>/", views.ObjectDoesNotExistDetail.as_view()),
path(
"detail/author/getmodel/<int:pk>/",
views.AuthorDetailOverridesGetModel.as_view(),
),
path("missing-model/", views.MissingModelView.as_view()),
path(
"detail/conflicting-model-and-getmodel/<int:pk>/",
views.AuthorDetailOverridesGetModel.as_view(model=Artist),
),
path(
"detail/author/conflicting-model-and-queryset/<int:pk>/",
views.AuthorDetailConflictingModelAndQueryset.as_view(),
),
path(
"detail/conflicting-queryset-and-getmodel/<int:pk>/",
views.AuthorDetailOverridesGetModel.as_view(queryset=Artist.objects.all()),
),
# FormView
path("contact/", views.ContactView.as_view()),
path("late-validation/", views.LateValidationView.as_view()),

View File

@ -43,6 +43,20 @@ class AuthorCustomDetail(generic.DetailView):
return self.render_to_response(context)
class AuthorDetailOverridesGetModel(generic.DetailView):
def _get_model(self):
return Author
class AuthorDetailConflictingModelAndQueryset(generic.DetailView):
queryset = Author.objects.all()
model = Artist
class MissingModelView(generic.DetailView):
pass
class PageDetail(generic.DetailView):
queryset = Page.objects.all()
template_name_field = "template"