Initial commit
This commit is contained in:
0
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__init__.py
vendored
Normal file
0
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__init__.py
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/__init__.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/choose_parent.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/choose_parent.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/convert_alias.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/convert_alias.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/copy.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/copy.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/create.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/create.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/delete.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/delete.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/edit.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/edit.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/history.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/history.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/listing.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/listing.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/lock.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/lock.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/move.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/move.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/ordering.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/ordering.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/preview.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/preview.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/revisions.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/revisions.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/search.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/search.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/unpublish.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/unpublish.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/usage.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/usage.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/utils.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/utils.cpython-310.pyc
vendored
Normal file
Binary file not shown.
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/workflow.cpython-310.pyc
vendored
Normal file
BIN
env/lib/python3.10/site-packages/wagtail/admin/views/pages/__pycache__/workflow.cpython-310.pyc
vendored
Normal file
Binary file not shown.
11
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/__init__.py
vendored
Normal file
11
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/__init__.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
from .delete import DeleteBulkAction
|
||||
from .move import MoveBulkAction
|
||||
from .publish import PublishBulkAction
|
||||
from .unpublish import UnpublishBulkAction
|
||||
|
||||
__all__ = [
|
||||
"DeleteBulkAction",
|
||||
"MoveBulkAction",
|
||||
"PublishBulkAction",
|
||||
"UnpublishBulkAction",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
57
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/delete.py
vendored
Normal file
57
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/delete.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
|
||||
|
||||
|
||||
class DeleteBulkAction(PageBulkAction):
|
||||
display_name = _("Delete")
|
||||
action_type = "delete"
|
||||
aria_label = _("Delete selected pages")
|
||||
template_name = "wagtailadmin/pages/bulk_actions/confirm_bulk_delete.html"
|
||||
action_priority = 30
|
||||
classes = {"serious"}
|
||||
|
||||
def check_perm(self, page):
|
||||
return page.permissions_for_user(self.request.user).can_delete()
|
||||
|
||||
def object_context(self, page):
|
||||
return {
|
||||
"item": page,
|
||||
"descendant_count": page.get_descendant_count(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def execute_action(cls, objects, user=None, **kwargs):
|
||||
num_parent_objects, num_child_objects = 0, 0
|
||||
for page in objects:
|
||||
num_parent_objects += 1
|
||||
num_child_objects += page.get_descendant_count()
|
||||
page.delete(user=user)
|
||||
return num_parent_objects, num_child_objects
|
||||
|
||||
def get_success_message(self, num_parent_objects, num_child_objects):
|
||||
if num_parent_objects == 1:
|
||||
if num_child_objects == 0:
|
||||
success_message = _("1 page has been deleted")
|
||||
else:
|
||||
success_message = ngettext(
|
||||
"1 page and %(num_child_objects)d child page have been deleted",
|
||||
"1 page and %(num_child_objects)d child pages have been deleted",
|
||||
num_child_objects,
|
||||
) % {"num_child_objects": num_child_objects}
|
||||
else:
|
||||
if num_child_objects == 0:
|
||||
success_message = _(
|
||||
"%(num_parent_objects)d pages have been deleted"
|
||||
) % {"num_parent_objects": num_parent_objects}
|
||||
else:
|
||||
success_message = ngettext(
|
||||
"%(num_parent_objects)d pages and %(num_child_objects)d child page have been deleted",
|
||||
"%(num_parent_objects)d pages and %(num_child_objects)d child pages have been deleted",
|
||||
num_child_objects,
|
||||
) % {
|
||||
"num_child_objects": num_child_objects,
|
||||
"num_parent_objects": num_parent_objects,
|
||||
}
|
||||
return success_message
|
||||
158
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/move.py
vendored
Normal file
158
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/move.py
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
from django import forms
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
from wagtail.admin import widgets
|
||||
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
class MoveForm(forms.Form):
|
||||
def __init__(self, *args, **kwargs):
|
||||
destination = kwargs.pop("destination")
|
||||
target_parent_models = kwargs.pop("target_parent_models")
|
||||
pages_to_move = kwargs.pop("pages_to_move")
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["chooser"] = forms.ModelChoiceField(
|
||||
initial=destination,
|
||||
queryset=Page.objects.all(),
|
||||
widget=widgets.AdminPageMoveChooser(
|
||||
can_choose_root=True,
|
||||
user_perms="bulk_move_to",
|
||||
target_models=target_parent_models,
|
||||
pages_to_move=pages_to_move,
|
||||
),
|
||||
label=_("Select a new parent page"),
|
||||
)
|
||||
|
||||
|
||||
class MoveBulkAction(PageBulkAction):
|
||||
display_name = _("Move")
|
||||
action_type = "move"
|
||||
aria_label = _("Move selected pages")
|
||||
template_name = "wagtailadmin/pages/bulk_actions/confirm_bulk_move.html"
|
||||
action_priority = 10
|
||||
form_class = MoveForm
|
||||
destination = None
|
||||
|
||||
def __init__(self, request, model):
|
||||
super().__init__(request, model)
|
||||
self.target_parent_models = set()
|
||||
self.pages_to_move = []
|
||||
|
||||
def get_form_kwargs(self):
|
||||
ctx = super().get_form_kwargs()
|
||||
ctx["destination"] = self.destination or Page.get_first_root_node()
|
||||
ctx["target_parent_models"] = self.target_parent_models
|
||||
ctx["pages_to_move"] = self.pages_to_move
|
||||
return ctx
|
||||
|
||||
def check_perm(self, page):
|
||||
return page.permissions_for_user(self.request.user).can_move()
|
||||
|
||||
def get_success_message(self, num_parent_objects, num_child_objects):
|
||||
success_message = ngettext(
|
||||
"%(num_pages)d page has been moved",
|
||||
"%(num_pages)d pages have been moved",
|
||||
num_parent_objects,
|
||||
) % {"num_pages": num_parent_objects}
|
||||
return success_message
|
||||
|
||||
def object_context(self, obj):
|
||||
context = super().object_context(obj)
|
||||
context["child_pages"] = context["item"].get_descendants().count()
|
||||
return context
|
||||
|
||||
def get_actionable_objects(self):
|
||||
objects, objects_without_access = super().get_actionable_objects()
|
||||
request = self.request
|
||||
|
||||
if objects:
|
||||
self.target_parent_models = set(
|
||||
objects[0].specific_class.allowed_parent_page_models()
|
||||
)
|
||||
for obj in objects:
|
||||
self.target_parent_models.intersection_update(
|
||||
set(obj.specific_class.allowed_parent_page_models())
|
||||
)
|
||||
|
||||
self.pages_to_move = [page.id for page in objects]
|
||||
|
||||
if self.cleaned_form is None:
|
||||
if len(self.target_parent_models) == 0:
|
||||
return [], {
|
||||
**objects_without_access,
|
||||
"pages_without_common_parent_page": [
|
||||
{
|
||||
"item": page,
|
||||
"can_edit": page.permissions_for_user(
|
||||
self.request.user
|
||||
).can_edit(),
|
||||
}
|
||||
for page in objects
|
||||
],
|
||||
}
|
||||
return objects, objects_without_access
|
||||
|
||||
destination = self.cleaned_form.cleaned_data["chooser"]
|
||||
pages = []
|
||||
pages_without_destination_access = []
|
||||
pages_with_duplicate_slugs = []
|
||||
|
||||
for page in objects:
|
||||
if not page.permissions_for_user(request.user).can_move_to(destination):
|
||||
pages_without_destination_access.append(page)
|
||||
elif not Page._slug_is_available(page.slug, destination, page=page):
|
||||
pages_with_duplicate_slugs.append(page)
|
||||
else:
|
||||
pages.append(page)
|
||||
|
||||
return pages, {
|
||||
**objects_without_access,
|
||||
"pages_without_destination_access": [
|
||||
{
|
||||
"item": page,
|
||||
"can_edit": page.permissions_for_user(self.request.user).can_edit(),
|
||||
}
|
||||
for page in pages_without_destination_access
|
||||
],
|
||||
"pages_with_duplicate_slugs": [
|
||||
{
|
||||
"item": page,
|
||||
"can_edit": page.permissions_for_user(self.request.user).can_edit(),
|
||||
}
|
||||
for page in pages_with_duplicate_slugs
|
||||
],
|
||||
}
|
||||
|
||||
def prepare_action(self, pages, pages_without_access):
|
||||
request = self.request
|
||||
destination = self.cleaned_form.cleaned_data["chooser"]
|
||||
if (
|
||||
pages_without_access["pages_without_destination_access"]
|
||||
or pages_without_access["pages_with_duplicate_slugs"]
|
||||
):
|
||||
# this will be picked up by the form
|
||||
self.destination = destination
|
||||
return TemplateResponse(
|
||||
request,
|
||||
self.template_name,
|
||||
{"destination": destination, **self.get_context_data()},
|
||||
)
|
||||
|
||||
def get_execution_context(self):
|
||||
return {
|
||||
**super().get_execution_context(),
|
||||
"destination": self.cleaned_form.cleaned_data["chooser"],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def execute_action(cls, objects, destination=None, user=None, **kwargs):
|
||||
num_parent_objects = 0
|
||||
if destination is None:
|
||||
return
|
||||
for page in objects:
|
||||
page.move(destination, pos="last-child", user=user)
|
||||
num_parent_objects += 1
|
||||
return num_parent_objects, 0
|
||||
57
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/page_bulk_action.py
vendored
Normal file
57
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/page_bulk_action.py
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
from django import forms
|
||||
|
||||
from wagtail.admin.views.bulk_action import BulkAction
|
||||
from wagtail.admin.views.pages.search import page_filter_search
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
class DefaultPageForm(forms.Form):
|
||||
include_descendants = forms.BooleanField(required=False)
|
||||
|
||||
|
||||
class PageBulkAction(BulkAction):
|
||||
models = [Page]
|
||||
form_class = DefaultPageForm
|
||||
|
||||
def get_all_objects_in_listing_query(self, parent_id):
|
||||
listing_objects = self.model.objects.all()
|
||||
|
||||
q = None
|
||||
if "q" in self.request.GET:
|
||||
q = self.request.GET.get("q", "")
|
||||
|
||||
if parent_id is not None:
|
||||
listing_objects = listing_objects.get(id=parent_id)
|
||||
# If we're searching, include the descendants as well.
|
||||
# Otherwise, just include the direct children.
|
||||
if q:
|
||||
listing_objects = listing_objects.get_descendants()
|
||||
else:
|
||||
listing_objects = listing_objects.get_children()
|
||||
|
||||
listing_objects = listing_objects.values_list("pk", flat=True)
|
||||
|
||||
if q:
|
||||
listing_objects = page_filter_search(q, listing_objects)[0].results()
|
||||
|
||||
return listing_objects
|
||||
|
||||
def object_context(self, obj):
|
||||
context = super().object_context(obj)
|
||||
# Make 'item' into the specific instance, so that custom get_admin_display_title methods are respected
|
||||
context["item"] = context["item"].specific_deferred
|
||||
return context
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["items_with_no_access"] = [
|
||||
{
|
||||
"item": page,
|
||||
"can_edit": page.permissions_for_user(self.request.user).can_edit(),
|
||||
}
|
||||
for page in context["items_with_no_access"]
|
||||
]
|
||||
return context
|
||||
|
||||
def get_execution_context(self):
|
||||
return {"user": self.request.user}
|
||||
105
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/publish.py
vendored
Normal file
105
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/publish.py
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
|
||||
|
||||
|
||||
class PublishBulkAction(PageBulkAction):
|
||||
display_name = _("Publish")
|
||||
action_type = "publish"
|
||||
aria_label = _("Publish selected pages")
|
||||
template_name = "wagtailadmin/pages/bulk_actions/confirm_bulk_publish.html"
|
||||
action_priority = 40
|
||||
|
||||
def check_perm(self, page):
|
||||
return page.permissions_for_user(self.request.user).can_publish()
|
||||
|
||||
def object_context(self, obj):
|
||||
context = super().object_context(obj)
|
||||
context["draft_descendant_count"] = (
|
||||
context["item"].get_descendants().not_live().count()
|
||||
)
|
||||
return context
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["has_draft_descendants"] = any(
|
||||
item["draft_descendant_count"] for item in context["items"]
|
||||
)
|
||||
return context
|
||||
|
||||
def get_execution_context(self):
|
||||
return {
|
||||
**super().get_execution_context(),
|
||||
"include_descendants": self.cleaned_form.cleaned_data[
|
||||
"include_descendants"
|
||||
],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def execute_action(cls, objects, include_descendants=False, user=None, **kwargs):
|
||||
num_parent_objects, num_child_objects = 0, 0
|
||||
for page in objects:
|
||||
revision = page.get_latest_revision() or page.specific.save_revision(
|
||||
user=user
|
||||
)
|
||||
revision.publish(user=user)
|
||||
num_parent_objects += 1
|
||||
|
||||
if include_descendants:
|
||||
for draft_descendant_page in (
|
||||
page.get_descendants()
|
||||
.not_live()
|
||||
.defer_streamfields()
|
||||
.specific()
|
||||
.iterator()
|
||||
):
|
||||
if (
|
||||
user is None
|
||||
or draft_descendant_page.permissions_for_user(
|
||||
user
|
||||
).can_publish()
|
||||
):
|
||||
draft_descendant_revision = (
|
||||
draft_descendant_page.get_latest_revision()
|
||||
or draft_descendant_page.save_revision(user=user)
|
||||
)
|
||||
draft_descendant_revision.publish(user=user)
|
||||
num_child_objects += 1
|
||||
|
||||
return num_parent_objects, num_child_objects
|
||||
|
||||
def get_success_message(self, num_parent_objects, num_child_objects):
|
||||
include_descendants = self.cleaned_form.cleaned_data["include_descendants"]
|
||||
if num_parent_objects == 1:
|
||||
if include_descendants:
|
||||
if num_child_objects == 0:
|
||||
success_message = _("1 page has been published")
|
||||
else:
|
||||
success_message = ngettext(
|
||||
"1 page and %(num_child_objects)d child page have been published",
|
||||
"1 page and %(num_child_objects)d child pages have been published",
|
||||
num_child_objects,
|
||||
) % {"num_child_objects": num_child_objects}
|
||||
else:
|
||||
success_message = _("1 page has been published")
|
||||
else:
|
||||
if include_descendants:
|
||||
if num_child_objects == 0:
|
||||
success_message = _(
|
||||
"%(num_parent_objects)d pages have been published"
|
||||
) % {"num_parent_objects": num_parent_objects}
|
||||
else:
|
||||
success_message = ngettext(
|
||||
"%(num_parent_objects)d pages and %(num_child_objects)d child page have been published",
|
||||
"%(num_parent_objects)d pages and %(num_child_objects)d child pages have been published",
|
||||
num_child_objects,
|
||||
) % {
|
||||
"num_child_objects": num_child_objects,
|
||||
"num_parent_objects": num_parent_objects,
|
||||
}
|
||||
else:
|
||||
success_message = _(
|
||||
"%(num_parent_objects)d pages have been published"
|
||||
) % {"num_parent_objects": num_parent_objects}
|
||||
return success_message
|
||||
99
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/unpublish.py
vendored
Normal file
99
env/lib/python3.10/site-packages/wagtail/admin/views/pages/bulk_actions/unpublish.py
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.translation import ngettext
|
||||
|
||||
from wagtail.admin.views.pages.bulk_actions.page_bulk_action import PageBulkAction
|
||||
|
||||
|
||||
class UnpublishBulkAction(PageBulkAction):
|
||||
display_name = _("Unpublish")
|
||||
action_type = "unpublish"
|
||||
aria_label = _("Unpublish selected pages")
|
||||
template_name = "wagtailadmin/pages/bulk_actions/confirm_bulk_unpublish.html"
|
||||
action_priority = 50
|
||||
|
||||
def check_perm(self, page):
|
||||
return page.permissions_for_user(self.request.user).can_unpublish()
|
||||
|
||||
def object_context(self, page):
|
||||
return {
|
||||
**super().object_context(page),
|
||||
"live_descendant_count": page.get_descendants().live().count(),
|
||||
}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["has_live_descendants"] = any(
|
||||
item["live_descendant_count"] > 0 for item in context["items"]
|
||||
)
|
||||
return context
|
||||
|
||||
def get_execution_context(self):
|
||||
return {
|
||||
**super().get_execution_context(),
|
||||
"permission_checker": self.check_perm,
|
||||
"include_descendants": self.cleaned_form.cleaned_data[
|
||||
"include_descendants"
|
||||
],
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def execute_action(
|
||||
cls,
|
||||
objects,
|
||||
include_descendants=False,
|
||||
user=None,
|
||||
permission_checker=None,
|
||||
**kwargs,
|
||||
):
|
||||
num_parent_objects, num_child_objects = 0, 0
|
||||
for page in objects:
|
||||
page.unpublish(user=user)
|
||||
num_parent_objects += 1
|
||||
|
||||
if include_descendants:
|
||||
for live_descendant_page in (
|
||||
page.get_descendants()
|
||||
.live()
|
||||
.defer_streamfields()
|
||||
.specific()
|
||||
.iterator()
|
||||
):
|
||||
if user is None or permission_checker(live_descendant_page):
|
||||
live_descendant_page.unpublish()
|
||||
num_child_objects += 1
|
||||
return num_parent_objects, num_child_objects
|
||||
|
||||
def get_success_message(self, num_parent_objects, num_child_objects):
|
||||
include_descendants = self.cleaned_form.cleaned_data["include_descendants"]
|
||||
if num_parent_objects == 1:
|
||||
if include_descendants:
|
||||
if num_child_objects == 0:
|
||||
success_message = _("1 page has been unpublished")
|
||||
else:
|
||||
success_message = ngettext(
|
||||
"1 page and %(num_child_objects)d child page have been unpublished",
|
||||
"1 page and %(num_child_objects)d child pages have been unpublished",
|
||||
num_child_objects,
|
||||
) % {"num_child_objects": num_child_objects}
|
||||
else:
|
||||
success_message = _("1 page has been unpublished")
|
||||
else:
|
||||
if include_descendants:
|
||||
if num_child_objects == 0:
|
||||
success_message = _(
|
||||
"%(num_parent_objects)d pages have been unpublished"
|
||||
) % {"num_parent_objects": num_parent_objects}
|
||||
else:
|
||||
success_message = ngettext(
|
||||
"%(num_parent_objects)d pages and %(num_child_objects)d child page have been unpublished",
|
||||
"%(num_parent_objects)d pages and %(num_child_objects)d child pages have been unpublished",
|
||||
num_child_objects,
|
||||
) % {
|
||||
"num_child_objects": num_child_objects,
|
||||
"num_parent_objects": num_parent_objects,
|
||||
}
|
||||
else:
|
||||
success_message = _(
|
||||
"%(num_parent_objects)d pages have been unpublished"
|
||||
) % {"num_parent_objects": num_parent_objects}
|
||||
return success_message
|
||||
135
env/lib/python3.10/site-packages/wagtail/admin/views/pages/choose_parent.py
vendored
Normal file
135
env/lib/python3.10/site-packages/wagtail/admin/views/pages/choose_parent.py
vendored
Normal file
@@ -0,0 +1,135 @@
|
||||
from django.contrib.admin.utils import quote
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.text import capfirst
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy
|
||||
from django.views.generic import FormView
|
||||
|
||||
from wagtail.admin.forms.pages import ParentChooserForm
|
||||
from wagtail.admin.views.generic.base import WagtailAdminTemplateMixin
|
||||
from wagtail.models import Page
|
||||
from wagtail.permissions import page_permission_policy
|
||||
|
||||
|
||||
class ChooseParentView(WagtailAdminTemplateMixin, FormView):
|
||||
template_name = "wagtailadmin/pages/choose_parent.html"
|
||||
model = Page
|
||||
index_url_name = None
|
||||
page_title = gettext_lazy("Choose parent")
|
||||
|
||||
def get_valid_parent_pages(self, user):
|
||||
"""
|
||||
Identifies possible parent pages for the current user by first looking
|
||||
at allowed_parent_page_models() on self.model to limit options to the
|
||||
correct type of page, then checking permissions on those individual
|
||||
pages to make sure we have permission to add a subpage to it.
|
||||
"""
|
||||
# Get queryset of pages where this page type can be added
|
||||
allowed_parent_page_content_types = list(
|
||||
ContentType.objects.get_for_models(
|
||||
*self.model.allowed_parent_page_models()
|
||||
).values()
|
||||
)
|
||||
allowed_parent_pages = Page.objects.filter(
|
||||
content_type__in=allowed_parent_page_content_types
|
||||
)
|
||||
|
||||
# Get queryset of pages where the user has permission to add subpages
|
||||
if user.is_superuser:
|
||||
pages_where_user_can_add = Page.objects.all()
|
||||
else:
|
||||
pages_where_user_can_add = Page.objects.none()
|
||||
|
||||
perms = {
|
||||
perm
|
||||
for perm in page_permission_policy.get_cached_permissions_for_user(user)
|
||||
if perm.permission.codename == "add_page"
|
||||
}
|
||||
|
||||
for perm in perms:
|
||||
# user has add permission on any subpage of perm.page
|
||||
# (including perm.page itself)
|
||||
pages_where_user_can_add |= Page.objects.descendant_of(
|
||||
perm.page, inclusive=True
|
||||
)
|
||||
|
||||
# Combine them
|
||||
return allowed_parent_pages & pages_where_user_can_add
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
parents = self.get_valid_parent_pages(request.user)
|
||||
|
||||
# Only fetch the IDs for the first two parents to check if there's only
|
||||
# one valid parent, and if so, redirect to the add page view with that
|
||||
# parent pre-selected
|
||||
parent_ids = parents.values_list("pk", flat=True)[:2]
|
||||
if len(parent_ids) == 1:
|
||||
parent_id = quote(parent_ids[0])
|
||||
model_opts = self.model._meta
|
||||
return redirect(
|
||||
"wagtailadmin_pages:add",
|
||||
model_opts.app_label,
|
||||
model_opts.model_name,
|
||||
parent_id,
|
||||
)
|
||||
|
||||
# The page can be added in multiple places, so proceed with rendering
|
||||
# the view's form so that the parent can be specified
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
def get_form(self):
|
||||
if self.request.method == "POST":
|
||||
return ParentChooserForm(self.model, self.request.user, self.request.POST)
|
||||
return ParentChooserForm(self.model, self.request.user)
|
||||
|
||||
def get_index_url(self):
|
||||
if self.index_url_name:
|
||||
return reverse(self.index_url_name)
|
||||
|
||||
def get_breadcrumbs_items(self):
|
||||
items = []
|
||||
index_url = self.get_index_url()
|
||||
if index_url:
|
||||
items.append(
|
||||
{
|
||||
"url": index_url,
|
||||
"label": capfirst(self.model._meta.verbose_name_plural),
|
||||
}
|
||||
)
|
||||
items.append(
|
||||
{
|
||||
"url": "",
|
||||
"label": self.get_page_title(),
|
||||
"sublabel": self.get_page_subtitle(),
|
||||
}
|
||||
)
|
||||
|
||||
return self.breadcrumbs_items + items
|
||||
|
||||
def get_page_subtitle(self):
|
||||
return self.model.get_verbose_name()
|
||||
|
||||
@cached_property
|
||||
def submit_button_label(self):
|
||||
return _("Create a new %(model_name)s") % {
|
||||
"model_name": self.model._meta.verbose_name,
|
||||
}
|
||||
|
||||
def form_valid(self, form):
|
||||
model_opts = self.model._meta
|
||||
parent_id = quote(form.cleaned_data["parent_page"].pk)
|
||||
return redirect(
|
||||
"wagtailadmin_pages:add",
|
||||
model_opts.app_label,
|
||||
model_opts.model_name,
|
||||
parent_id,
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["media"] = context["form"].media
|
||||
context["submit_button_label"] = self.submit_button_label
|
||||
return context
|
||||
53
env/lib/python3.10/site-packages/wagtail/admin/views/pages/convert_alias.py
vendored
Normal file
53
env/lib/python3.10/site-packages/wagtail/admin/views/pages/convert_alias.py
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail import hooks
|
||||
from wagtail.actions.convert_alias import ConvertAliasPageAction
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.utils import get_valid_next_url_from_request
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
def convert_alias(request, page_id):
|
||||
page = get_object_or_404(Page, id=page_id, alias_of_id__isnull=False).specific
|
||||
if not page.permissions_for_user(request.user).can_edit():
|
||||
raise PermissionDenied
|
||||
|
||||
with transaction.atomic():
|
||||
for fn in hooks.get_hooks("before_convert_alias_page"):
|
||||
result = fn(request, page)
|
||||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
next_url = get_valid_next_url_from_request(request)
|
||||
|
||||
if request.method == "POST":
|
||||
action = ConvertAliasPageAction(page, user=request.user)
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
_("Page '%(page_title)s' has been converted into an ordinary page.")
|
||||
% {"page_title": page.get_admin_display_title()},
|
||||
)
|
||||
|
||||
for fn in hooks.get_hooks("after_convert_alias_page"):
|
||||
result = fn(request, page)
|
||||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
if next_url:
|
||||
return redirect(next_url)
|
||||
return redirect("wagtailadmin_pages:edit", page.id)
|
||||
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"wagtailadmin/pages/confirm_convert_alias.html",
|
||||
{
|
||||
"page": page,
|
||||
"next": next_url,
|
||||
},
|
||||
)
|
||||
115
env/lib/python3.10/site-packages/wagtail/admin/views/pages/copy.py
vendored
Normal file
115
env/lib/python3.10/site-packages/wagtail/admin/views/pages/copy.py
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
from django.shortcuts import redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail import hooks
|
||||
from wagtail.actions.copy_page import CopyPageAction
|
||||
from wagtail.actions.create_alias import CreatePageAliasAction
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.auth import user_has_any_page_permission, user_passes_test
|
||||
from wagtail.admin.forms.pages import CopyForm
|
||||
from wagtail.admin.utils import get_valid_next_url_from_request
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
@user_passes_test(user_has_any_page_permission)
|
||||
def copy(request, page_id):
|
||||
page = Page.objects.get(id=page_id)
|
||||
|
||||
# Parent page defaults to parent of source page
|
||||
parent_page = page.get_parent()
|
||||
|
||||
# Check if the user has permission to publish subpages on the parent
|
||||
can_publish = parent_page.permissions_for_user(request.user).can_publish_subpage()
|
||||
|
||||
form_class = getattr(page.specific_class, "copy_form_class", CopyForm)
|
||||
form = form_class(
|
||||
request.POST or None, user=request.user, page=page, can_publish=can_publish
|
||||
)
|
||||
|
||||
next_url = get_valid_next_url_from_request(request)
|
||||
|
||||
for fn in hooks.get_hooks("before_copy_page"):
|
||||
result = fn(request, page)
|
||||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
# Check if user is submitting
|
||||
if request.method == "POST":
|
||||
# Prefill parent_page in case the form is invalid (as prepopulated value for the form field,
|
||||
# because ModelChoiceField seems to not fall back to the user given value)
|
||||
parent_page = Page.objects.get(id=request.POST["new_parent_page"])
|
||||
|
||||
if form.is_valid():
|
||||
# Receive the parent page (this should never be empty)
|
||||
if form.cleaned_data["new_parent_page"]:
|
||||
parent_page = form.cleaned_data["new_parent_page"]
|
||||
|
||||
# Re-check if the user has permission to publish subpages on the new parent
|
||||
can_publish = parent_page.permissions_for_user(
|
||||
request.user
|
||||
).can_publish_subpage()
|
||||
keep_live = can_publish and form.cleaned_data.get("publish_copies")
|
||||
|
||||
# Copy the page
|
||||
# Note that only users who can publish in the new parent page can create an alias.
|
||||
# This is because alias pages must always match their original page's state.
|
||||
if can_publish and form.cleaned_data.get("alias"):
|
||||
action = CreatePageAliasAction(
|
||||
page.specific,
|
||||
recursive=form.cleaned_data.get("copy_subpages"),
|
||||
parent=parent_page,
|
||||
update_slug=form.cleaned_data["new_slug"],
|
||||
user=request.user,
|
||||
)
|
||||
new_page = action.execute(skip_permission_checks=True)
|
||||
else:
|
||||
action = CopyPageAction(
|
||||
page=page,
|
||||
recursive=form.cleaned_data.get("copy_subpages"),
|
||||
to=parent_page,
|
||||
update_attrs={
|
||||
"title": form.cleaned_data["new_title"],
|
||||
"slug": form.cleaned_data["new_slug"],
|
||||
},
|
||||
keep_live=keep_live,
|
||||
user=request.user,
|
||||
)
|
||||
new_page = action.execute()
|
||||
|
||||
# Give a success message back to the user
|
||||
if form.cleaned_data.get("copy_subpages"):
|
||||
messages.success(
|
||||
request,
|
||||
_("Page '%(page_title)s' and %(subpages_count)s subpages copied.")
|
||||
% {
|
||||
"page_title": page.specific_deferred.get_admin_display_title(),
|
||||
"subpages_count": new_page.get_descendants().count(),
|
||||
},
|
||||
)
|
||||
else:
|
||||
messages.success(
|
||||
request,
|
||||
_("Page '%(page_title)s' copied.")
|
||||
% {"page_title": page.specific_deferred.get_admin_display_title()},
|
||||
)
|
||||
|
||||
for fn in hooks.get_hooks("after_copy_page"):
|
||||
result = fn(request, page, new_page)
|
||||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
# Redirect to explore of parent page
|
||||
if next_url:
|
||||
return redirect(next_url)
|
||||
return redirect("wagtailadmin_explore", parent_page.id)
|
||||
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"wagtailadmin/pages/copy.html",
|
||||
{
|
||||
"page": page,
|
||||
"form": form,
|
||||
"next": next_url,
|
||||
},
|
||||
)
|
||||
454
env/lib/python3.10/site-packages/wagtail/admin/views/pages/create.py
vendored
Normal file
454
env/lib/python3.10/site-packages/wagtail/admin/views/pages/create.py
vendored
Normal file
@@ -0,0 +1,454 @@
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy
|
||||
from django.views.generic.base import View
|
||||
|
||||
from wagtail.admin import messages, signals
|
||||
from wagtail.admin.action_menu import PageActionMenu
|
||||
from wagtail.admin.ui.components import MediaContainer
|
||||
from wagtail.admin.ui.side_panels import (
|
||||
ChecksSidePanel,
|
||||
CommentsSidePanel,
|
||||
PageStatusSidePanel,
|
||||
PreviewSidePanel,
|
||||
)
|
||||
from wagtail.admin.utils import get_valid_next_url_from_request
|
||||
from wagtail.admin.views.generic import HookResponseMixin
|
||||
from wagtail.admin.views.generic.base import WagtailAdminTemplateMixin
|
||||
from wagtail.models import Locale, Page, PageSubscription
|
||||
|
||||
|
||||
def add_subpage(request, parent_page_id):
|
||||
parent_page = get_object_or_404(Page, id=parent_page_id).specific
|
||||
if not parent_page.permissions_for_user(request.user).can_add_subpage():
|
||||
raise PermissionDenied
|
||||
|
||||
page_types = [
|
||||
(
|
||||
model.get_verbose_name(),
|
||||
model._meta.app_label,
|
||||
model._meta.model_name,
|
||||
model.get_page_description(),
|
||||
)
|
||||
for model in type(parent_page).creatable_subpage_models()
|
||||
if model.can_create_at(parent_page)
|
||||
]
|
||||
# sort by lower-cased version of verbose name
|
||||
page_types.sort(key=lambda page_type: page_type[0].lower())
|
||||
|
||||
if len(page_types) == 1:
|
||||
# Only one page type is available - redirect straight to the create form rather than
|
||||
# making the user choose
|
||||
verbose_name, app_label, model_name, description = page_types[0]
|
||||
return redirect("wagtailadmin_pages:add", app_label, model_name, parent_page.id)
|
||||
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"wagtailadmin/pages/add_subpage.html",
|
||||
{
|
||||
"parent_page": parent_page,
|
||||
"page_types": page_types,
|
||||
"next": get_valid_next_url_from_request(request),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class CreateView(WagtailAdminTemplateMixin, HookResponseMixin, View):
|
||||
template_name = "wagtailadmin/pages/create.html"
|
||||
page_title = gettext_lazy("New")
|
||||
|
||||
def dispatch(
|
||||
self, request, content_type_app_name, content_type_model_name, parent_page_id
|
||||
):
|
||||
self.parent_page = get_object_or_404(Page, id=parent_page_id).specific
|
||||
self.parent_page_perms = self.parent_page.permissions_for_user(
|
||||
self.request.user
|
||||
)
|
||||
if not self.parent_page_perms.can_add_subpage():
|
||||
raise PermissionDenied
|
||||
|
||||
try:
|
||||
self.page_content_type = ContentType.objects.get_by_natural_key(
|
||||
content_type_app_name, content_type_model_name
|
||||
)
|
||||
except ContentType.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
# Get class
|
||||
self.page_class = self.page_content_type.model_class()
|
||||
|
||||
# Make sure the class is a descendant of Page
|
||||
if not issubclass(self.page_class, Page):
|
||||
raise Http404
|
||||
|
||||
# page must be in the list of allowed subpage types for this parent ID
|
||||
if self.page_class not in self.parent_page.creatable_subpage_models():
|
||||
raise PermissionDenied
|
||||
|
||||
if not self.page_class.can_create_at(self.parent_page):
|
||||
raise PermissionDenied
|
||||
|
||||
response = self.run_hook(
|
||||
"before_create_page", self.request, self.parent_page, self.page_class
|
||||
)
|
||||
if response:
|
||||
return response
|
||||
|
||||
self.locale = self.parent_page.locale
|
||||
self.page = self.page_class(owner=self.request.user)
|
||||
self.page.locale = self.locale
|
||||
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
# If the parent page is the root page. The user may specify any locale they like
|
||||
if self.parent_page.is_root():
|
||||
selected_locale = request.GET.get("locale", None) or request.POST.get(
|
||||
"locale", None
|
||||
)
|
||||
if selected_locale:
|
||||
self.locale = get_object_or_404(
|
||||
Locale, language_code=selected_locale
|
||||
)
|
||||
self.page.locale = self.locale
|
||||
self.translations = self.get_translations()
|
||||
else:
|
||||
self.locale = None
|
||||
self.translations = []
|
||||
|
||||
self.edit_handler = self.page_class.get_edit_handler()
|
||||
self.form_class = self.edit_handler.get_form_class()
|
||||
|
||||
# Note: Comment notifications should be enabled by default for pages that a user creates
|
||||
self.subscription = PageSubscription(
|
||||
page=self.page, user=self.request.user, comment_notifications=True
|
||||
)
|
||||
|
||||
self.next_url = get_valid_next_url_from_request(self.request)
|
||||
|
||||
return super().dispatch(request)
|
||||
|
||||
def post(self, request):
|
||||
self.form = self.form_class(
|
||||
self.request.POST,
|
||||
self.request.FILES,
|
||||
instance=self.page,
|
||||
subscription=self.subscription,
|
||||
parent_page=self.parent_page,
|
||||
for_user=self.request.user,
|
||||
)
|
||||
|
||||
if self.form.is_valid():
|
||||
return self.form_valid(self.form)
|
||||
else:
|
||||
return self.form_invalid(self.form)
|
||||
|
||||
def form_valid(self, form):
|
||||
if (
|
||||
bool(self.request.POST.get("action-publish"))
|
||||
and self.parent_page_perms.can_publish_subpage()
|
||||
):
|
||||
return self.publish_action()
|
||||
elif (
|
||||
bool(self.request.POST.get("action-submit"))
|
||||
and self.parent_page.has_workflow
|
||||
):
|
||||
return self.submit_action()
|
||||
else:
|
||||
return self.save_action()
|
||||
|
||||
def get_page_subtitle(self):
|
||||
return self.page_class.get_verbose_name()
|
||||
|
||||
def get_edit_message_button(self):
|
||||
return messages.button(
|
||||
reverse("wagtailadmin_pages:edit", args=(self.page.id,)), _("Edit")
|
||||
)
|
||||
|
||||
def get_view_draft_message_button(self):
|
||||
return messages.button(
|
||||
reverse("wagtailadmin_pages:view_draft", args=(self.page.id,)),
|
||||
_("View draft"),
|
||||
new_window=False,
|
||||
)
|
||||
|
||||
def get_view_live_message_button(self):
|
||||
return messages.button(self.page.url, _("View live"), new_window=False)
|
||||
|
||||
def save_action(self):
|
||||
self.page = self.form.save(commit=False)
|
||||
self.page.live = False
|
||||
|
||||
# Save page
|
||||
self.parent_page.add_child(instance=self.page)
|
||||
|
||||
# Save revision
|
||||
self.page.save_revision(user=self.request.user, log_action=True)
|
||||
|
||||
# Save subscription settings
|
||||
self.subscription.page = self.page
|
||||
self.subscription.save()
|
||||
|
||||
# Notification
|
||||
messages.success(
|
||||
self.request,
|
||||
_("Page '%(page_title)s' created.")
|
||||
% {"page_title": self.page.get_admin_display_title()},
|
||||
)
|
||||
|
||||
response = self.run_hook("after_create_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# remain on edit page for further edits
|
||||
return self.redirect_and_remain()
|
||||
|
||||
def publish_action(self):
|
||||
self.page = self.form.save(commit=False)
|
||||
|
||||
# Save page
|
||||
self.parent_page.add_child(instance=self.page)
|
||||
|
||||
# Save revision
|
||||
revision = self.page.save_revision(user=self.request.user, log_action=True)
|
||||
|
||||
# Save subscription settings
|
||||
self.subscription.page = self.page
|
||||
self.subscription.save()
|
||||
|
||||
# Publish
|
||||
response = self.run_hook("before_publish_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
revision.publish(user=self.request.user)
|
||||
|
||||
# get a fresh copy so that any changes coming from revision.publish() are passed on
|
||||
self.page.refresh_from_db()
|
||||
|
||||
response = self.run_hook("after_publish_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# Notification
|
||||
if self.page.go_live_at and self.page.go_live_at > timezone.now():
|
||||
messages.success(
|
||||
self.request,
|
||||
_("Page '%(page_title)s' created and scheduled for publishing.")
|
||||
% {"page_title": self.page.get_admin_display_title()},
|
||||
buttons=[self.get_edit_message_button()],
|
||||
)
|
||||
else:
|
||||
buttons = []
|
||||
if self.page.url is not None:
|
||||
buttons.append(self.get_view_live_message_button())
|
||||
buttons.append(self.get_edit_message_button())
|
||||
messages.success(
|
||||
self.request,
|
||||
_("Page '%(page_title)s' created and published.")
|
||||
% {"page_title": self.page.get_admin_display_title()},
|
||||
buttons=buttons,
|
||||
)
|
||||
|
||||
response = self.run_hook("after_create_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
return self.redirect_away()
|
||||
|
||||
def submit_action(self):
|
||||
self.page = self.form.save(commit=False)
|
||||
self.page.live = False
|
||||
|
||||
# Save page
|
||||
self.parent_page.add_child(instance=self.page)
|
||||
|
||||
# Save revision
|
||||
self.page.save_revision(user=self.request.user, log_action=True)
|
||||
|
||||
# Submit
|
||||
workflow = self.page.get_workflow()
|
||||
workflow.start(self.page, self.request.user)
|
||||
|
||||
# Save subscription settings
|
||||
self.subscription.page = self.page
|
||||
self.subscription.save()
|
||||
|
||||
# Notification
|
||||
buttons = []
|
||||
if self.page.is_previewable():
|
||||
buttons.append(self.get_view_draft_message_button())
|
||||
|
||||
buttons.append(self.get_edit_message_button())
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
_("Page '%(page_title)s' created and submitted for moderation.")
|
||||
% {"page_title": self.page.get_admin_display_title()},
|
||||
buttons=buttons,
|
||||
)
|
||||
|
||||
response = self.run_hook("after_create_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
return self.redirect_away()
|
||||
|
||||
def redirect_away(self):
|
||||
if self.next_url:
|
||||
# redirect back to 'next' url if present
|
||||
return redirect(self.next_url)
|
||||
else:
|
||||
# redirect back to the explorer
|
||||
return redirect("wagtailadmin_explore", self.page.get_parent().id)
|
||||
|
||||
def redirect_and_remain(self):
|
||||
target_url = reverse("wagtailadmin_pages:edit", args=[self.page.id])
|
||||
if self.next_url:
|
||||
# Ensure the 'next' url is passed through again if present
|
||||
target_url += "?next=%s" % quote(self.next_url)
|
||||
return redirect(target_url)
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.validation_error(
|
||||
self.request,
|
||||
_("The page could not be created due to validation errors"),
|
||||
self.form,
|
||||
)
|
||||
self.has_unsaved_changes = True
|
||||
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
def get(self, request):
|
||||
signals.init_new_page.send(
|
||||
sender=CreateView, page=self.page, parent=self.parent_page
|
||||
)
|
||||
self.form = self.form_class(
|
||||
instance=self.page,
|
||||
subscription=self.subscription,
|
||||
parent_page=self.parent_page,
|
||||
for_user=self.request.user,
|
||||
)
|
||||
self.has_unsaved_changes = False
|
||||
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
def get_preview_url(self):
|
||||
return reverse(
|
||||
"wagtailadmin_pages:preview_on_add",
|
||||
args=[
|
||||
self.page_content_type.app_label,
|
||||
self.page_content_type.model,
|
||||
self.parent_page.id,
|
||||
],
|
||||
)
|
||||
|
||||
def get_side_panels(self):
|
||||
side_panels = [
|
||||
PageStatusSidePanel(
|
||||
self.page,
|
||||
self.request,
|
||||
show_schedule_publishing_toggle=self.form.show_schedule_publishing_toggle,
|
||||
locale=self.locale,
|
||||
translations=self.translations,
|
||||
),
|
||||
]
|
||||
if self.page.is_previewable():
|
||||
side_panels.append(
|
||||
PreviewSidePanel(
|
||||
self.page, self.request, preview_url=self.get_preview_url()
|
||||
)
|
||||
)
|
||||
side_panels.append(
|
||||
ChecksSidePanel(
|
||||
self.page,
|
||||
self.request,
|
||||
)
|
||||
)
|
||||
if self.form.show_comments_toggle:
|
||||
side_panels.append(CommentsSidePanel(self.page, self.request))
|
||||
return MediaContainer(side_panels)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
bound_panel = self.edit_handler.get_bound_panel(
|
||||
request=self.request, instance=self.page, form=self.form
|
||||
)
|
||||
action_menu = PageActionMenu(
|
||||
self.request,
|
||||
view="create",
|
||||
parent_page=self.parent_page,
|
||||
lock=None,
|
||||
locked_for_user=False,
|
||||
)
|
||||
side_panels = self.get_side_panels()
|
||||
|
||||
media = MediaContainer([bound_panel, self.form, action_menu, side_panels]).media
|
||||
|
||||
context.update(
|
||||
{
|
||||
"content_type": self.page_content_type,
|
||||
"page_class": self.page_class,
|
||||
"parent_page": self.parent_page,
|
||||
"edit_handler": bound_panel,
|
||||
"action_menu": action_menu,
|
||||
"side_panels": side_panels,
|
||||
"form": self.form,
|
||||
"next": self.next_url,
|
||||
"has_unsaved_changes": self.has_unsaved_changes,
|
||||
"locale": self.locale,
|
||||
"media": media,
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
def get_translations(self):
|
||||
# Pages can be created in any language at the root level
|
||||
if self.parent_page.is_root():
|
||||
return [
|
||||
{
|
||||
"locale": locale,
|
||||
"url": reverse(
|
||||
"wagtailadmin_pages:add",
|
||||
args=[
|
||||
self.page_content_type.app_label,
|
||||
self.page_content_type.model,
|
||||
self.parent_page.id,
|
||||
],
|
||||
)
|
||||
+ "?"
|
||||
+ urlencode({"locale": locale.language_code}),
|
||||
}
|
||||
# Do not show the switcher for the current locale
|
||||
for locale in Locale.objects.exclude(pk=self.locale.pk)
|
||||
]
|
||||
|
||||
else:
|
||||
return [
|
||||
{
|
||||
"locale": translation.locale,
|
||||
"url": reverse(
|
||||
"wagtailadmin_pages:add",
|
||||
args=[
|
||||
self.page_content_type.app_label,
|
||||
self.page_content_type.model,
|
||||
translation.id,
|
||||
],
|
||||
),
|
||||
}
|
||||
for translation in self.parent_page.get_translations()
|
||||
.only("id", "locale")
|
||||
.select_related("locale")
|
||||
if translation.permissions_for_user(self.request.user).can_add_subpage()
|
||||
and self.page_class
|
||||
in translation.specific_class.creatable_subpage_models()
|
||||
and self.page_class.can_create_at(translation)
|
||||
]
|
||||
119
env/lib/python3.10/site-packages/wagtail/admin/views/pages/delete.py
vendored
Normal file
119
env/lib/python3.10/site-packages/wagtail/admin/views/pages/delete.py
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail import hooks
|
||||
from wagtail.actions.delete_page import DeletePageAction
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.utils import get_valid_next_url_from_request
|
||||
from wagtail.models import Page, ReferenceIndex
|
||||
|
||||
|
||||
def delete(request, page_id):
|
||||
page = get_object_or_404(Page, id=page_id).specific
|
||||
if not page.permissions_for_user(request.user).can_delete():
|
||||
raise PermissionDenied
|
||||
|
||||
wagtail_site_name = getattr(settings, "WAGTAIL_SITE_NAME", "wagtail")
|
||||
with transaction.atomic():
|
||||
for fn in hooks.get_hooks("before_delete_page"):
|
||||
result = fn(request, page)
|
||||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
next_url = get_valid_next_url_from_request(request)
|
||||
|
||||
pages_to_delete = {page}
|
||||
|
||||
# The `construct_translated_pages_to_cascade_actions` hook returns translation and
|
||||
# alias pages when the action is set to "delete"
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
fn_pages = fn([page], "delete")
|
||||
if fn_pages and isinstance(fn_pages, dict):
|
||||
for additional_pages in fn_pages.values():
|
||||
pages_to_delete.update(additional_pages)
|
||||
|
||||
pages_to_delete = list(pages_to_delete)
|
||||
|
||||
if request.method == "POST":
|
||||
continue_deleting = True
|
||||
if (
|
||||
request.POST.get("confirm_site_name")
|
||||
and request.POST.get("confirm_site_name") != wagtail_site_name
|
||||
):
|
||||
messages.error(
|
||||
request, f"Please type '{wagtail_site_name}' to confirm."
|
||||
)
|
||||
continue_deleting = False
|
||||
if continue_deleting:
|
||||
parent_id = page.get_parent().id
|
||||
# Delete the source page.
|
||||
action = DeletePageAction(page, user=request.user)
|
||||
# Permission checks are done above, so skip them in execute.
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
# Delete translation and alias pages if they have the same parent page.
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
parent_page_translations = page.get_parent().get_translations()
|
||||
for page_or_alias in pages_to_delete:
|
||||
if page_or_alias.get_parent() in parent_page_translations:
|
||||
action = DeletePageAction(page_or_alias, user=request.user)
|
||||
# Permission checks are done above, so skip them in execute.
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
_("Page '%(page_title)s' deleted.")
|
||||
% {"page_title": page.get_admin_display_title()},
|
||||
)
|
||||
|
||||
for fn in hooks.get_hooks("after_delete_page"):
|
||||
result = fn(request, page)
|
||||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
if next_url:
|
||||
return redirect(next_url)
|
||||
return redirect("wagtailadmin_explore", parent_id)
|
||||
|
||||
usage = ReferenceIndex.get_references_to(page).group_by_source_object()
|
||||
descendant_count = page.get_descendant_count()
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"wagtailadmin/pages/confirm_delete.html",
|
||||
{
|
||||
"page": page,
|
||||
"descendant_count": descendant_count,
|
||||
"next": next_url,
|
||||
"model_opts": page._meta,
|
||||
"usage_url": reverse("wagtailadmin_pages:usage", args=(page.id,))
|
||||
+ "?describe_on_delete=1",
|
||||
"usage_count": usage.count(),
|
||||
"is_protected": usage.is_protected,
|
||||
# if the number of pages ( child pages + current page) exceeds this limit, then confirm before delete.
|
||||
"confirm_before_delete": (descendant_count + 1)
|
||||
>= getattr(settings, "WAGTAILADMIN_UNSAFE_PAGE_DELETION_LIMIT", 10),
|
||||
"wagtail_site_name": wagtail_site_name,
|
||||
# note that while pages_to_delete may contain a mix of translated pages
|
||||
# and aliases, we count the "translations" only, as aliases are similar
|
||||
# to symlinks, so they should just follow the source
|
||||
"translation_count": len(
|
||||
[
|
||||
translation.id
|
||||
for translation in pages_to_delete
|
||||
if not translation.alias_of_id and translation.id != page.id
|
||||
]
|
||||
),
|
||||
"translation_descendant_count": sum(
|
||||
[
|
||||
translation.get_descendants().filter(alias_of__isnull=True).count()
|
||||
for translation in pages_to_delete
|
||||
]
|
||||
),
|
||||
},
|
||||
)
|
||||
970
env/lib/python3.10/site-packages/wagtail/admin/views/pages/edit.py
vendored
Normal file
970
env/lib/python3.10/site-packages/wagtail/admin/views/pages/edit.py
vendored
Normal file
@@ -0,0 +1,970 @@
|
||||
import json
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Prefetch, Q
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.html import format_html
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic.base import View
|
||||
|
||||
from wagtail.actions.publish_page_revision import PublishPageRevisionAction
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.action_menu import PageActionMenu
|
||||
from wagtail.admin.mail import send_notification
|
||||
from wagtail.admin.models import EditingSession
|
||||
from wagtail.admin.ui.components import MediaContainer
|
||||
from wagtail.admin.ui.editing_sessions import EditingSessionsModule
|
||||
from wagtail.admin.ui.side_panels import (
|
||||
ChecksSidePanel,
|
||||
CommentsSidePanel,
|
||||
PageStatusSidePanel,
|
||||
PreviewSidePanel,
|
||||
)
|
||||
from wagtail.admin.utils import get_valid_next_url_from_request
|
||||
from wagtail.admin.views.generic import HookResponseMixin
|
||||
from wagtail.admin.views.generic.base import WagtailAdminTemplateMixin
|
||||
from wagtail.exceptions import PageClassNotFoundError
|
||||
from wagtail.locks import BasicLock, ScheduledForPublishLock, WorkflowLock
|
||||
from wagtail.models import (
|
||||
COMMENTS_RELATION_NAME,
|
||||
Comment,
|
||||
CommentReply,
|
||||
Page,
|
||||
PageSubscription,
|
||||
WorkflowState,
|
||||
get_default_page_content_type,
|
||||
)
|
||||
from wagtail.utils.timestamps import render_timestamp
|
||||
|
||||
|
||||
class EditView(WagtailAdminTemplateMixin, HookResponseMixin, View):
|
||||
def get_page_title(self):
|
||||
return _("Editing %(page_type)s") % {
|
||||
"page_type": self.page_class.get_verbose_name()
|
||||
}
|
||||
|
||||
def get_page_subtitle(self):
|
||||
return self.page.get_admin_display_title()
|
||||
|
||||
def get_template_names(self):
|
||||
if self.page.alias_of_id:
|
||||
return ["wagtailadmin/pages/edit_alias.html"]
|
||||
|
||||
else:
|
||||
return ["wagtailadmin/pages/edit.html"]
|
||||
|
||||
def add_save_confirmation_message(self):
|
||||
if self.is_reverting:
|
||||
message = _(
|
||||
"Page '%(page_title)s' has been replaced "
|
||||
"with version from %(previous_revision_datetime)s."
|
||||
) % {
|
||||
"page_title": self.page.get_admin_display_title(),
|
||||
"previous_revision_datetime": render_timestamp(
|
||||
self.previous_revision.created_at
|
||||
),
|
||||
}
|
||||
else:
|
||||
message = _("Page '%(page_title)s' has been updated.") % {
|
||||
"page_title": self.page.get_admin_display_title()
|
||||
}
|
||||
|
||||
messages.success(self.request, message)
|
||||
|
||||
def get_commenting_changes(self):
|
||||
"""
|
||||
Finds comments that have been changed during this request.
|
||||
|
||||
Returns a tuple of 5 lists:
|
||||
- New comments
|
||||
- Deleted comments
|
||||
- Resolved comments
|
||||
- Edited comments
|
||||
- Replied comments (dict containing the instance and list of replies)
|
||||
"""
|
||||
# Get changes
|
||||
comments_formset = self.form.formsets["comments"]
|
||||
new_comments = comments_formset.new_objects
|
||||
deleted_comments = comments_formset.deleted_objects
|
||||
|
||||
# Assume any changed comments that are resolved were only just resolved
|
||||
resolved_comments = []
|
||||
edited_comments = []
|
||||
for changed_comment, changed_fields in comments_formset.changed_objects:
|
||||
if changed_comment.resolved_at and "resolved" in changed_fields:
|
||||
resolved_comments.append(changed_comment)
|
||||
|
||||
if "text" in changed_fields:
|
||||
edited_comments.append(changed_comment)
|
||||
|
||||
new_replies = []
|
||||
deleted_replies = []
|
||||
edited_replies = []
|
||||
for comment_form in comments_formset.forms:
|
||||
# New
|
||||
replies = getattr(comment_form.formsets["replies"], "new_objects", [])
|
||||
if replies:
|
||||
new_replies.append((comment_form.instance, replies))
|
||||
|
||||
# Deleted
|
||||
replies = getattr(comment_form.formsets["replies"], "deleted_objects", [])
|
||||
if replies:
|
||||
deleted_replies.append((comment_form.instance, replies))
|
||||
|
||||
# Edited
|
||||
replies = getattr(comment_form.formsets["replies"], "changed_objects", [])
|
||||
replies = [
|
||||
reply for reply, changed_fields in replies if "text" in changed_fields
|
||||
]
|
||||
if replies:
|
||||
edited_replies.append((comment_form.instance, replies))
|
||||
|
||||
return {
|
||||
"new_comments": new_comments,
|
||||
"deleted_comments": deleted_comments,
|
||||
"resolved_comments": resolved_comments,
|
||||
"edited_comments": edited_comments,
|
||||
"new_replies": new_replies,
|
||||
"deleted_replies": deleted_replies,
|
||||
"edited_replies": edited_replies,
|
||||
}
|
||||
|
||||
def send_commenting_notifications(self, changes):
|
||||
"""
|
||||
Sends notifications about any changes to comments to anyone who is subscribed.
|
||||
"""
|
||||
relevant_comment_ids = []
|
||||
relevant_comment_ids.extend(
|
||||
comment.pk for comment in changes["resolved_comments"]
|
||||
)
|
||||
relevant_comment_ids.extend(
|
||||
comment.pk for comment, replies in changes["new_replies"]
|
||||
)
|
||||
|
||||
# Skip if no changes were made
|
||||
# Note: We don't email about edited comments so ignore those here
|
||||
if (
|
||||
not changes["new_comments"]
|
||||
and not changes["deleted_comments"]
|
||||
and not changes["resolved_comments"]
|
||||
and not changes["new_replies"]
|
||||
):
|
||||
return
|
||||
|
||||
# Get global page comment subscribers
|
||||
subscribers = PageSubscription.objects.filter(
|
||||
page=self.page, comment_notifications=True
|
||||
).select_related("user")
|
||||
global_recipient_users = [
|
||||
subscriber.user
|
||||
for subscriber in subscribers
|
||||
if subscriber.user != self.request.user
|
||||
]
|
||||
|
||||
# Get subscribers to individual threads
|
||||
replies = CommentReply.objects.filter(comment_id__in=relevant_comment_ids)
|
||||
comments = Comment.objects.filter(id__in=relevant_comment_ids)
|
||||
thread_users = (
|
||||
get_user_model()
|
||||
.objects.exclude(pk=self.request.user.pk)
|
||||
.exclude(pk__in=subscribers.values_list("user_id", flat=True))
|
||||
.filter(
|
||||
Q(comment_replies__comment_id__in=relevant_comment_ids)
|
||||
| Q(**{("%s__pk__in" % COMMENTS_RELATION_NAME): relevant_comment_ids})
|
||||
)
|
||||
.prefetch_related(
|
||||
Prefetch("comment_replies", queryset=replies),
|
||||
Prefetch(COMMENTS_RELATION_NAME, queryset=comments),
|
||||
)
|
||||
)
|
||||
|
||||
# Skip if no recipients
|
||||
if not (global_recipient_users or thread_users):
|
||||
return
|
||||
thread_users = [
|
||||
(
|
||||
user,
|
||||
set(
|
||||
list(user.comment_replies.values_list("comment_id", flat=True))
|
||||
+ list(
|
||||
getattr(user, COMMENTS_RELATION_NAME).values_list(
|
||||
"pk", flat=True
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
for user in thread_users
|
||||
]
|
||||
mailed_users = set()
|
||||
|
||||
for current_user, current_threads in thread_users:
|
||||
# We are trying to avoid calling send_notification for each user for performance reasons
|
||||
# so group the users receiving the same thread notifications together here
|
||||
if current_user in mailed_users:
|
||||
continue
|
||||
users = [current_user]
|
||||
mailed_users.add(current_user)
|
||||
for user, threads in thread_users:
|
||||
if user not in mailed_users and threads == current_threads:
|
||||
users.append(user)
|
||||
mailed_users.add(user)
|
||||
send_notification(
|
||||
users,
|
||||
"updated_comments",
|
||||
{
|
||||
"page": self.page,
|
||||
"editor": self.request.user,
|
||||
"new_comments": [
|
||||
comment
|
||||
for comment in changes["new_comments"]
|
||||
if comment.pk in threads
|
||||
],
|
||||
"resolved_comments": [
|
||||
comment
|
||||
for comment in changes["resolved_comments"]
|
||||
if comment.pk in threads
|
||||
],
|
||||
"deleted_comments": [],
|
||||
"replied_comments": [
|
||||
{
|
||||
"comment": comment,
|
||||
"replies": replies,
|
||||
}
|
||||
for comment, replies in changes["new_replies"]
|
||||
if comment.pk in threads
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
return send_notification(
|
||||
global_recipient_users,
|
||||
"updated_comments",
|
||||
{
|
||||
"page": self.page,
|
||||
"editor": self.request.user,
|
||||
"new_comments": changes["new_comments"],
|
||||
"resolved_comments": changes["resolved_comments"],
|
||||
"deleted_comments": changes["deleted_comments"],
|
||||
"replied_comments": [
|
||||
{
|
||||
"comment": comment,
|
||||
"replies": replies,
|
||||
}
|
||||
for comment, replies in changes["new_replies"]
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
def log_commenting_changes(self, changes, revision):
|
||||
"""
|
||||
Generates log entries for any changes made to comments or replies.
|
||||
"""
|
||||
for comment in changes["new_comments"]:
|
||||
comment.log_create(page_revision=revision, user=self.request.user)
|
||||
|
||||
for comment in changes["edited_comments"]:
|
||||
comment.log_edit(page_revision=revision, user=self.request.user)
|
||||
|
||||
for comment in changes["resolved_comments"]:
|
||||
comment.log_resolve(page_revision=revision, user=self.request.user)
|
||||
|
||||
for comment in changes["deleted_comments"]:
|
||||
comment.log_delete(page_revision=revision, user=self.request.user)
|
||||
|
||||
for comment, replies in changes["new_replies"]:
|
||||
for reply in replies:
|
||||
reply.log_create(page_revision=revision, user=self.request.user)
|
||||
|
||||
for comment, replies in changes["edited_replies"]:
|
||||
for reply in replies:
|
||||
reply.log_edit(page_revision=revision, user=self.request.user)
|
||||
|
||||
for comment, replies in changes["deleted_replies"]:
|
||||
for reply in replies:
|
||||
reply.log_delete(page_revision=revision, user=self.request.user)
|
||||
|
||||
def get_edit_message_button(self):
|
||||
return messages.button(
|
||||
reverse("wagtailadmin_pages:edit", args=(self.page.id,)), _("Edit")
|
||||
)
|
||||
|
||||
def get_view_draft_message_button(self):
|
||||
return messages.button(
|
||||
reverse("wagtailadmin_pages:view_draft", args=(self.page.id,)),
|
||||
_("View draft"),
|
||||
new_window=False,
|
||||
)
|
||||
|
||||
def get_view_live_message_button(self):
|
||||
return messages.button(self.page.url, _("View live"), new_window=False)
|
||||
|
||||
def get_compare_with_live_message_button(self):
|
||||
return messages.button(
|
||||
reverse(
|
||||
"wagtailadmin_pages:revisions_compare",
|
||||
args=(self.page.id, "live", self.latest_revision.id),
|
||||
),
|
||||
_("Compare with live version"),
|
||||
)
|
||||
|
||||
def get_page_for_status(self):
|
||||
if self.page.live and self.page.has_unpublished_changes:
|
||||
# Page status needs to present the version of the page containing the correct live URL
|
||||
return self.real_page_record.specific
|
||||
else:
|
||||
return self.page
|
||||
|
||||
def dispatch(self, request, page_id):
|
||||
self.real_page_record = get_object_or_404(
|
||||
Page.objects.prefetch_workflow_states(), id=page_id
|
||||
)
|
||||
self.latest_revision = self.real_page_record.get_latest_revision()
|
||||
self.scheduled_revision = self.real_page_record.scheduled_revision
|
||||
self.page_content_type = self.real_page_record.cached_content_type
|
||||
self.page_class = self.real_page_record.specific_class
|
||||
|
||||
if self.page_class is None:
|
||||
raise PageClassNotFoundError(
|
||||
f"The page '{self.real_page_record}' cannot be edited because the "
|
||||
f"model class used to create it ({self.page_content_type.app_label}."
|
||||
f"{self.page_content_type.model}) can no longer be found in the codebase. "
|
||||
"This usually happens as a result of switching between git "
|
||||
"branches without running migrations to trigger the removal of "
|
||||
"unused ContentTypes. To edit the page, you will need to switch "
|
||||
"back to a branch where the model class is still present."
|
||||
)
|
||||
|
||||
self.page = self.real_page_record.get_latest_revision_as_object()
|
||||
self.parent = self.page.get_parent()
|
||||
self.scheduled_page = self.real_page_record.get_scheduled_revision_as_object()
|
||||
|
||||
self.page_perms = self.page.permissions_for_user(self.request.user)
|
||||
self.lock = self.page.get_lock()
|
||||
self.locked_for_user = self.lock is not None and self.lock.for_user(
|
||||
self.request.user
|
||||
)
|
||||
|
||||
if not self.page_perms.can_edit():
|
||||
raise PermissionDenied
|
||||
|
||||
self.next_url = get_valid_next_url_from_request(self.request)
|
||||
|
||||
response = self.run_hook("before_edit_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
self.subscription, created = PageSubscription.objects.get_or_create(
|
||||
page=self.page,
|
||||
user=self.request.user,
|
||||
defaults={
|
||||
"comment_notifications": False,
|
||||
},
|
||||
)
|
||||
|
||||
self.edit_handler = self.page_class.get_edit_handler()
|
||||
self.form_class = self.edit_handler.get_form_class()
|
||||
|
||||
if getattr(settings, "WAGTAIL_WORKFLOW_ENABLED", True):
|
||||
# Retrieve current workflow state if set, default to last workflow state
|
||||
self.workflow_state = (
|
||||
self.page.current_workflow_state
|
||||
or self.page.workflow_states.order_by("created_at").last()
|
||||
)
|
||||
else:
|
||||
self.workflow_state = None
|
||||
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
self.locale = self.page.locale
|
||||
self.translations = self.get_translations()
|
||||
else:
|
||||
self.locale = None
|
||||
self.translations = []
|
||||
|
||||
if self.workflow_state:
|
||||
self.workflow_tasks = self.workflow_state.all_tasks_with_status()
|
||||
else:
|
||||
self.workflow_tasks = []
|
||||
|
||||
self.errors_debug = None
|
||||
|
||||
return super().dispatch(request)
|
||||
|
||||
def get(self, request):
|
||||
if self.lock:
|
||||
lock_message = self.lock.get_message(self.request.user)
|
||||
if lock_message:
|
||||
if isinstance(self.lock, BasicLock) and self.page_perms.can_unlock():
|
||||
lock_message = format_html(
|
||||
'{} <span class="buttons"><button type="button" class="button button-small button-secondary" data-action="w-action#post" data-controller="w-action" data-w-action-url-value="{}">{}</button></span>',
|
||||
lock_message,
|
||||
reverse("wagtailadmin_pages:unlock", args=(self.page.id,)),
|
||||
_("Unlock"),
|
||||
)
|
||||
|
||||
if (
|
||||
isinstance(self.lock, ScheduledForPublishLock)
|
||||
and self.page_perms.can_unschedule()
|
||||
):
|
||||
lock_message = format_html(
|
||||
'{} <span class="buttons"><button type="button" class="button button-small button-secondary" data-action="w-action#post" data-controller="w-action" data-w-action-url-value="{}">{}</button></span>',
|
||||
lock_message,
|
||||
reverse(
|
||||
"wagtailadmin_pages:revisions_unschedule",
|
||||
args=[self.page.id, self.scheduled_revision.pk],
|
||||
),
|
||||
_("Cancel scheduled publish"),
|
||||
)
|
||||
|
||||
if (
|
||||
not isinstance(self.lock, ScheduledForPublishLock)
|
||||
and self.locked_for_user
|
||||
):
|
||||
messages.warning(self.request, lock_message, extra_tags="lock")
|
||||
else:
|
||||
messages.info(self.request, lock_message, extra_tags="lock")
|
||||
|
||||
self.form = self.form_class(
|
||||
instance=self.page,
|
||||
subscription=self.subscription,
|
||||
parent_page=self.parent,
|
||||
for_user=self.request.user,
|
||||
)
|
||||
self.has_unsaved_changes = False
|
||||
self.page_for_status = self.get_page_for_status()
|
||||
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
def add_cancel_workflow_confirmation_message(self):
|
||||
message = _("Workflow on page '%(page_title)s' has been cancelled.") % {
|
||||
"page_title": self.page.get_admin_display_title()
|
||||
}
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
message,
|
||||
buttons=[
|
||||
self.get_view_draft_message_button(),
|
||||
self.get_edit_message_button(),
|
||||
],
|
||||
)
|
||||
|
||||
def post(self, request):
|
||||
# Don't allow POST requests if the page is an alias
|
||||
if self.page.alias_of_id:
|
||||
# Return 405 "Method Not Allowed" response
|
||||
return HttpResponse(status=405)
|
||||
|
||||
self.form = self.form_class(
|
||||
self.request.POST,
|
||||
self.request.FILES,
|
||||
instance=self.page,
|
||||
subscription=self.subscription,
|
||||
parent_page=self.parent,
|
||||
for_user=self.request.user,
|
||||
)
|
||||
|
||||
self.is_cancelling_workflow = (
|
||||
bool(self.request.POST.get("action-cancel-workflow"))
|
||||
and self.workflow_state
|
||||
and self.workflow_state.user_can_cancel(self.request.user)
|
||||
)
|
||||
|
||||
if self.form.is_valid() and not self.locked_for_user:
|
||||
return self.form_valid(self.form)
|
||||
else:
|
||||
return self.form_invalid(self.form)
|
||||
|
||||
def workflow_action_is_valid(self):
|
||||
self.workflow_action = self.request.POST["workflow-action-name"]
|
||||
available_actions = self.page.current_workflow_task.get_actions(
|
||||
self.page, self.request.user
|
||||
)
|
||||
available_action_names = [
|
||||
name for name, verbose_name, modal in available_actions
|
||||
]
|
||||
return self.workflow_action in available_action_names
|
||||
|
||||
def form_valid(self, form):
|
||||
self.is_reverting = bool(self.request.POST.get("revision"))
|
||||
# If a revision ID was passed in the form, get that revision so its
|
||||
# date can be referenced in notification messages
|
||||
if self.is_reverting:
|
||||
self.previous_revision = get_object_or_404(
|
||||
self.page.revisions, id=self.request.POST.get("revision")
|
||||
)
|
||||
|
||||
self.has_content_changes = self.form.has_changed()
|
||||
|
||||
if self.request.POST.get("action-publish") and self.page_perms.can_publish():
|
||||
return self.publish_action()
|
||||
elif (
|
||||
self.request.POST.get("action-submit")
|
||||
and self.page_perms.can_submit_for_moderation()
|
||||
):
|
||||
return self.submit_action()
|
||||
elif (
|
||||
self.request.POST.get("action-restart-workflow")
|
||||
and self.page_perms.can_submit_for_moderation()
|
||||
and self.workflow_state
|
||||
and self.workflow_state.user_can_cancel(self.request.user)
|
||||
):
|
||||
return self.restart_workflow_action()
|
||||
elif (
|
||||
self.request.POST.get("action-workflow-action")
|
||||
and self.workflow_action_is_valid()
|
||||
):
|
||||
return self.perform_workflow_action()
|
||||
elif self.is_cancelling_workflow:
|
||||
return self.cancel_workflow_action()
|
||||
else:
|
||||
return self.save_action()
|
||||
|
||||
def save_action(self):
|
||||
self.page = self.form.save(commit=not self.page.live)
|
||||
self.subscription.save()
|
||||
|
||||
# Save revision
|
||||
revision = self.page.save_revision(
|
||||
user=self.request.user,
|
||||
log_action=True, # Always log the new revision on edit
|
||||
previous_revision=(self.previous_revision if self.is_reverting else None),
|
||||
)
|
||||
|
||||
self.add_save_confirmation_message()
|
||||
|
||||
if self.has_content_changes and "comments" in self.form.formsets:
|
||||
changes = self.get_commenting_changes()
|
||||
self.log_commenting_changes(changes, revision)
|
||||
self.send_commenting_notifications(changes)
|
||||
|
||||
response = self.run_hook("after_edit_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# Just saving - remain on edit page for further edits
|
||||
return self.redirect_and_remain()
|
||||
|
||||
def publish_action(self):
|
||||
self.page = self.form.save(commit=not self.page.live)
|
||||
self.subscription.save()
|
||||
|
||||
# Save revision
|
||||
revision = self.page.save_revision(
|
||||
user=self.request.user,
|
||||
log_action=True, # Always log the new revision on edit
|
||||
previous_revision=(self.previous_revision if self.is_reverting else None),
|
||||
)
|
||||
|
||||
# store submitted go_live_at for messaging below
|
||||
go_live_at = self.page.go_live_at
|
||||
|
||||
response = self.run_hook("before_publish_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
action = PublishPageRevisionAction(
|
||||
revision,
|
||||
user=self.request.user,
|
||||
changed=self.has_content_changes,
|
||||
previous_revision=(self.previous_revision if self.is_reverting else None),
|
||||
)
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
if self.has_content_changes and "comments" in self.form.formsets:
|
||||
changes = self.get_commenting_changes()
|
||||
self.log_commenting_changes(changes, revision)
|
||||
self.send_commenting_notifications(changes)
|
||||
|
||||
# Need to reload the page because the URL may have changed, and we
|
||||
# need the up-to-date URL for the "View Live" button.
|
||||
self.page = self.page.specific_class.objects.get(pk=self.page.pk)
|
||||
|
||||
response = self.run_hook("after_publish_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# Notifications
|
||||
if go_live_at and go_live_at > timezone.now():
|
||||
# Page has been scheduled for publishing in the future
|
||||
|
||||
if self.is_reverting:
|
||||
message = _(
|
||||
"Version from %(previous_revision_datetime)s "
|
||||
"of page '%(page_title)s' has been scheduled for publishing."
|
||||
) % {
|
||||
"previous_revision_datetime": render_timestamp(
|
||||
self.previous_revision.created_at
|
||||
),
|
||||
"page_title": self.page.get_admin_display_title(),
|
||||
}
|
||||
else:
|
||||
if self.page.live:
|
||||
message = _(
|
||||
"Page '%(page_title)s' is live and this version has been scheduled for publishing."
|
||||
) % {"page_title": self.page.get_admin_display_title()}
|
||||
|
||||
else:
|
||||
message = _(
|
||||
"Page '%(page_title)s' has been scheduled for publishing."
|
||||
) % {"page_title": self.page.get_admin_display_title()}
|
||||
|
||||
messages.success(
|
||||
self.request, message, buttons=[self.get_edit_message_button()]
|
||||
)
|
||||
|
||||
else:
|
||||
# Page is being published now
|
||||
|
||||
if self.is_reverting:
|
||||
message = _(
|
||||
"Version from %(datetime)s of page '%(page_title)s' has been published."
|
||||
) % {
|
||||
"datetime": render_timestamp(self.previous_revision.created_at),
|
||||
"page_title": self.page.get_admin_display_title(),
|
||||
}
|
||||
else:
|
||||
message = _("Page '%(page_title)s' has been published.") % {
|
||||
"page_title": self.page.get_admin_display_title()
|
||||
}
|
||||
|
||||
buttons = []
|
||||
if self.page.url is not None:
|
||||
buttons.append(self.get_view_live_message_button())
|
||||
buttons.append(self.get_edit_message_button())
|
||||
messages.success(self.request, message, buttons=buttons)
|
||||
|
||||
response = self.run_hook("after_edit_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# we're done here - redirect back to the explorer
|
||||
return self.redirect_away()
|
||||
|
||||
def submit_action(self):
|
||||
self.page = self.form.save(commit=not self.page.live)
|
||||
self.subscription.save()
|
||||
|
||||
# Save revision
|
||||
revision = self.page.save_revision(
|
||||
user=self.request.user,
|
||||
log_action=True, # Always log the new revision on edit
|
||||
previous_revision=(self.previous_revision if self.is_reverting else None),
|
||||
)
|
||||
|
||||
if self.has_content_changes and "comments" in self.form.formsets:
|
||||
changes = self.get_commenting_changes()
|
||||
self.log_commenting_changes(changes, revision)
|
||||
self.send_commenting_notifications(changes)
|
||||
|
||||
if (
|
||||
self.workflow_state
|
||||
and self.workflow_state.status == WorkflowState.STATUS_NEEDS_CHANGES
|
||||
):
|
||||
# If the workflow was in the needs changes state, resume the existing workflow on submission
|
||||
self.workflow_state.resume(self.request.user)
|
||||
else:
|
||||
# Otherwise start a new workflow
|
||||
workflow = self.page.get_workflow()
|
||||
workflow.start(self.page, self.request.user)
|
||||
|
||||
message = _("Page '%(page_title)s' has been submitted for moderation.") % {
|
||||
"page_title": self.page.get_admin_display_title()
|
||||
}
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
message,
|
||||
buttons=[
|
||||
self.get_view_draft_message_button(),
|
||||
self.get_edit_message_button(),
|
||||
],
|
||||
)
|
||||
|
||||
response = self.run_hook("after_edit_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# we're done here - redirect back to the explorer
|
||||
return self.redirect_away()
|
||||
|
||||
def restart_workflow_action(self):
|
||||
self.page = self.form.save(commit=not self.page.live)
|
||||
self.subscription.save()
|
||||
|
||||
# save revision
|
||||
revision = self.page.save_revision(
|
||||
user=self.request.user,
|
||||
log_action=True, # Always log the new revision on edit
|
||||
previous_revision=(self.previous_revision if self.is_reverting else None),
|
||||
)
|
||||
|
||||
if self.has_content_changes and "comments" in self.form.formsets:
|
||||
changes = self.get_commenting_changes()
|
||||
self.log_commenting_changes(changes, revision)
|
||||
self.send_commenting_notifications(changes)
|
||||
|
||||
# cancel workflow
|
||||
self.workflow_state.cancel(user=self.request.user)
|
||||
# start new workflow
|
||||
workflow = self.page.get_workflow()
|
||||
workflow.start(self.page, self.request.user)
|
||||
|
||||
message = _("Workflow on page '%(page_title)s' has been restarted.") % {
|
||||
"page_title": self.page.get_admin_display_title()
|
||||
}
|
||||
|
||||
messages.success(
|
||||
self.request,
|
||||
message,
|
||||
buttons=[
|
||||
self.get_view_draft_message_button(),
|
||||
self.get_edit_message_button(),
|
||||
],
|
||||
)
|
||||
|
||||
response = self.run_hook("after_edit_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# we're done here - redirect back to the explorer
|
||||
return self.redirect_away()
|
||||
|
||||
def perform_workflow_action(self):
|
||||
self.page = self.form.save(commit=not self.page.live)
|
||||
self.subscription.save()
|
||||
|
||||
if self.has_content_changes:
|
||||
# Save revision
|
||||
revision = self.page.save_revision(
|
||||
user=self.request.user,
|
||||
log_action=True, # Always log the new revision on edit
|
||||
previous_revision=(
|
||||
self.previous_revision if self.is_reverting else None
|
||||
),
|
||||
)
|
||||
|
||||
if "comments" in self.form.formsets:
|
||||
changes = self.get_commenting_changes()
|
||||
self.log_commenting_changes(changes, revision)
|
||||
self.send_commenting_notifications(changes)
|
||||
|
||||
extra_workflow_data_json = self.request.POST.get(
|
||||
"workflow-action-extra-data", "{}"
|
||||
)
|
||||
extra_workflow_data = json.loads(extra_workflow_data_json)
|
||||
self.page.current_workflow_task.on_action(
|
||||
self.page.current_workflow_task_state,
|
||||
self.request.user,
|
||||
self.workflow_action,
|
||||
**extra_workflow_data,
|
||||
)
|
||||
|
||||
self.add_save_confirmation_message()
|
||||
|
||||
response = self.run_hook("after_edit_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# we're done here - redirect back to the explorer
|
||||
return self.redirect_away()
|
||||
|
||||
def cancel_workflow_action(self):
|
||||
self.workflow_state.cancel(user=self.request.user)
|
||||
self.page = self.form.save(commit=not self.page.live)
|
||||
self.subscription.save()
|
||||
|
||||
# Save revision
|
||||
revision = self.page.save_revision(
|
||||
user=self.request.user,
|
||||
log_action=True, # Always log the new revision on edit
|
||||
previous_revision=(self.previous_revision if self.is_reverting else None),
|
||||
)
|
||||
|
||||
if self.has_content_changes and "comments" in self.form.formsets:
|
||||
changes = self.get_commenting_changes()
|
||||
self.log_commenting_changes(changes, revision)
|
||||
self.send_commenting_notifications(changes)
|
||||
|
||||
# Notifications
|
||||
self.add_cancel_workflow_confirmation_message()
|
||||
|
||||
response = self.run_hook("after_edit_page", self.request, self.page)
|
||||
if response:
|
||||
return response
|
||||
|
||||
# Just saving - remain on edit page for further edits
|
||||
return self.redirect_and_remain()
|
||||
|
||||
def redirect_away(self):
|
||||
if self.next_url:
|
||||
# redirect back to 'next' url if present
|
||||
return redirect(self.next_url)
|
||||
else:
|
||||
# redirect back to the explorer
|
||||
return redirect("wagtailadmin_explore", self.page.get_parent().id)
|
||||
|
||||
def redirect_and_remain(self):
|
||||
target_url = reverse("wagtailadmin_pages:edit", args=[self.page.id])
|
||||
if self.next_url:
|
||||
# Ensure the 'next' url is passed through again if present
|
||||
target_url += "?next=%s" % quote(self.next_url)
|
||||
return redirect(target_url)
|
||||
|
||||
def form_invalid(self, form):
|
||||
# even if the page is locked due to not having permissions, the original submitter can still cancel the workflow
|
||||
if self.is_cancelling_workflow:
|
||||
self.workflow_state.cancel(user=self.request.user)
|
||||
self.add_cancel_workflow_confirmation_message()
|
||||
|
||||
# Refresh the lock object as now WorkflowLock no longer applies
|
||||
self.lock = self.page.get_lock()
|
||||
self.locked_for_user = self.lock is not None and self.lock.for_user(
|
||||
self.request.user
|
||||
)
|
||||
elif self.locked_for_user:
|
||||
messages.error(
|
||||
self.request, _("The page could not be saved as it is locked")
|
||||
)
|
||||
else:
|
||||
messages.validation_error(
|
||||
self.request,
|
||||
_("The page could not be saved due to validation errors"),
|
||||
self.form,
|
||||
)
|
||||
self.errors_debug = repr(self.form.errors) + repr(
|
||||
[
|
||||
(name, formset.errors)
|
||||
for (name, formset) in self.form.formsets.items()
|
||||
if formset.errors
|
||||
]
|
||||
)
|
||||
self.has_unsaved_changes = True
|
||||
|
||||
self.page_for_status = self.get_page_for_status()
|
||||
|
||||
return self.render_to_response(self.get_context_data())
|
||||
|
||||
def get_preview_url(self):
|
||||
return reverse("wagtailadmin_pages:preview_on_edit", args=[self.page.id])
|
||||
|
||||
def get_history_url(self):
|
||||
permissions = self.page.permissions_for_user(self.request.user)
|
||||
if permissions.can_view_revisions():
|
||||
return reverse("wagtailadmin_pages:history", args=[self.page.id])
|
||||
|
||||
def get_side_panels(self):
|
||||
side_panels = [
|
||||
PageStatusSidePanel(
|
||||
self.page,
|
||||
self.request,
|
||||
show_schedule_publishing_toggle=self.form.show_schedule_publishing_toggle,
|
||||
live_object=self.real_page_record,
|
||||
scheduled_object=self.scheduled_page,
|
||||
locale=self.locale,
|
||||
translations=self.translations,
|
||||
),
|
||||
]
|
||||
if self.page.is_previewable():
|
||||
side_panels.append(
|
||||
PreviewSidePanel(
|
||||
self.page, self.request, preview_url=self.get_preview_url()
|
||||
)
|
||||
)
|
||||
side_panels.append(ChecksSidePanel(self.page, self.request))
|
||||
if self.form.show_comments_toggle:
|
||||
side_panels.append(CommentsSidePanel(self.page, self.request))
|
||||
return MediaContainer(side_panels)
|
||||
|
||||
def get_editing_sessions(self):
|
||||
EditingSession.cleanup()
|
||||
content_type = get_default_page_content_type()
|
||||
session = EditingSession.objects.create(
|
||||
user=self.request.user,
|
||||
content_type=content_type,
|
||||
object_id=self.page.pk,
|
||||
last_seen_at=timezone.now(),
|
||||
)
|
||||
return EditingSessionsModule(
|
||||
session,
|
||||
reverse(
|
||||
"wagtailadmin_editing_sessions:ping",
|
||||
args=("wagtailcore", "page", self.page.pk, session.id),
|
||||
),
|
||||
reverse(
|
||||
"wagtailadmin_editing_sessions:release",
|
||||
args=(session.id,),
|
||||
),
|
||||
[],
|
||||
self.page.latest_revision_id,
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
user_perms = self.page.permissions_for_user(self.request.user)
|
||||
bound_panel = self.edit_handler.get_bound_panel(
|
||||
instance=self.page, request=self.request, form=self.form
|
||||
)
|
||||
action_menu = PageActionMenu(
|
||||
self.request,
|
||||
view="edit",
|
||||
page=self.page,
|
||||
lock=self.lock,
|
||||
locked_for_user=self.locked_for_user,
|
||||
)
|
||||
side_panels = self.get_side_panels()
|
||||
|
||||
media = MediaContainer([bound_panel, self.form, action_menu, side_panels]).media
|
||||
|
||||
context.update(
|
||||
{
|
||||
"page": self.page,
|
||||
"page_for_status": self.page_for_status,
|
||||
"content_type": self.page_content_type,
|
||||
"edit_handler": bound_panel,
|
||||
"errors_debug": self.errors_debug,
|
||||
"action_menu": action_menu,
|
||||
"side_panels": side_panels,
|
||||
"form": self.form,
|
||||
"next": self.next_url,
|
||||
"history_url": self.get_history_url(),
|
||||
"has_unsaved_changes": self.has_unsaved_changes,
|
||||
"page_locked": self.locked_for_user,
|
||||
"workflow_state": self.workflow_state
|
||||
if self.workflow_state and self.workflow_state.is_active
|
||||
else None,
|
||||
"current_task_state": self.page.current_workflow_task_state,
|
||||
"publishing_will_cancel_workflow": self.workflow_tasks
|
||||
and getattr(settings, "WAGTAIL_WORKFLOW_CANCEL_ON_PUBLISH", True),
|
||||
"confirm_workflow_cancellation_url": reverse(
|
||||
"wagtailadmin_pages:confirm_workflow_cancellation",
|
||||
args=(self.page.id,),
|
||||
),
|
||||
"user_can_lock": (not self.lock or isinstance(self.lock, WorkflowLock))
|
||||
and user_perms.can_lock(),
|
||||
"user_can_unlock": isinstance(self.lock, BasicLock)
|
||||
and user_perms.can_unlock(),
|
||||
"locale": self.locale,
|
||||
"media": media,
|
||||
"editing_sessions": self.get_editing_sessions(),
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
|
||||
def get_translations(self):
|
||||
return [
|
||||
{
|
||||
"locale": translation.locale,
|
||||
"url": reverse("wagtailadmin_pages:edit", args=[translation.id]),
|
||||
}
|
||||
for translation in self.page.get_translations()
|
||||
.only("id", "locale", "depth")
|
||||
.select_related("locale")
|
||||
if translation.permissions_for_user(self.request.user).can_edit()
|
||||
]
|
||||
95
env/lib/python3.10/site-packages/wagtail/admin/views/pages/history.py
vendored
Normal file
95
env/lib/python3.10/site-packages/wagtail/admin/views/pages/history.py
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
import django_filters
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
from wagtail.admin.views.generic import history
|
||||
from wagtail.admin.views.pages.utils import (
|
||||
GenericPageBreadcrumbsMixin,
|
||||
)
|
||||
from wagtail.admin.widgets import BooleanRadioSelect
|
||||
from wagtail.models import Page, PageLogEntry
|
||||
from wagtail.permissions import page_permission_policy
|
||||
|
||||
|
||||
class PageHistoryFilterSet(history.HistoryFilterSet):
|
||||
is_commenting_action = django_filters.BooleanFilter(
|
||||
label=gettext_lazy("Is commenting action"),
|
||||
method="filter_is_commenting_action",
|
||||
widget=BooleanRadioSelect,
|
||||
)
|
||||
|
||||
def filter_is_commenting_action(self, queryset, name, value):
|
||||
if value is None:
|
||||
return queryset
|
||||
|
||||
q = Q(action__startswith="wagtail.comments")
|
||||
if value is False:
|
||||
q = ~q
|
||||
|
||||
return queryset.filter(q)
|
||||
|
||||
|
||||
class PageWorkflowHistoryViewMixin:
|
||||
model = Page
|
||||
pk_url_kwarg = "page_id"
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.object.permissions_for_user(request.user).can_edit():
|
||||
raise PermissionDenied
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(**kwargs, page=self.object)
|
||||
|
||||
|
||||
class WorkflowHistoryView(PageWorkflowHistoryViewMixin, history.WorkflowHistoryView):
|
||||
header_icon = "doc-empty-inverse"
|
||||
workflow_history_url_name = "wagtailadmin_pages:workflow_history"
|
||||
workflow_history_detail_url_name = "wagtailadmin_pages:workflow_history_detail"
|
||||
|
||||
|
||||
class WorkflowHistoryDetailView(
|
||||
PageWorkflowHistoryViewMixin, history.WorkflowHistoryDetailView
|
||||
):
|
||||
object_icon = "doc-empty-inverse"
|
||||
workflow_history_url_name = "wagtailadmin_pages:workflow_history"
|
||||
|
||||
|
||||
class PageHistoryView(GenericPageBreadcrumbsMixin, history.HistoryView):
|
||||
template_name = "wagtailadmin/pages/history.html"
|
||||
filterset_class = PageHistoryFilterSet
|
||||
model = Page
|
||||
pk_url_kwarg = "page_id"
|
||||
permission_policy = page_permission_policy
|
||||
any_permission_required = {
|
||||
"add",
|
||||
"change",
|
||||
"publish",
|
||||
"bulk_delete",
|
||||
"lock",
|
||||
"unlock",
|
||||
}
|
||||
history_url_name = "wagtailadmin_pages:history"
|
||||
history_results_url_name = "wagtailadmin_pages:history_results"
|
||||
edit_url_name = "wagtailadmin_pages:edit"
|
||||
revisions_view_url_name = "wagtailadmin_pages:revisions_view"
|
||||
revisions_revert_url_name = "wagtailadmin_pages:revisions_revert"
|
||||
revisions_compare_url_name = "wagtailadmin_pages:revisions_compare"
|
||||
revisions_unschedule_url_name = "wagtailadmin_pages:revisions_unschedule"
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(Page, id=self.pk).specific
|
||||
|
||||
def get_page_subtitle(self):
|
||||
return self.object.get_admin_display_title()
|
||||
|
||||
def user_can_unschedule(self):
|
||||
return self.object.permissions_for_user(self.request.user).can_unschedule()
|
||||
|
||||
def get_base_queryset(self):
|
||||
return self._annotate_queryset(PageLogEntry.objects.filter(page=self.object))
|
||||
|
||||
def _annotate_queryset(self, queryset):
|
||||
return super()._annotate_queryset(queryset).select_related("page")
|
||||
523
env/lib/python3.10/site-packages/wagtail/admin/views/pages/listing.py
vendored
Normal file
523
env/lib/python3.10/site-packages/wagtail/admin/views/pages/listing.py
vendored
Normal file
@@ -0,0 +1,523 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db.models import F
|
||||
from django.forms import CheckboxSelectMultiple, RadioSelect
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property, classproperty
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_filters.filters import (
|
||||
ChoiceFilter,
|
||||
DateFromToRangeFilter,
|
||||
ModelMultipleChoiceFilter,
|
||||
)
|
||||
|
||||
from wagtail import hooks
|
||||
from wagtail.admin.filters import (
|
||||
DateRangePickerWidget,
|
||||
MultipleContentTypeFilter,
|
||||
MultipleUserFilter,
|
||||
WagtailFilterSet,
|
||||
)
|
||||
from wagtail.admin.forms.search import SearchForm
|
||||
from wagtail.admin.ui.components import MediaContainer
|
||||
from wagtail.admin.ui.side_panels import (
|
||||
PageStatusSidePanel,
|
||||
)
|
||||
from wagtail.admin.ui.tables import Column, DateColumn
|
||||
from wagtail.admin.ui.tables.pages import (
|
||||
BulkActionsColumn,
|
||||
NavigateToChildrenColumn,
|
||||
OrderingColumn,
|
||||
PageStatusColumn,
|
||||
PageTable,
|
||||
PageTitleColumn,
|
||||
)
|
||||
from wagtail.admin.views import generic
|
||||
from wagtail.models import Page, PageLogEntry, Site, get_page_content_types
|
||||
from wagtail.permissions import page_permission_policy
|
||||
|
||||
|
||||
class SiteFilter(ModelMultipleChoiceFilter):
|
||||
def get_filter_predicate(self, v):
|
||||
return {"path__startswith": v.root_page.path}
|
||||
|
||||
|
||||
class HasChildPagesFilter(ChoiceFilter):
|
||||
def filter(self, qs, value):
|
||||
if value == "true":
|
||||
return qs.filter(numchild__gt=0)
|
||||
elif value == "false":
|
||||
return qs.filter(numchild=0)
|
||||
else: # None / empty string
|
||||
return qs
|
||||
|
||||
|
||||
class EditedByFilter(MultipleUserFilter):
|
||||
def filter(self, qs, value):
|
||||
if value:
|
||||
qs = qs.filter(
|
||||
pk__in=PageLogEntry.objects.filter(
|
||||
action="wagtail.edit", user__in=value
|
||||
)
|
||||
.order_by()
|
||||
.values_list("page_id", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
return qs
|
||||
|
||||
|
||||
class PageFilterSet(WagtailFilterSet):
|
||||
latest_revision_created_at = DateFromToRangeFilter(
|
||||
label=_("Date updated"),
|
||||
widget=DateRangePickerWidget,
|
||||
)
|
||||
owner = MultipleUserFilter(
|
||||
label=_("Owner"),
|
||||
queryset=(
|
||||
lambda request: get_user_model().objects.filter(
|
||||
pk__in=Page.objects.order_by()
|
||||
.values_list("owner_id", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
),
|
||||
widget=CheckboxSelectMultiple,
|
||||
)
|
||||
edited_by = EditedByFilter(
|
||||
label=_("Edited by"),
|
||||
queryset=(
|
||||
lambda request: get_user_model().objects.filter(
|
||||
pk__in=PageLogEntry.objects.filter(action="wagtail.edit")
|
||||
.order_by()
|
||||
.values_list("user_id", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
),
|
||||
widget=CheckboxSelectMultiple,
|
||||
)
|
||||
site = SiteFilter(
|
||||
label=_("Site"),
|
||||
queryset=Site.objects.all(),
|
||||
widget=CheckboxSelectMultiple,
|
||||
)
|
||||
has_child_pages = HasChildPagesFilter(
|
||||
label=_("Has child pages"),
|
||||
empty_label=_("Any"),
|
||||
choices=[
|
||||
("true", _("Yes")),
|
||||
("false", _("No")),
|
||||
],
|
||||
widget=RadioSelect,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Page
|
||||
fields = [] # only needed for filters being generated automatically
|
||||
|
||||
|
||||
class ExplorablePageFilterSet(PageFilterSet):
|
||||
content_type = MultipleContentTypeFilter(
|
||||
label=_("Page type"),
|
||||
queryset=lambda request: get_page_content_types(include_base_page_type=False),
|
||||
widget=CheckboxSelectMultiple,
|
||||
)
|
||||
|
||||
|
||||
class IndexView(generic.IndexView):
|
||||
template_name = "wagtailadmin/pages/index.html"
|
||||
results_template_name = "wagtailadmin/pages/index_results.html"
|
||||
permission_policy = page_permission_policy
|
||||
any_permission_required = {
|
||||
"add",
|
||||
"change",
|
||||
"publish",
|
||||
"bulk_delete",
|
||||
"lock",
|
||||
"unlock",
|
||||
}
|
||||
context_object_name = "pages"
|
||||
page_kwarg = "p"
|
||||
paginate_by = 50
|
||||
table_class = PageTable
|
||||
table_classname = "listing full-width"
|
||||
filterset_class = PageFilterSet
|
||||
index_url_name = None
|
||||
index_results_url_name = None
|
||||
default_ordering = "-latest_revision_created_at"
|
||||
model = Page
|
||||
_show_breadcrumbs = True
|
||||
|
||||
columns = [
|
||||
BulkActionsColumn("bulk_actions"),
|
||||
PageTitleColumn(
|
||||
"title",
|
||||
label=_("Title"),
|
||||
sort_key="title",
|
||||
classname="title",
|
||||
),
|
||||
DateColumn(
|
||||
"latest_revision_created_at",
|
||||
label=_("Updated"),
|
||||
sort_key="latest_revision_created_at",
|
||||
width="12%",
|
||||
),
|
||||
PageStatusColumn(
|
||||
"status",
|
||||
label=_("Status"),
|
||||
sort_key="live",
|
||||
width="12%",
|
||||
),
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
# Search
|
||||
self.query_string = None
|
||||
self.is_searching = False
|
||||
if "q" in self.request.GET:
|
||||
self.search_form = SearchForm(self.request.GET)
|
||||
if self.search_form.is_valid():
|
||||
self.query_string = self.search_form.cleaned_data["q"]
|
||||
else:
|
||||
self.search_form = SearchForm()
|
||||
|
||||
if self.query_string:
|
||||
self.is_searching = True
|
||||
|
||||
return super().get(request)
|
||||
|
||||
def get_valid_orderings(self):
|
||||
valid_orderings = super().get_valid_orderings()
|
||||
|
||||
if self.is_searching:
|
||||
# ordering by content type not currently available when searching, due to
|
||||
# https://github.com/wagtail/wagtail/issues/6616
|
||||
try:
|
||||
valid_orderings.remove("content_type")
|
||||
valid_orderings.remove("-content_type")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return valid_orderings
|
||||
|
||||
def get_ordering(self):
|
||||
if self.is_searching and not self.is_explicitly_ordered:
|
||||
# default to ordering by relevance
|
||||
default_ordering = None
|
||||
else:
|
||||
default_ordering = self.default_ordering
|
||||
|
||||
ordering = self.request.GET.get("ordering", default_ordering)
|
||||
if ordering not in self.get_valid_orderings():
|
||||
ordering = default_ordering
|
||||
|
||||
return ordering
|
||||
|
||||
def get_base_queryset(self):
|
||||
pages = self.model.objects.filter(depth__gt=1)
|
||||
pages = self._annotate_queryset(pages)
|
||||
return pages
|
||||
|
||||
def _annotate_queryset(self, pages):
|
||||
pages = pages.prefetch_related("content_type", "sites_rooted_here").filter(
|
||||
pk__in=self.permission_policy.explorable_instances(
|
||||
self.request.user
|
||||
).values_list("pk", flat=True)
|
||||
)
|
||||
|
||||
# We want specific page instances, but do not need streamfield values here
|
||||
pages = pages.defer_streamfields().specific()
|
||||
|
||||
# Annotate queryset with various states to be used later for performance optimisations
|
||||
if getattr(settings, "WAGTAIL_WORKFLOW_ENABLED", True):
|
||||
pages = pages.prefetch_workflow_states()
|
||||
|
||||
pages = pages.annotate_site_root_state().annotate_approved_schedule()
|
||||
|
||||
return pages
|
||||
|
||||
def order_queryset(self, queryset):
|
||||
if self.is_searching and not self.is_explicitly_ordered:
|
||||
# search backend will order by relevance in this case, so don't bother to
|
||||
# apply an ordering on the queryset
|
||||
return queryset
|
||||
|
||||
if self.ordering == "ord":
|
||||
# preserve the native ordering from get_children()
|
||||
pass
|
||||
elif self.ordering == "latest_revision_created_at" and not self.is_searching:
|
||||
# order by oldest revision first.
|
||||
# Special case NULL entries - these should go at the top of the list.
|
||||
# Skip this special case when searching (and fall through to plain field ordering
|
||||
# instead) as search backends do not support F objects in order_by
|
||||
queryset = queryset.order_by(
|
||||
F("latest_revision_created_at").asc(nulls_first=True)
|
||||
)
|
||||
elif self.ordering == "-latest_revision_created_at" and not self.is_searching:
|
||||
# order by oldest revision first.
|
||||
# Special case NULL entries - these should go at the end of the list.
|
||||
# Skip this special case when searching (and fall through to plain field ordering
|
||||
# instead) as search backends do not support F objects in order_by
|
||||
queryset = queryset.order_by(
|
||||
F("latest_revision_created_at").desc(nulls_last=True)
|
||||
)
|
||||
else:
|
||||
queryset = super().order_queryset(queryset)
|
||||
|
||||
return queryset
|
||||
|
||||
def search_queryset(self, queryset):
|
||||
if self.is_searching:
|
||||
queryset = queryset.autocomplete(
|
||||
self.query_string, order_by_relevance=(not self.is_explicitly_ordered)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_index_url(self):
|
||||
return reverse(self.index_url_name)
|
||||
|
||||
def get_index_results_url(self):
|
||||
return reverse(self.index_results_url_name)
|
||||
|
||||
def get_breadcrumbs_items(self):
|
||||
return self.breadcrumbs_items + [{"url": "", "label": self.get_page_title()}]
|
||||
|
||||
def get_table_kwargs(self):
|
||||
kwargs = super().get_table_kwargs()
|
||||
kwargs["actions_next_url"] = self.index_url
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context.update(
|
||||
{
|
||||
"ordering": self.ordering,
|
||||
"search_form": self.search_form,
|
||||
"is_searching": self.is_searching,
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class ExplorableIndexView(IndexView):
|
||||
"""
|
||||
A version of the page listing where the user is presented with a view of a specified parent page;
|
||||
normally this will show the children of that page, but it may show results from the whole tree while
|
||||
searching or filtering.
|
||||
"""
|
||||
|
||||
template_name = "wagtailadmin/pages/explorable_index.html"
|
||||
index_url_name = "wagtailadmin_explore"
|
||||
index_results_url_name = "wagtailadmin_explore_results"
|
||||
page_title = _("Exploring")
|
||||
filterset_class = ExplorablePageFilterSet
|
||||
|
||||
@classproperty
|
||||
def columns(cls):
|
||||
columns = super().columns.copy()
|
||||
columns.insert(
|
||||
3,
|
||||
Column(
|
||||
"type",
|
||||
label=_("Type"),
|
||||
accessor="page_type_display_name",
|
||||
sort_key="content_type",
|
||||
width="12%",
|
||||
),
|
||||
)
|
||||
columns.append(NavigateToChildrenColumn("navigate", width="10%"))
|
||||
return columns
|
||||
|
||||
def get(self, request, parent_page_id=None):
|
||||
if parent_page_id:
|
||||
self.parent_page = get_object_or_404(
|
||||
Page.objects.all().prefetch_workflow_states(), id=parent_page_id
|
||||
)
|
||||
else:
|
||||
self.parent_page = Page.get_first_root_node()
|
||||
|
||||
# This will always succeed because of the check performed by PermissionCheckedMixin.
|
||||
root_page = self.permission_policy.explorable_root_instance(request.user)
|
||||
|
||||
# If this page isn't a descendant of the user's explorable root page,
|
||||
# then redirect to that explorable root page instead.
|
||||
if not (
|
||||
self.parent_page.pk == root_page.pk
|
||||
or self.parent_page.is_descendant_of(root_page)
|
||||
):
|
||||
return redirect(self.index_url_name, root_page.pk)
|
||||
|
||||
self.parent_page = self.parent_page.specific
|
||||
self.scheduled_page = self.parent_page.get_scheduled_revision_as_object()
|
||||
|
||||
self.i18n_enabled = getattr(settings, "WAGTAIL_I18N_ENABLED", False)
|
||||
if self.i18n_enabled and not self.parent_page.is_root():
|
||||
self.locale = self.parent_page.locale
|
||||
self.translations = self.get_translations()
|
||||
else:
|
||||
self.locale = None
|
||||
self.translations = []
|
||||
|
||||
return super().get(request)
|
||||
|
||||
@cached_property
|
||||
def is_searching_whole_tree(self):
|
||||
return bool(self.request.GET.get("search_all")) and (
|
||||
self.is_searching or self.is_filtering
|
||||
)
|
||||
|
||||
def get_base_queryset(self):
|
||||
if self.is_searching or self.is_filtering:
|
||||
if self.is_searching_whole_tree:
|
||||
pages = Page.objects.all()
|
||||
else:
|
||||
pages = self.parent_page.get_descendants()
|
||||
else:
|
||||
pages = self.parent_page.get_children()
|
||||
|
||||
pages = self._annotate_queryset(pages)
|
||||
return pages
|
||||
|
||||
def search_queryset(self, queryset):
|
||||
# allow hooks to modify queryset. This should happen as close as possible to the
|
||||
# final queryset, but (for backward compatibility) needs to be passed an actual queryset
|
||||
# rather than a search result object
|
||||
for hook in hooks.get_hooks("construct_explorer_page_queryset"):
|
||||
queryset = hook(self.parent_page, queryset, self.request)
|
||||
return super().search_queryset(queryset)
|
||||
|
||||
def get_index_url(self):
|
||||
return reverse(self.index_url_name, args=[self.parent_page.id])
|
||||
|
||||
def get_index_results_url(self):
|
||||
return reverse(self.index_results_url_name, args=[self.parent_page.id])
|
||||
|
||||
def get_history_url(self):
|
||||
permissions = self.parent_page.permissions_for_user(self.request.user)
|
||||
if permissions.can_view_revisions():
|
||||
return reverse("wagtailadmin_pages:history", args=[self.parent_page.id])
|
||||
|
||||
def get_table_kwargs(self):
|
||||
kwargs = super().get_table_kwargs()
|
||||
kwargs["use_row_ordering_attributes"] = self.show_ordering_column
|
||||
kwargs["parent_page"] = self.parent_page
|
||||
kwargs["show_locale_labels"] = self.i18n_enabled and self.parent_page.is_root()
|
||||
|
||||
if self.show_ordering_column:
|
||||
kwargs["caption"] = _(
|
||||
"Focus on the drag button and press up or down arrows to move the item, then press enter to submit the change."
|
||||
)
|
||||
kwargs["attrs"] = {
|
||||
"data-controller": "w-orderable",
|
||||
"data-w-orderable-active-class": "w-orderable--active",
|
||||
"data-w-orderable-chosen-class": "w-orderable__item--active",
|
||||
"data-w-orderable-container-value": "tbody",
|
||||
"data-w-orderable-message-value": _(
|
||||
"'%(page_title)s' has been moved successfully."
|
||||
)
|
||||
% {"page_title": "__LABEL__"},
|
||||
"data-w-orderable-url-value": reverse(
|
||||
"wagtailadmin_pages:set_page_position", args=[999999]
|
||||
),
|
||||
}
|
||||
return kwargs
|
||||
|
||||
def get_valid_orderings(self):
|
||||
valid_orderings = super().get_valid_orderings()
|
||||
|
||||
if not self.is_searching:
|
||||
# ordering by page order is only available when not searching
|
||||
valid_orderings.append("ord")
|
||||
|
||||
return valid_orderings
|
||||
|
||||
def get_ordering(self):
|
||||
if self.is_searching and not self.is_explicitly_ordered:
|
||||
# default to ordering by relevance
|
||||
default_ordering = None
|
||||
else:
|
||||
default_ordering = self.parent_page.get_admin_default_ordering()
|
||||
|
||||
ordering = self.request.GET.get("ordering", default_ordering)
|
||||
if ordering not in self.get_valid_orderings():
|
||||
ordering = default_ordering
|
||||
|
||||
return ordering
|
||||
|
||||
def get_paginate_by(self, queryset):
|
||||
if self.ordering == "ord":
|
||||
# Don't paginate if sorting by page order - all pages must be shown to
|
||||
# allow drag-and-drop reordering
|
||||
return None
|
||||
else:
|
||||
return self.paginate_by
|
||||
|
||||
def get_page_subtitle(self):
|
||||
return self.parent_page.get_admin_display_title()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
self.show_ordering_column = self.ordering == "ord"
|
||||
if self.show_ordering_column:
|
||||
self.columns = self.columns.copy()
|
||||
self.columns[0] = OrderingColumn("ordering", width="80px", sort_key="ord")
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
if self.is_searching:
|
||||
# postprocess this page of results to annotate each result with its parent page
|
||||
parent_page_paths = {
|
||||
page.path[: -page.steplen] for page in context["object_list"]
|
||||
}
|
||||
parent_pages_by_path = {
|
||||
page.path: page
|
||||
for page in Page.objects.filter(path__in=parent_page_paths).specific()
|
||||
}
|
||||
for page in context["object_list"]:
|
||||
parent_page = parent_pages_by_path.get(page.path[: -page.steplen])
|
||||
# add annotation if parent page is found and is not the currently viewed parent
|
||||
if parent_page and parent_page != self.parent_page:
|
||||
page.annotated_parent_page = parent_page
|
||||
|
||||
context.update(
|
||||
{
|
||||
"parent_page": self.parent_page,
|
||||
"history_url": self.get_history_url(),
|
||||
"is_searching_whole_tree": self.is_searching_whole_tree,
|
||||
}
|
||||
)
|
||||
|
||||
if not self.results_only:
|
||||
side_panels = self.get_side_panels()
|
||||
context["side_panels"] = side_panels
|
||||
context["media"] += side_panels.media
|
||||
|
||||
return context
|
||||
|
||||
def get_side_panels(self):
|
||||
# Don't show side panels on the root page
|
||||
if self.parent_page.is_root():
|
||||
return MediaContainer()
|
||||
|
||||
side_panels = [
|
||||
PageStatusSidePanel(
|
||||
self.parent_page.get_latest_revision_as_object(),
|
||||
self.request,
|
||||
show_schedule_publishing_toggle=False,
|
||||
live_object=self.parent_page,
|
||||
scheduled_object=self.scheduled_page,
|
||||
locale=self.locale,
|
||||
translations=self.translations,
|
||||
),
|
||||
]
|
||||
return MediaContainer(side_panels)
|
||||
|
||||
def get_translations(self):
|
||||
return [
|
||||
{
|
||||
"locale": translation.locale,
|
||||
"url": reverse(self.index_url_name, args=[translation.id]),
|
||||
}
|
||||
for translation in self.parent_page.get_translations()
|
||||
.only("id", "locale")
|
||||
.select_related("locale")
|
||||
]
|
||||
38
env/lib/python3.10/site-packages/wagtail/admin/views/pages/lock.py
vendored
Normal file
38
env/lib/python3.10/site-packages/wagtail/admin/views/pages/lock.py
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail.admin.views.generic import lock
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
class PageOperationViewMixin:
|
||||
model = Page
|
||||
pk_url_kwarg = "page_id"
|
||||
|
||||
def get_object(self):
|
||||
return super().get_object().specific
|
||||
|
||||
def get_success_url(self):
|
||||
if self.next_url:
|
||||
return self.next_url
|
||||
return reverse("wagtailadmin_explore", args=[self.object.get_parent().id])
|
||||
|
||||
|
||||
class LockView(PageOperationViewMixin, lock.LockView):
|
||||
def perform_operation(self):
|
||||
if not self.object.permissions_for_user(self.request.user).can_lock():
|
||||
raise PermissionDenied
|
||||
return super().perform_operation()
|
||||
|
||||
|
||||
class UnlockView(PageOperationViewMixin, lock.UnlockView):
|
||||
def perform_operation(self):
|
||||
if not self.object.permissions_for_user(self.request.user).can_unlock():
|
||||
raise PermissionDenied
|
||||
return super().perform_operation()
|
||||
|
||||
def get_success_message(self):
|
||||
return _("Page '%(page_title)s' is now unlocked.") % {
|
||||
"page_title": self.object.get_admin_display_title()
|
||||
}
|
||||
142
env/lib/python3.10/site-packages/wagtail/admin/views/pages/move.py
vendored
Normal file
142
env/lib/python3.10/site-packages/wagtail/admin/views/pages/move.py
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail import hooks
|
||||
from wagtail.actions.move_page import MovePageAction
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.forms.pages import MoveForm
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
def move_choose_destination(request, page_to_move_id):
|
||||
page_to_move = get_object_or_404(Page, id=page_to_move_id)
|
||||
page_perms = page_to_move.permissions_for_user(request.user)
|
||||
if not page_perms.can_move():
|
||||
raise PermissionDenied
|
||||
|
||||
target_parent_models = set(page_to_move.specific_class.allowed_parent_page_models())
|
||||
|
||||
move_form = MoveForm(
|
||||
request.POST or None,
|
||||
page_to_move=page_to_move,
|
||||
target_parent_models=target_parent_models,
|
||||
)
|
||||
|
||||
if request.method == "POST":
|
||||
if move_form.is_valid():
|
||||
# Receive the new parent page (this should never be empty)
|
||||
if move_form.cleaned_data["new_parent_page"]:
|
||||
new_parent_page = move_form.cleaned_data["new_parent_page"]
|
||||
return redirect(
|
||||
"wagtailadmin_pages:move_confirm",
|
||||
page_to_move.id,
|
||||
new_parent_page.id,
|
||||
)
|
||||
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"wagtailadmin/pages/move_choose_destination.html",
|
||||
{
|
||||
"page_to_move": page_to_move,
|
||||
"move_form": move_form,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def move_confirm(request, page_to_move_id, destination_id):
|
||||
page_to_move = get_object_or_404(Page, id=page_to_move_id).specific
|
||||
# Needs .specific_deferred because the .get_admin_display_title method is called in template
|
||||
destination = get_object_or_404(Page, id=destination_id).specific_deferred
|
||||
|
||||
if not Page._slug_is_available(page_to_move.slug, destination, page=page_to_move):
|
||||
messages.error(
|
||||
request,
|
||||
_(
|
||||
"The slug '%(page_slug)s' is already in use at the selected parent page. Make sure the slug is unique and try again"
|
||||
)
|
||||
% {"page_slug": page_to_move.slug},
|
||||
)
|
||||
return redirect(
|
||||
"wagtailadmin_pages:move",
|
||||
page_to_move.id,
|
||||
)
|
||||
|
||||
for fn in hooks.get_hooks("before_move_page"):
|
||||
result = fn(request, page_to_move, destination)
|
||||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
pages_to_move = {page_to_move}
|
||||
|
||||
# The `construct_translated_pages_to_cascade_actions` hook returns translation and
|
||||
# alias pages when the action is set to "move"
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
fn_pages = fn([page_to_move], "move")
|
||||
if fn_pages and isinstance(fn_pages, dict):
|
||||
for additional_pages in fn_pages.values():
|
||||
pages_to_move.update(additional_pages)
|
||||
|
||||
pages_to_move = list(pages_to_move)
|
||||
|
||||
if request.method == "POST":
|
||||
# any invalid moves *should* be caught by the permission check in the action
|
||||
# class, so don't bother to catch InvalidMoveToDescendant
|
||||
action = MovePageAction(
|
||||
page_to_move, destination, pos="last-child", user=request.user
|
||||
)
|
||||
action.execute()
|
||||
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
# Move translation and alias pages if they have the same parent page.
|
||||
parent_page_translations = page_to_move.get_parent().get_translations()
|
||||
for translation in pages_to_move:
|
||||
if translation.get_parent() in parent_page_translations:
|
||||
# Move the translated or alias page to it's translated or
|
||||
# alias "destination" page.
|
||||
action = MovePageAction(
|
||||
translation,
|
||||
destination.get_translation(translation.locale),
|
||||
pos="last-child",
|
||||
user=request.user,
|
||||
)
|
||||
action.execute()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
_("Page '%(page_title)s' moved.")
|
||||
% {"page_title": page_to_move.get_admin_display_title()},
|
||||
buttons=[
|
||||
messages.button(
|
||||
reverse("wagtailadmin_pages:edit", args=(page_to_move.id,)),
|
||||
_("Edit"),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
for fn in hooks.get_hooks("after_move_page"):
|
||||
result = fn(request, page_to_move)
|
||||
if hasattr(result, "status_code"):
|
||||
return result
|
||||
|
||||
return redirect("wagtailadmin_explore", destination.id)
|
||||
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"wagtailadmin/pages/confirm_move.html",
|
||||
{
|
||||
"page_to_move": page_to_move,
|
||||
"destination": destination,
|
||||
"translations_to_move_count": len(
|
||||
[
|
||||
translation.id
|
||||
for translation in pages_to_move
|
||||
if not translation.alias_of_id and translation.id != page_to_move.id
|
||||
]
|
||||
),
|
||||
},
|
||||
)
|
||||
44
env/lib/python3.10/site-packages/wagtail/admin/views/pages/ordering.py
vendored
Normal file
44
env/lib/python3.10/site-packages/wagtail/admin/views/pages/ordering.py
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
def set_page_position(request, page_to_move_id):
|
||||
page_to_move = get_object_or_404(Page, id=page_to_move_id)
|
||||
parent_page = page_to_move.get_parent()
|
||||
|
||||
if not parent_page.permissions_for_user(request.user).can_reorder_children():
|
||||
raise PermissionDenied
|
||||
|
||||
if request.method == "POST":
|
||||
# Get position parameter
|
||||
position = request.GET.get("position", None)
|
||||
|
||||
# Find page that's already in this position
|
||||
position_page = None
|
||||
if position is not None:
|
||||
try:
|
||||
position_page = parent_page.get_children()[int(position)]
|
||||
except IndexError:
|
||||
pass # No page in this position
|
||||
|
||||
# Move page
|
||||
|
||||
# any invalid moves *should* be caught by the permission check above,
|
||||
# so don't bother to catch InvalidMoveToDescendant
|
||||
|
||||
if position_page:
|
||||
# If the page has been moved to the right, insert it to the
|
||||
# right. If left, then left.
|
||||
old_position = list(parent_page.get_children()).index(page_to_move)
|
||||
if int(position) < old_position:
|
||||
page_to_move.move(position_page, pos="left", user=request.user)
|
||||
elif int(position) > old_position:
|
||||
page_to_move.move(position_page, pos="right", user=request.user)
|
||||
else:
|
||||
# Move page to end
|
||||
page_to_move.move(parent_page, pos="last-child", user=request.user)
|
||||
|
||||
return HttpResponse("")
|
||||
103
env/lib/python3.10/site-packages/wagtail/admin/views/pages/preview.py
vendored
Normal file
103
env/lib/python3.10/site-packages/wagtail/admin/views/pages/preview.py
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from wagtail.admin.views.generic.preview import PreviewOnEdit as GenericPreviewOnEdit
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
def view_draft(request, page_id):
|
||||
page = get_object_or_404(Page, id=page_id).get_latest_revision_as_object()
|
||||
perms = page.permissions_for_user(request.user)
|
||||
if not (perms.can_publish() or perms.can_edit()):
|
||||
raise PermissionDenied
|
||||
|
||||
try:
|
||||
preview_mode = page.default_preview_mode
|
||||
except IndexError:
|
||||
raise PermissionDenied
|
||||
|
||||
return page.make_preview_request(request, preview_mode)
|
||||
|
||||
|
||||
class PreviewOnEdit(GenericPreviewOnEdit):
|
||||
@property
|
||||
def session_key(self):
|
||||
return "{}{}".format(self.session_key_prefix, self.kwargs["page_id"])
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(
|
||||
Page, id=self.kwargs["page_id"]
|
||||
).get_latest_revision_as_object()
|
||||
|
||||
def get_form(self, query_dict):
|
||||
form_class = self.object.get_edit_handler().get_form_class()
|
||||
parent_page = self.object.get_parent().specific
|
||||
|
||||
if not query_dict:
|
||||
# Query dict is empty, return null form
|
||||
return form_class(
|
||||
instance=self.object,
|
||||
parent_page=parent_page,
|
||||
for_user=self.request.user,
|
||||
)
|
||||
|
||||
return form_class(
|
||||
query_dict,
|
||||
instance=self.object,
|
||||
parent_page=parent_page,
|
||||
for_user=self.request.user,
|
||||
)
|
||||
|
||||
|
||||
class PreviewOnCreate(PreviewOnEdit):
|
||||
@property
|
||||
def session_key(self):
|
||||
return "{}{}-{}-{}".format(
|
||||
self.session_key_prefix,
|
||||
self.kwargs["content_type_app_name"],
|
||||
self.kwargs["content_type_model_name"],
|
||||
self.kwargs["parent_page_id"],
|
||||
)
|
||||
|
||||
def get_object(self):
|
||||
content_type_app_name = self.kwargs["content_type_app_name"]
|
||||
content_type_model_name = self.kwargs["content_type_model_name"]
|
||||
parent_page_id = self.kwargs["parent_page_id"]
|
||||
try:
|
||||
content_type = ContentType.objects.get_by_natural_key(
|
||||
content_type_app_name, content_type_model_name
|
||||
)
|
||||
except ContentType.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
page = content_type.model_class()()
|
||||
parent_page = get_object_or_404(Page, id=parent_page_id).specific
|
||||
# We need to populate treebeard's path / depth fields in order to
|
||||
# pass validation. We can't make these 100% consistent with the rest
|
||||
# of the tree without making actual database changes (such as
|
||||
# incrementing the parent's numchild field), but by calling treebeard's
|
||||
# internal _get_path method, we can set a 'realistic' value that will
|
||||
# hopefully enable tree traversal operations
|
||||
# to at least partially work.
|
||||
page.depth = parent_page.depth + 1
|
||||
# Puts the page at the next available path
|
||||
# for a child of `parent_page`.
|
||||
if parent_page.is_leaf():
|
||||
# set the path as the first child of parent_page
|
||||
page.path = page._get_path(parent_page.path, page.depth, 1)
|
||||
else:
|
||||
# add the new page after the last child of parent_page
|
||||
page.path = parent_page.get_last_child()._inc_path()
|
||||
|
||||
return page
|
||||
|
||||
def get_form(self, query_dict):
|
||||
form = super().get_form(query_dict)
|
||||
if form.is_valid():
|
||||
# Ensures our unsaved page has a suitable url.
|
||||
form.instance.set_url_path(form.parent_page)
|
||||
|
||||
form.instance.full_clean()
|
||||
return form
|
||||
206
env/lib/python3.10/site-packages/wagtail/admin/views/pages/revisions.py
vendored
Normal file
206
env/lib/python3.10/site-packages/wagtail/admin/views/pages/revisions.py
vendored
Normal file
@@ -0,0 +1,206 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template.loader import render_to_string
|
||||
from django.template.response import TemplateResponse
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext as _
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.action_menu import PageActionMenu
|
||||
from wagtail.admin.auth import user_has_any_page_permission, user_passes_test
|
||||
from wagtail.admin.ui.components import MediaContainer
|
||||
from wagtail.admin.ui.side_panels import (
|
||||
ChecksSidePanel,
|
||||
CommentsSidePanel,
|
||||
PageStatusSidePanel,
|
||||
PreviewSidePanel,
|
||||
)
|
||||
from wagtail.admin.views.generic.models import (
|
||||
RevisionsCompareView,
|
||||
RevisionsUnscheduleView,
|
||||
)
|
||||
from wagtail.admin.views.generic.preview import PreviewRevision
|
||||
from wagtail.models import Page
|
||||
from wagtail.utils.timestamps import render_timestamp
|
||||
|
||||
|
||||
def revisions_index(request, page_id):
|
||||
return redirect("wagtailadmin_pages:history", page_id)
|
||||
|
||||
|
||||
def revisions_revert(request, page_id, revision_id):
|
||||
# TODO: refactor this into a class-based view that extends the EditView
|
||||
page = get_object_or_404(Page, id=page_id).specific
|
||||
page_perms = page.permissions_for_user(request.user)
|
||||
if not page_perms.can_edit():
|
||||
raise PermissionDenied
|
||||
|
||||
revision = get_object_or_404(page.revisions, id=revision_id)
|
||||
revision_page = revision.as_object()
|
||||
|
||||
scheduled_page = page.get_scheduled_revision_as_object()
|
||||
|
||||
content_type = ContentType.objects.get_for_model(page)
|
||||
page_class = content_type.model_class()
|
||||
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
locale = page.locale
|
||||
translations = [
|
||||
{
|
||||
"locale": translation.locale,
|
||||
"url": reverse("wagtailadmin_pages:edit", args=[translation.id]),
|
||||
}
|
||||
for translation in page.get_translations()
|
||||
.only("id", "locale", "depth")
|
||||
.select_related("locale")
|
||||
if translation.permissions_for_user(request.user).can_edit()
|
||||
]
|
||||
else:
|
||||
locale = None
|
||||
translations = []
|
||||
|
||||
edit_handler = page_class.get_edit_handler()
|
||||
form_class = edit_handler.get_form_class()
|
||||
|
||||
form = form_class(instance=revision_page, for_user=request.user)
|
||||
edit_handler = edit_handler.get_bound_panel(
|
||||
instance=revision_page, request=request, form=form
|
||||
)
|
||||
|
||||
preview_url = reverse("wagtailadmin_pages:preview_on_edit", args=[page.id])
|
||||
lock = page.get_lock()
|
||||
|
||||
action_menu = PageActionMenu(
|
||||
request,
|
||||
view="revisions_revert",
|
||||
is_revision=True,
|
||||
page=page,
|
||||
lock=lock,
|
||||
locked_for_user=lock is not None and lock.for_user(request.user),
|
||||
)
|
||||
side_panels = [
|
||||
PageStatusSidePanel(
|
||||
revision_page,
|
||||
request,
|
||||
show_schedule_publishing_toggle=form.show_schedule_publishing_toggle,
|
||||
live_object=page,
|
||||
scheduled_object=scheduled_page,
|
||||
locale=locale,
|
||||
translations=translations,
|
||||
),
|
||||
]
|
||||
if page.is_previewable():
|
||||
side_panels.append(PreviewSidePanel(page, request, preview_url=preview_url))
|
||||
side_panels.append(ChecksSidePanel(page, request))
|
||||
if form.show_comments_toggle:
|
||||
side_panels.append(CommentsSidePanel(page, request))
|
||||
side_panels = MediaContainer(side_panels)
|
||||
|
||||
media = MediaContainer([edit_handler, form, action_menu, side_panels]).media
|
||||
|
||||
user_avatar = render_to_string(
|
||||
"wagtailadmin/shared/user_avatar.html", {"user": revision.user}
|
||||
)
|
||||
|
||||
messages.warning(
|
||||
request,
|
||||
mark_safe(
|
||||
_(
|
||||
"You are viewing a previous version of this page from <b>%(created_at)s</b> by %(user)s"
|
||||
)
|
||||
% {
|
||||
"created_at": render_timestamp(revision.created_at),
|
||||
"user": user_avatar,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
page_title = _("Editing %(page_type)s") % {
|
||||
"page_type": page_class.get_verbose_name()
|
||||
}
|
||||
page_subtitle = page.get_admin_display_title()
|
||||
header_title = f"{page_title}: {page_subtitle}"
|
||||
|
||||
return TemplateResponse(
|
||||
request,
|
||||
"wagtailadmin/pages/edit.html",
|
||||
{
|
||||
"page": page,
|
||||
"revision": revision,
|
||||
"is_revision": True,
|
||||
"content_type": content_type,
|
||||
"edit_handler": edit_handler,
|
||||
"errors_debug": None,
|
||||
"action_menu": action_menu,
|
||||
"side_panels": side_panels,
|
||||
"header_title": header_title,
|
||||
"form": form, # Used in unit tests
|
||||
"media": media,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(user_passes_test(user_has_any_page_permission), name="dispatch")
|
||||
class RevisionsView(PreviewRevision):
|
||||
model = Page
|
||||
|
||||
def setup(self, request, page_id, revision_id, *args, **kwargs):
|
||||
# Rename path kwargs from pk to page_id
|
||||
return super().setup(request, page_id, revision_id, *args, **kwargs)
|
||||
|
||||
def get_object(self):
|
||||
page = get_object_or_404(Page, id=self.pk).specific
|
||||
|
||||
perms = page.permissions_for_user(self.request.user)
|
||||
if not (perms.can_publish() or perms.can_edit()):
|
||||
raise PermissionDenied
|
||||
|
||||
return page
|
||||
|
||||
|
||||
class RevisionsCompare(RevisionsCompareView):
|
||||
history_label = gettext_lazy("Page history")
|
||||
edit_label = gettext_lazy("Edit this page")
|
||||
history_url_name = "wagtailadmin_pages:history"
|
||||
edit_url_name = "wagtailadmin_pages:edit"
|
||||
header_icon = "doc-empty-inverse"
|
||||
|
||||
@method_decorator(user_passes_test(user_has_any_page_permission))
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(Page, id=self.pk).specific
|
||||
|
||||
def get_edit_handler(self):
|
||||
return self.object.get_edit_handler()
|
||||
|
||||
def get_page_subtitle(self):
|
||||
return self.object.get_admin_display_title()
|
||||
|
||||
|
||||
class RevisionsUnschedule(RevisionsUnscheduleView):
|
||||
model = Page
|
||||
edit_url_name = "wagtailadmin_pages:edit"
|
||||
history_url_name = "wagtailadmin_pages:history"
|
||||
revisions_unschedule_url_name = "wagtailadmin_pages:revisions_unschedule"
|
||||
header_icon = "doc-empty-inverse"
|
||||
|
||||
def setup(self, request, page_id, revision_id, *args, **kwargs):
|
||||
# Rename path kwargs from pk to page_id
|
||||
return super().setup(request, page_id, revision_id, *args, **kwargs)
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
page = get_object_or_404(Page, id=self.pk).specific
|
||||
|
||||
if not page.permissions_for_user(self.request.user).can_unschedule():
|
||||
raise PermissionDenied
|
||||
return page
|
||||
|
||||
def get_object_display_title(self):
|
||||
return self.object.get_admin_display_title()
|
||||
192
env/lib/python3.10/site-packages/wagtail/admin/views/pages/search.py
vendored
Normal file
192
env/lib/python3.10/site-packages/wagtail/admin/views/pages/search.py
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models.query import QuerySet
|
||||
from django.http import Http404
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.forms.search import SearchForm
|
||||
from wagtail.admin.ui.tables import Column, DateColumn
|
||||
from wagtail.admin.ui.tables.pages import (
|
||||
BulkActionsColumn,
|
||||
NavigateToChildrenColumn,
|
||||
PageStatusColumn,
|
||||
PageTable,
|
||||
PageTitleColumn,
|
||||
ParentPageColumn,
|
||||
)
|
||||
from wagtail.admin.views.generic.base import BaseListingView
|
||||
from wagtail.admin.views.generic.permissions import PermissionCheckedMixin
|
||||
from wagtail.models import Page
|
||||
from wagtail.permissions import page_permission_policy
|
||||
from wagtail.search.query import MATCH_ALL
|
||||
from wagtail.search.utils import parse_query_string
|
||||
|
||||
|
||||
def page_filter_search(q, pages, all_pages=None, ordering=None):
|
||||
# Parse query
|
||||
filters, query = parse_query_string(q, operator="and", zero_terms=MATCH_ALL)
|
||||
|
||||
# Live filter
|
||||
live_filter = filters.get("live") or filters.get("published")
|
||||
live_filter = live_filter and live_filter.lower()
|
||||
|
||||
if live_filter in ["yes", "true"]:
|
||||
if all_pages is not None:
|
||||
all_pages = all_pages.filter(live=True)
|
||||
pages = pages.filter(live=True)
|
||||
elif live_filter in ["no", "false"]:
|
||||
if all_pages is not None:
|
||||
all_pages = all_pages.filter(live=False)
|
||||
pages = pages.filter(live=False)
|
||||
|
||||
# Search
|
||||
if all_pages is not None:
|
||||
all_pages = all_pages.autocomplete(query, order_by_relevance=not ordering)
|
||||
pages = pages.autocomplete(query, order_by_relevance=not ordering)
|
||||
|
||||
return pages, all_pages
|
||||
|
||||
|
||||
class BaseSearchView(PermissionCheckedMixin, BaseListingView):
|
||||
permission_policy = page_permission_policy
|
||||
any_permission_required = {
|
||||
"add",
|
||||
"change",
|
||||
"publish",
|
||||
"bulk_delete",
|
||||
"lock",
|
||||
"unlock",
|
||||
}
|
||||
paginate_by = 20
|
||||
page_kwarg = "p"
|
||||
context_object_name = "pages"
|
||||
table_class = PageTable
|
||||
index_url_name = "wagtailadmin_pages:search"
|
||||
|
||||
columns = [
|
||||
BulkActionsColumn("bulk_actions"),
|
||||
PageTitleColumn(
|
||||
"title",
|
||||
classname="title",
|
||||
label=_("Title"),
|
||||
sort_key="title",
|
||||
),
|
||||
ParentPageColumn("parent", label=_("Parent")),
|
||||
DateColumn(
|
||||
"latest_revision_created_at",
|
||||
label=_("Updated"),
|
||||
sort_key="latest_revision_created_at",
|
||||
width="12%",
|
||||
),
|
||||
Column(
|
||||
"type",
|
||||
label=_("Type"),
|
||||
accessor="page_type_display_name",
|
||||
width="12%",
|
||||
),
|
||||
PageStatusColumn(
|
||||
"status",
|
||||
label=_("Status"),
|
||||
sort_key="live",
|
||||
width="12%",
|
||||
),
|
||||
NavigateToChildrenColumn("navigate", width="10%"),
|
||||
]
|
||||
|
||||
def get(self, request):
|
||||
self.show_locale_labels = getattr(settings, "WAGTAIL_I18N_ENABLED", False)
|
||||
self.content_types = []
|
||||
self.ordering = None
|
||||
|
||||
if "ordering" in request.GET and request.GET["ordering"] in [
|
||||
"title",
|
||||
"-title",
|
||||
"latest_revision_created_at",
|
||||
"-latest_revision_created_at",
|
||||
"live",
|
||||
"-live",
|
||||
]:
|
||||
self.ordering = request.GET["ordering"]
|
||||
|
||||
if "content_type" in request.GET:
|
||||
try:
|
||||
app_label, model_name = request.GET["content_type"].split(".")
|
||||
except ValueError:
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
self.selected_content_type = ContentType.objects.get_by_natural_key(
|
||||
app_label, model_name
|
||||
)
|
||||
except ContentType.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
else:
|
||||
self.selected_content_type = None
|
||||
|
||||
self.q = self.request.GET.get("q", "")
|
||||
|
||||
return super().get(request)
|
||||
|
||||
def get_queryset(self) -> QuerySet[Any]:
|
||||
pages = self.all_pages = (
|
||||
Page.objects.all().prefetch_related("content_type").specific()
|
||||
)
|
||||
if self.show_locale_labels:
|
||||
pages = pages.select_related("locale")
|
||||
|
||||
if self.ordering:
|
||||
pages = pages.order_by(self.ordering)
|
||||
|
||||
if self.selected_content_type:
|
||||
pages = pages.filter(content_type=self.selected_content_type)
|
||||
|
||||
# Parse query and filter
|
||||
pages, self.all_pages = page_filter_search(
|
||||
self.q, pages, self.all_pages, self.ordering
|
||||
)
|
||||
|
||||
# Facets
|
||||
if pages.supports_facet:
|
||||
self.content_types = [
|
||||
(ContentType.objects.get(id=content_type_id), count)
|
||||
for content_type_id, count in self.all_pages.facet(
|
||||
"content_type_id"
|
||||
).items()
|
||||
]
|
||||
|
||||
return pages
|
||||
|
||||
def get_table_kwargs(self):
|
||||
kwargs = super().get_table_kwargs()
|
||||
kwargs["show_locale_labels"] = self.show_locale_labels
|
||||
kwargs["actions_next_url"] = self.get_index_url()
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
"all_pages": self.all_pages,
|
||||
"query_string": self.q,
|
||||
"content_types": self.content_types,
|
||||
"selected_content_type": self.selected_content_type,
|
||||
"ordering": self.ordering,
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class SearchView(BaseSearchView):
|
||||
template_name = "wagtailadmin/pages/search.html"
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["search_form"] = SearchForm(self.request.GET)
|
||||
return context
|
||||
|
||||
|
||||
class SearchResultsView(BaseSearchView):
|
||||
template_name = "wagtailadmin/pages/search_results.html"
|
||||
96
env/lib/python3.10/site-packages/wagtail/admin/views/pages/unpublish.py
vendored
Normal file
96
env/lib/python3.10/site-packages/wagtail/admin/views/pages/unpublish.py
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail import hooks
|
||||
from wagtail.actions.unpublish_page import UnpublishPageAction
|
||||
from wagtail.admin.utils import get_valid_next_url_from_request
|
||||
from wagtail.admin.views.generic.models import UnpublishView
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
class Unpublish(UnpublishView):
|
||||
model = Page
|
||||
index_url_name = "wagtailadmin_explore"
|
||||
edit_url_name = "wagtailadmin_pages:edit"
|
||||
unpublish_url_name = "wagtailadmin_pages:unpublish"
|
||||
usage_url_name = "wagtailadmin_pages:usage"
|
||||
success_message = _("Page '%(page_title)s' unpublished.")
|
||||
template_name = "wagtailadmin/pages/confirm_unpublish.html"
|
||||
header_icon = "doc-empty-inverse"
|
||||
|
||||
def setup(self, request, page_id, *args, **kwargs):
|
||||
# Rename path kwargs from pk to page_id
|
||||
return super().setup(request, page_id, *args, **kwargs)
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return get_object_or_404(Page, id=self.pk).specific
|
||||
|
||||
def get_object_display_title(self):
|
||||
return self.object.get_admin_display_title()
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.object.permissions_for_user(request.user).can_unpublish():
|
||||
raise PermissionDenied
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_success_message(self):
|
||||
return self.success_message % {
|
||||
"page_title": self.object.get_admin_display_title()
|
||||
}
|
||||
|
||||
def get_next_url(self):
|
||||
next_url = get_valid_next_url_from_request(self.request)
|
||||
if next_url:
|
||||
return next_url
|
||||
return reverse(self.index_url_name, args=(self.object.get_parent().id,))
|
||||
|
||||
def get_objects_to_unpublish(self):
|
||||
objects_to_unpublish = {self.object}
|
||||
|
||||
if getattr(settings, "WAGTAIL_I18N_ENABLED", False):
|
||||
for fn in hooks.get_hooks("construct_translated_pages_to_cascade_actions"):
|
||||
fn_pages = fn([self.object], "unpublish")
|
||||
if fn_pages and isinstance(fn_pages, dict):
|
||||
for additional_pages in fn_pages.values():
|
||||
objects_to_unpublish.update(additional_pages)
|
||||
|
||||
return list(objects_to_unpublish)
|
||||
|
||||
def unpublish(self):
|
||||
hook_response = self.run_hook(
|
||||
"before_unpublish_page", self.request, self.object
|
||||
)
|
||||
if hook_response is not None:
|
||||
return hook_response
|
||||
|
||||
include_descendants = self.request.POST.get("include_descendants", False)
|
||||
|
||||
for object in self.objects_to_unpublish:
|
||||
action = UnpublishPageAction(
|
||||
object, user=self.request.user, include_descendants=include_descendants
|
||||
)
|
||||
action.execute(skip_permission_checks=True)
|
||||
|
||||
hook_response = self.run_hook("after_unpublish_page", self.request, self.object)
|
||||
if hook_response is not None:
|
||||
return hook_response
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
"page": self.object,
|
||||
"live_descendant_count": self.object.get_descendants().live().count(),
|
||||
"translation_count": len(self.objects_to_unpublish[1:]),
|
||||
"translation_descendant_count": sum(
|
||||
[
|
||||
p.get_descendants().filter(alias_of__isnull=True).live().count()
|
||||
for p in self.objects_to_unpublish[1:]
|
||||
]
|
||||
),
|
||||
}
|
||||
)
|
||||
return context
|
||||
87
env/lib/python3.10/site-packages/wagtail/admin/views/pages/usage.py
vendored
Normal file
87
env/lib/python3.10/site-packages/wagtail/admin/views/pages/usage.py
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
from typing import Any, Dict
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import Http404
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from wagtail.admin.ui.tables import Column, DateColumn
|
||||
from wagtail.admin.ui.tables.pages import (
|
||||
PageStatusColumn,
|
||||
PageTable,
|
||||
PageTitleColumn,
|
||||
ParentPageColumn,
|
||||
)
|
||||
from wagtail.admin.views import generic
|
||||
from wagtail.admin.views.generic.base import BaseListingView
|
||||
from wagtail.admin.views.pages.utils import (
|
||||
GenericPageBreadcrumbsMixin,
|
||||
)
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
class ContentTypeUseView(BaseListingView):
|
||||
results_template_name = "wagtailadmin/pages/usage_results.html"
|
||||
page_title = _("Pages using")
|
||||
header_icon = "doc-empty-inverse"
|
||||
page_kwarg = "p"
|
||||
paginate_by = 50
|
||||
columns = [
|
||||
PageTitleColumn("title", classname="title", label=_("Title")),
|
||||
ParentPageColumn("parent", label=_("Parent")),
|
||||
DateColumn("latest_revision_created_at", label=_("Updated"), width="12%"),
|
||||
Column("type", label=_("Type"), accessor="page_type_display_name", width="12%"),
|
||||
PageStatusColumn("status", label=_("Status"), width="12%"),
|
||||
]
|
||||
table_class = PageTable
|
||||
table_classname = "listing"
|
||||
|
||||
def get(self, request, *, content_type_app_name, content_type_model_name):
|
||||
try:
|
||||
content_type = ContentType.objects.get_by_natural_key(
|
||||
content_type_app_name, content_type_model_name
|
||||
)
|
||||
except ContentType.DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
self.page_class = content_type.model_class()
|
||||
|
||||
# page_class must be a Page type and not some other random model
|
||||
if not issubclass(self.page_class, Page):
|
||||
raise Http404
|
||||
|
||||
return super().get(request)
|
||||
|
||||
def get_page_subtitle(self):
|
||||
return self.page_class.get_verbose_name()
|
||||
|
||||
def get_queryset(self):
|
||||
return self.page_class.objects.all().specific(defer=True)
|
||||
|
||||
def get_index_url(self):
|
||||
return reverse(
|
||||
"wagtailadmin_pages:type_use",
|
||||
args=[
|
||||
self.kwargs["content_type_app_name"],
|
||||
self.kwargs["content_type_model_name"],
|
||||
],
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["page_class"] = self.page_class
|
||||
return context
|
||||
|
||||
|
||||
class UsageView(GenericPageBreadcrumbsMixin, generic.UsageView):
|
||||
model = Page
|
||||
pk_url_kwarg = "page_id"
|
||||
header_icon = "doc-empty-inverse"
|
||||
usage_url_name = "wagtailadmin_pages:usage"
|
||||
edit_url_name = "wagtailadmin_pages:edit"
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if not self.object.permissions_for_user(request.user).can_edit():
|
||||
raise PermissionDenied
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
71
env/lib/python3.10/site-packages/wagtail/admin/views/pages/utils.py
vendored
Normal file
71
env/lib/python3.10/site-packages/wagtail/admin/views/pages/utils.py
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
# Retain backwards compatibility for imports
|
||||
from wagtail.admin.utils import ( # noqa: F401
|
||||
get_latest_str,
|
||||
get_valid_next_url_from_request,
|
||||
)
|
||||
from wagtail.permissions import page_permission_policy
|
||||
|
||||
|
||||
def get_breadcrumbs_items_for_page(
|
||||
page,
|
||||
user,
|
||||
url_name="wagtailadmin_explore",
|
||||
root_url_name="wagtailadmin_explore_root",
|
||||
include_self=True,
|
||||
querystring_value="",
|
||||
):
|
||||
# find the closest common ancestor of the pages that this user has direct explore permission
|
||||
# (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb
|
||||
cca = page_permission_policy.explorable_root_instance(user)
|
||||
if not cca:
|
||||
return []
|
||||
|
||||
pages = (
|
||||
page.get_ancestors(inclusive=include_self)
|
||||
.descendant_of(cca, inclusive=True)
|
||||
.specific(defer=True)
|
||||
)
|
||||
|
||||
items = []
|
||||
for page in pages:
|
||||
if page.is_root() and root_url_name:
|
||||
url = reverse(root_url_name)
|
||||
else:
|
||||
url = reverse(url_name, args=(page.id,))
|
||||
items.append({"url": url + querystring_value, "label": get_latest_str(page)})
|
||||
|
||||
return items
|
||||
|
||||
|
||||
class GenericPageBreadcrumbsMixin:
|
||||
"""
|
||||
A mixin that allows a view for pages that extends a generic view to combine
|
||||
the page explorer breadcrumbs with the generic view's breadcrumbs.
|
||||
|
||||
This is done by generating the explorer breadcrumbs items for the page as a
|
||||
normalised breadcrumbs items list, and then concatenating that with the last
|
||||
item of the generic view's generated breadcrumbs items.
|
||||
"""
|
||||
|
||||
_show_breadcrumbs = True
|
||||
breadcrumbs_items_to_take = 1
|
||||
|
||||
@cached_property
|
||||
def breadcrumbs_items(self):
|
||||
return get_breadcrumbs_items_for_page(self.object, self.request.user)
|
||||
|
||||
def get_breadcrumbs_items(self):
|
||||
# The generic view tends to generate breadcrumbs with items such as
|
||||
# IndexView > EditView > CurrentView,
|
||||
# but we don't want that because we want the preceding items to be links
|
||||
# to the explore view of the page's ancestors for consistency with how
|
||||
# page breadcrumbs have always worked. So we only take the last N items,
|
||||
# which in most cases is the final item that links to the current view.
|
||||
# However, this can be customised in the case of generic views that are
|
||||
# nested inside another generic view.
|
||||
return self.breadcrumbs_items + [
|
||||
super().get_breadcrumbs_items()[-self.breadcrumbs_items_to_take]
|
||||
]
|
||||
54
env/lib/python3.10/site-packages/wagtail/admin/views/pages/workflow.py
vendored
Normal file
54
env/lib/python3.10/site-packages/wagtail/admin/views/pages/workflow.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail.admin import messages
|
||||
from wagtail.admin.utils import get_latest_str
|
||||
from wagtail.admin.views.generic import workflow
|
||||
from wagtail.models import Page
|
||||
|
||||
|
||||
class WorkflowPageViewMixin:
|
||||
model = Page
|
||||
pk_url_kwarg = "page_id"
|
||||
redirect_url_name = "wagtailadmin_pages:edit"
|
||||
|
||||
def add_not_in_moderation_error(self):
|
||||
messages.error(
|
||||
self.request,
|
||||
_("The page '%(page_title)s' is not currently awaiting moderation.")
|
||||
% {
|
||||
"page_title": get_latest_str(self.object),
|
||||
},
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
return super().get_context_data(page=self.object, **kwargs)
|
||||
|
||||
|
||||
class WorkflowAction(WorkflowPageViewMixin, workflow.WorkflowAction):
|
||||
submit_url_name = "wagtailadmin_pages:workflow_action"
|
||||
|
||||
|
||||
class CollectWorkflowActionData(
|
||||
WorkflowPageViewMixin, workflow.CollectWorkflowActionData
|
||||
):
|
||||
submit_url_name = "wagtailadmin_pages:collect_workflow_action_data"
|
||||
|
||||
|
||||
class ConfirmWorkflowCancellation(
|
||||
WorkflowPageViewMixin, workflow.ConfirmWorkflowCancellation
|
||||
):
|
||||
template_name = "wagtailadmin/pages/confirm_workflow_cancellation.html"
|
||||
|
||||
|
||||
class PreviewRevisionForTask(WorkflowPageViewMixin, workflow.PreviewRevisionForTask):
|
||||
def add_error_message(self):
|
||||
messages.error(
|
||||
self.request,
|
||||
_(
|
||||
"The page '%(page_title)s' is not currently awaiting moderation in task '%(task_name)s'."
|
||||
)
|
||||
% {
|
||||
"page_title": get_latest_str(self.object),
|
||||
"task_name": self.task.name,
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user