kopia lustrzana https://github.com/wagtail/wagtail
Support passing model to chooser viewset / views / widgets as a string
rodzic
a40b82fc05
commit
89e2917b91
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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"
|
||||
|
|
Ładowanie…
Reference in New Issue