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 # 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 = self.page_class(owner=self.request.user) self.page.locale = self.locale 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 dispatch(self, request, page_id): self.real_page_record = get_object_or_404(Page, id=page_id) self.latest_revision = self.real_page_record.get_latest_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_page() self.parent = self.page.get_parent() self.page_perms = self.page.permissions_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 try: self.subscription = PageSubscription.objects.get( page=self.page, user=self.request.user) except PageSubscription.DoesNotExist: self.subscription = PageSubscription(page=self.page, user=self.request.user, comment_notifications=False) self.edit_handler = self.page_class.get_edit_handler() self.edit_handler = self.edit_handler.bind_to(instance=self.page, request=self.request) 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 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)
class CreateView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View): template_name = "wagtailadmin/pages/create.html" 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 # 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 = self.page_class(owner=self.request.user) self.page.locale = self.locale 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_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=False) # Save subscription settings self.subscription.page = self.page self.subscription.save() # Notification messages.success( self.request, _("Page '{0}' created.").format( 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=False) # 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 '{0}' created and scheduled for publishing.").format( 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 '{0}' created and published.").format( 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=False) # 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 '{0}' created and submitted for moderation.").format( 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_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) 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, "preview_modes": self.page.preview_modes, "form": self.form, "next": self.next_url, "has_unsaved_changes": self.has_unsaved_changes, "locale": None, "translations": [], "media": bound_panel.media + self.form.media + action_menu.media, }) if getattr(settings, "WAGTAIL_I18N_ENABLED", False): # Pages can be created in any language at the root level if self.parent_page.is_root(): translations = [{ "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}), } for locale in Locale.objects.all()] else: user_perms = UserPagePermissionsProxy(self.request.user) translations = [ { "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 user_perms.for_page(translation).can_add_subpage() and self.page_class in translation.specific_class.creatable_subpage_models() and self.page_class.can_create_at(translation) ] context.update({ "locale": self.locale, "translations": translations, }) return context
class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View): 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_legacy_moderation_warning(self): # Check for revisions still undergoing moderation and warn - this is for the old moderation system if self.latest_revision and self.latest_revision.submitted_for_moderation: buttons = [] if self.page.live: buttons.append(self.get_compare_with_live_message_button()) messages.warning( self.request, _("This page is currently awaiting moderation"), buttons=buttons, ) def add_save_confirmation_message(self): if self.is_reverting: message = _( "Page '{0}' has been replaced with version from {1}.").format( self.page.get_admin_display_title(), self.previous_revision.created_at.strftime( "%d %b %Y %H:%M"), ) else: message = _("Page '{0}' has been updated.").format( 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, id=page_id) self.latest_revision = self.real_page_record.get_latest_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_page() self.parent = self.page.get_parent() self.page_perms = self.page.permissions_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 try: self.subscription = PageSubscription.objects.get( page=self.page, user=self.request.user) except PageSubscription.DoesNotExist: self.subscription = PageSubscription(page=self.page, user=self.request.user, 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 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.page_perms.user_has_lock(): if self.page.locked_at: lock_message = format_html( _("<b>Page '{}' was locked</b> by <b>you</b> on <b>{}</b>." ), self.page.get_admin_display_title(), self.page.locked_at.strftime("%d %b %Y %H:%M"), ) else: lock_message = format_html( _("<b>Page '{}' is locked</b> by <b>you</b>."), self.page.get_admin_display_title(), ) lock_message += format_html( '<span class="buttons"><button type="button" class="button button-small button-secondary" data-action-lock-unlock data-url="{}">{}</button></span>', reverse("wagtailadmin_pages:unlock", args=(self.page.id, )), _("Unlock"), ) messages.warning(self.request, lock_message, extra_tags="lock") elif self.page.locked and self.page_perms.page_locked(): # the page can also be locked at a permissions level if in a workflow, on a task the user is not a reviewer for # this should be indicated separately if self.page.locked_by and self.page.locked_at: lock_message = format_html( _("<b>Page '{}' was locked</b> by <b>{}</b> on <b>{}</b>." ), self.page.get_admin_display_title(), str(self.page.locked_by), self.page.locked_at.strftime("%d %b %Y %H:%M"), ) else: # Page was probably locked with an old version of Wagtail, or a script lock_message = format_html( _("<b>Page '{}' is locked</b>."), self.page.get_admin_display_title(), ) if self.page_perms.can_unlock(): lock_message += format_html( '<span class="buttons"><button type="button" class="button button-small button-secondary" data-action-lock-unlock data-url="{}">{}</button></span>', reverse("wagtailadmin_pages:unlock", args=(self.page.id, )), _("Unlock"), ) messages.error(self.request, lock_message, extra_tags="lock") if self.page.current_workflow_state: workflow = self.workflow_state.workflow task = self.workflow_state.current_task_state.task if (self.workflow_state.status != WorkflowState.STATUS_NEEDS_CHANGES and task.specific.page_locked_for_user( self.page, self.request.user)): # Check for revisions still undergoing moderation and warn if len(self.workflow_tasks) == 1: # If only one task in workflow, show simple message workflow_info = _( "This page is currently awaiting moderation.") else: workflow_info = format_html( _("This page is awaiting <b>'{}'</b> in the <b>'{}'</b> workflow." ), task.name, workflow.name, ) messages.error( self.request, mark_safe( workflow_info + " " + _("Only reviewers for this task can edit the page.")), 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.add_legacy_moderation_warning() 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 '{0}' has been cancelled.").format( 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.page_perms.page_locked(): 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=False) 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=False) 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 {0} of page '{1}' has been scheduled for publishing." ).format( self.previous_revision.created_at.strftime( "%d %b %Y %H:%M"), self.page.get_admin_display_title(), ) else: if self.page.live: message = _( "Page '{0}' is live and this version has been scheduled for publishing." ).format(self.page.get_admin_display_title()) else: message = _("Page '{0}' has been scheduled for publishing." ).format(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 {0} of page '{1}' has been published." ).format( self.previous_revision.created_at.strftime( "%d %b %Y %H:%M"), self.page.get_admin_display_title(), ) else: message = _("Page '{0}' has been published.").format( 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=False) 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 '{0}' has been submitted for moderation.").format( 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=False) 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 '{0}' has been restarted.").format( 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=False) 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=False) 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() if self.page_perms.page_locked(): 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.add_legacy_moderation_warning() self.page_for_status = self.get_page_for_status() return self.render_to_response(self.get_context_data()) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) 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) side_panels = PageSidePanels(self.request, self.page_for_status) 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, "preview_modes": self.page.preview_modes, "form": self.form, "next": self.next_url, "has_unsaved_changes": self.has_unsaved_changes, "page_locked": self.page_perms.page_locked(), "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), "locale": None, "translations": [], "media": bound_panel.media + self.form.media + action_menu.media + side_panels.media, }) if getattr(settings, "WAGTAIL_I18N_ENABLED", False): user_perms = UserPagePermissionsProxy(self.request.user) context.update({ "locale": self.page.locale, "translations": [{ "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 user_perms.for_page(translation).can_edit()], }) return context