Avoid importing document model class from wagtail.documents.permissions and wagtail.documents.views.chooser

Fixes #9118. Permission policies can now be initialised by passing a model string rather than a class; wagtail.admin.widgets.chooser.BaseChooser had this capability already. Between these, we can adjust wagtail.documents.views.chooser so that no models need to be imported at module load time. As a result, definitions that depend on this module (such as DocumentChooserBlock) can now be included in the same models file as a custom document model, without causing a circular import.
pull/9136/head
Matt Westcott 2022-09-01 15:04:10 +01:00
rodzic 6433aebe6b
commit 1829ed3dbe
6 zmienionych plików z 47 dodań i 14 usunięć

Wyświetl plik

@ -22,6 +22,7 @@ Changelog
* Fix: On the Locked Pages report, limit the "locked by" filter to just users who have locked pages (Stefan Hammer)
* Fix: Prevent JavaScript error when using StreamField on views without commenting support, such as snippets (Jacob Topp-Mugglestone)
* Fix: Modify base template for new projects so that links opened from the preview panel open in a new window (Sage Abdullah)
* Fix: Prevent circular import error between custom document models and document chooser blocks (Matt Westcott)
4.0 (31.08.2022)

Wyświetl plik

@ -16,3 +16,4 @@ depth: 1
* On the Locked Pages report, limit the "locked by" filter to just users who have locked pages (Stefan Hammer)
* Prevent JavaScript error when using StreamField on views without commenting support, such as snippets (Jacob Topp-Mugglestone)
* Modify base template for new projects so that links opened from the preview panel open in a new window (Sage Abdullah)
* Prevent circular import error between custom document models and document chooser blocks (Matt Westcott)

Wyświetl plik

@ -1,7 +1,8 @@
from wagtail.documents import get_document_model
from wagtail.documents.models import Document
from wagtail.documents import get_document_model_string
from wagtail.permission_policies.collections import CollectionOwnershipPermissionPolicy
permission_policy = CollectionOwnershipPermissionPolicy(
get_document_model(), auth_model=Document, owner_field_name="uploaded_by_user"
get_document_model_string(),
auth_model="wagtaildocs.Document",
owner_field_name="uploaded_by_user",
)

Wyświetl plik

@ -21,7 +21,7 @@ from wagtail.admin.views.generic.chooser import (
from wagtail.admin.viewsets.chooser import ChooserViewSet
from wagtail.admin.widgets import BaseChooser, BaseChooserAdapter
from wagtail.blocks import ChooserBlock
from wagtail.documents import get_document_model
from wagtail.documents import get_document_model, get_document_model_string
from wagtail.documents.permissions import permission_policy
@ -153,7 +153,7 @@ class BaseAdminDocumentChooser(BaseChooser):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.model = get_document_model()
self.model = get_document_model_string()
def render_js_init(self, id_, name, value_data):
return "new DocumentChooser({0});".format(json.dumps(id_))
@ -206,6 +206,6 @@ class DocumentChooserViewSet(ChooserViewSet):
viewset = DocumentChooserViewSet(
"wagtaildocs_chooser",
model=get_document_model(),
model=get_document_model_string(),
url_prefix="documents/chooser",
)

Wyświetl plik

@ -5,6 +5,8 @@ from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
from django.db.models import Q
from django.utils.functional import cached_property
from wagtail.coreutils import resolve_model_string
class BasePermissionPolicy:
"""
@ -25,7 +27,19 @@ class BasePermissionPolicy:
"""
def __init__(self, model):
self.model = model
self._model_or_name = model
@cached_property
def model(self):
model = resolve_model_string(self._model_or_name)
self.check_model(model)
return model
def check_model(self, model):
# a hook that is called at the point that the model argument (which may be a string
# rather than a model class) is resolved to a model class, for subclasses to perform
# any necessary validation checks on that model class
pass
# Basic user permission tests. Most policies are expected to override these,
# since the default implementation is to query the set of permitted users
@ -175,9 +189,19 @@ class BaseDjangoAuthPermissionPolicy(BasePermissionPolicy):
# records might use a custom User model but will typically still refer to the
# permission records for auth.user.
super().__init__(model)
self.auth_model = auth_model or self.model
self.app_label = self.auth_model._meta.app_label
self.model_name = self.auth_model._meta.model_name
self._auth_model_or_name = auth_model or model
@cached_property
def auth_model(self):
return resolve_model_string(self._auth_model_or_name)
@cached_property
def app_label(self):
return self.auth_model._meta.app_label
@cached_property
def model_name(self):
return self.auth_model._meta.model_name
@cached_property
def _content_type(self):
@ -255,14 +279,17 @@ class OwnershipPermissionPolicy(BaseDjangoAuthPermissionPolicy):
super().__init__(model, auth_model=auth_model)
self.owner_field_name = owner_field_name
def check_model(self, model):
super().check_model(model)
# make sure owner_field_name is a field that exists on the model
try:
self.model._meta.get_field(self.owner_field_name)
model._meta.get_field(self.owner_field_name)
except FieldDoesNotExist:
raise ImproperlyConfigured(
"%s has no field named '%s'. To use this model with OwnershipPermissionPolicy, "
"you must specify a valid field name as owner_field_name."
% (self.model, self.owner_field_name)
% (model, self.owner_field_name)
)
def user_has_permission(self, user, action):

Wyświetl plik

@ -218,14 +218,17 @@ class CollectionOwnershipPermissionPolicy(
super().__init__(model, auth_model=auth_model)
self.owner_field_name = owner_field_name
def check_model(self, model):
super().check_model(model)
# make sure owner_field_name is a field that exists on the model
try:
self.model._meta.get_field(self.owner_field_name)
model._meta.get_field(self.owner_field_name)
except FieldDoesNotExist:
raise ImproperlyConfigured(
"%s has no field named '%s'. To use this model with "
"CollectionOwnershipPermissionPolicy, you must specify a valid field name as "
"owner_field_name." % (self.model, self.owner_field_name)
"owner_field_name." % (model, self.owner_field_name)
)
def user_has_permission(self, user, action):