Support passing model to chooser viewset / views / widgets as a string

pull/8921/head
Matt Westcott 2022-07-07 17:44:31 +01:00 zatwierdzone przez LB (Ben Johnston)
rodzic a40b82fc05
commit 89e2917b91
4 zmienionych plików z 45 dodań i 18 usunięć

Wyświetl plik

@ -10,6 +10,7 @@ from django.http import Http404
from django.template.loader import render_to_string
from django.template.response import TemplateResponse
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import ContextMixin, View
@ -23,6 +24,7 @@ from wagtail.admin.forms.choosers import (
)
from wagtail.admin.modal_workflow import render_modal_workflow
from wagtail.admin.ui.tables import Table, TitleColumn
from wagtail.coreutils import resolve_model_string
from wagtail.models import CollectionMember, TranslatableMixin
from wagtail.permission_policies import BlanketPermissionPolicy, ModelPermissionPolicy
from wagtail.search.index import class_is_indexed
@ -49,13 +51,25 @@ class ModalPageFurnitureMixin(ContextMixin):
return context
class BaseChooseView(ModalPageFurnitureMixin, ContextMixin, View):
class ModelLookupMixin:
"""
Allows a class to have a `model` attribute, which can be set as either a model class or a string,
and then retrieve it as `model_class` to consistently get back a model class
"""
model = None
@cached_property
def model_class(self):
return resolve_model_string(self.model)
class BaseChooseView(ModalPageFurnitureMixin, ModelLookupMixin, ContextMixin, View):
"""
Provides common functionality for views that present a (possibly searchable / filterable) list
of objects to choose from
"""
model = None
per_page = 10
ordering = None
chosen_url_name = None
@ -68,7 +82,7 @@ class BaseChooseView(ModalPageFurnitureMixin, ContextMixin, View):
construct_queryset_hook_name = None
def get_object_list(self):
return self.model.objects.all()
return self.model_class.objects.all()
def apply_object_list_ordering(self, objects):
if isinstance(self.ordering, (list, tuple)):
@ -89,11 +103,11 @@ class BaseChooseView(ModalPageFurnitureMixin, ContextMixin, View):
return self.filter_form_class
else:
bases = [BaseFilterForm]
if class_is_indexed(self.model):
if class_is_indexed(self.model_class):
bases.insert(0, SearchFilterMixin)
if issubclass(self.model, CollectionMember):
if issubclass(self.model_class, CollectionMember):
bases.insert(0, CollectionFilterMixin)
if issubclass(self.model, TranslatableMixin):
if issubclass(self.model_class, TranslatableMixin):
bases.insert(0, LocaleFilterMixin)
return type(
@ -162,7 +176,7 @@ class BaseChooseView(ModalPageFurnitureMixin, ContextMixin, View):
raise NotImplementedError()
class CreationFormMixin:
class CreationFormMixin(ModelLookupMixin):
"""
Provides a form class for creating new objects
"""
@ -180,8 +194,8 @@ class CreationFormMixin:
def get_permission_policy(self):
if self.permission_policy:
return self.permission_policy
elif self.model:
return ModelPermissionPolicy(self.model)
elif self.model_class:
return ModelPermissionPolicy(self.model_class)
else:
return BlanketPermissionPolicy(None)
@ -195,7 +209,9 @@ class CreationFormMixin:
return self.creation_form_class
elif self.form_fields is not None or self.exclude_form_fields is not None:
return modelform_factory(
self.model, fields=self.form_fields, exclude=self.exclude_form_fields
self.model_class,
fields=self.form_fields,
exclude=self.exclude_form_fields,
)
def get_creation_form_kwargs(self):
@ -344,16 +360,14 @@ class ChosenResponseMixin:
)
class ChosenViewMixin:
class ChosenViewMixin(ModelLookupMixin):
"""
A view that takes an object ID in the URL and returns a modal workflow response indicating
that object has been chosen
"""
model = None
def get_object(self, pk):
return self.model.objects.get(pk=pk)
return self.model_class.objects.get(pk=pk)
def get(self, request, pk):
try:

Wyświetl plik

@ -124,8 +124,13 @@ class ChooserViewSet(ViewSet):
"""
Returns the form widget class for this chooser.
"""
if isinstance(self.model, str):
model_name = self.model.split(".")[-1]
else:
model_name = self.model.__name__
return type(
"%sChooserWidget" % self.model.__name__,
"%sChooserWidget" % model_name,
(self.base_widget_class,),
{
"model": self.model,

Wyświetl plik

@ -6,6 +6,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.forms import widgets
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
@ -99,6 +100,7 @@ class BaseChooser(widgets.Input):
)
icon = None
classname = None
model = None
# when looping over form fields, this one should appear in visible_fields, not hidden_fields
# despite the underlying input being type="hidden"
@ -121,6 +123,10 @@ class BaseChooser(widgets.Input):
self.show_clear_link = kwargs.pop("show_clear_link")
super().__init__(**kwargs)
@cached_property
def model_class(self):
return resolve_model_string(self.model)
def value_from_datadict(self, data, files, name):
# treat the empty string as None
result = super().value_from_datadict(data, files, name)
@ -176,10 +182,10 @@ class BaseChooser(widgets.Input):
"""
if value is None:
return None
elif isinstance(value, self.model):
elif isinstance(value, self.model_class):
return value
else: # assume instance ID
return self.model.objects.get(pk=value)
return self.model_class.objects.get(pk=value)
def get_display_title(self, instance):
"""

Wyświetl plik

@ -1,6 +1,8 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from wagtail.coreutils import resolve_model_string
class ObjectTypeRegistry:
"""
@ -66,7 +68,7 @@ class ModelFieldRegistry(ObjectTypeRegistry):
def register(self, field_class, to=None, value=None, exact_class=False):
if to:
if field_class == models.ForeignKey:
self.values_by_fk_related_model[to] = value
self.values_by_fk_related_model[resolve_model_string(to)] = value
else:
raise ImproperlyConfigured(
"The 'to' argument on ModelFieldRegistry.register is only valid for ForeignKey fields"