def save_instance(self): """ Called after the form is successfully validated - saves the object to the db and returns the new object. Override this to implement custom save logic. """ if self.model and issubclass(self.model, DraftStateMixin): instance = self.form.save(commit=False) instance.live = False instance.save() self.form.save_m2m() else: instance = self.form.save() self.new_revision = None # Save revision if the model inherits from RevisionMixin if isinstance(instance, RevisionMixin): self.new_revision = instance.save_revision(user=self.request.user) log( instance=instance, action="wagtail.create", revision=self.new_revision, content_changed=True, ) return instance
def _unpublish_object(self, object, set_expired, commit, user, log_action): """ Unpublish the object by setting ``live`` to ``False``. Does nothing if ``live`` is already ``False`` :param log_action: flag for logging the action. Pass False to skip logging. Can be passed an action string. Defaults to 'wagtail.unpublish' """ if object.live: object.live = False object.has_unpublished_changes = True object.live_revision = None if set_expired: object.expired = True if commit: self._commit_unpublish(object) if log_action: log( instance=object, action=log_action if isinstance(log_action, str) else "wagtail.unpublish", user=user, ) logger.info('Unpublished: "%s" id=%d', str(object), object.id) object.revisions.update(approved_go_live_at=None) self._after_unpublish(object)
def save_instance(self): """ Called after the form is successfully validated - saves the object to the db. Override this to implement custom save logic. """ commit = not self.draftstate_enabled instance = self.form.save(commit=commit) self.new_revision = None self.has_content_changes = self.form.has_changed() # Save revision if the model inherits from RevisionMixin if self.revision_enabled: self.new_revision = instance.save_revision( user=self.request.user, changed=self.has_content_changes, ) log( instance=instance, action="wagtail.edit", revision=self.new_revision, content_changed=self.has_content_changes, ) return instance
def add(request): if request.method == "POST": form = RedirectForm(request.POST, request.FILES) if form.is_valid(): with transaction.atomic(): theredirect = form.save() log(instance=theredirect, action="wagtail.edit") messages.success( request, _("Redirect '{0}' added.").format(theredirect.title), buttons=[ messages.button( reverse("wagtailredirects:edit", args=(theredirect.id, )), _("Edit"), ) ], ) return redirect("wagtailredirects:index") else: messages.error( request, _("The redirect could not be created due to errors.")) else: form = RedirectForm() return TemplateResponse( request, "wagtailredirects/add.html", { "form": form, }, )
def delete(request, user_id): user = get_object_or_404(User, pk=user_id) if not user_can_delete_user(request.user, user): raise PermissionDenied for fn in hooks.get_hooks("before_delete_user"): result = fn(request, user) if hasattr(result, "status_code"): return result if request.method == "POST": with transaction.atomic(): log(user, "wagtail.delete") user.delete() messages.success(request, _("User '{0}' deleted.").format(user)) for fn in hooks.get_hooks("after_delete_user"): result = fn(request, user) if hasattr(result, "status_code"): return result return redirect("wagtailusers_users:index") return TemplateResponse( request, "wagtailusers/users/confirm_delete.html", { "user": user, }, )
def log_deletion(self, page): log( instance=page, action="wagtail.delete", user=self.user, deleted=True, )
def post(self, request, *args, **kwargs): try: msg = _("%(model_name)s '%(instance)s' deleted.") % { "model_name": self.verbose_name, "instance": self.instance, } with transaction.atomic(): log(instance=self.instance, action="wagtail.delete") self.delete_instance() messages.success(request, msg) return redirect(self.index_url) except models.ProtectedError: linked_objects = [] fields = self.model._meta.fields_map.values() fields = (obj for obj in fields if not isinstance(obj.field, ManyToManyField)) for rel in fields: if rel.on_delete == models.PROTECT: if isinstance(rel, OneToOneRel): try: obj = getattr(self.instance, rel.get_accessor_name()) except ObjectDoesNotExist: pass else: linked_objects.append(obj) else: qs = getattr(self.instance, rel.get_accessor_name()) for obj in qs.all(): linked_objects.append(obj) context = self.get_context_data(protected_error=True, linked_objects=linked_objects) return self.render_to_response(context)
def add(request): if request.method == "POST": # Get query query_form = search_forms.QueryForm(request.POST) if query_form.is_valid(): query = Query.get(query_form["query_string"].value()) # Save search picks searchpicks_formset = forms.SearchPromotionsFormSet(request.POST, instance=query) if save_searchpicks(query, query, searchpicks_formset): for search_pick in searchpicks_formset.new_objects: log(search_pick, "wagtail.create") messages.success( request, _("Editor's picks for '{0}' created.").format(query), buttons=[ messages.button( reverse("wagtailsearchpromotions:edit", args=(query.id, )), _("Edit"), ) ], ) return redirect("wagtailsearchpromotions:index") else: if len(searchpicks_formset.non_form_errors()): # formset level error (e.g. no forms submitted) messages.error( request, " ".join(error for error in searchpicks_formset.non_form_errors()), ) else: # specific errors will be displayed within form fields messages.error( request, _("Recommendations have not been created due to errors" ), ) else: searchpicks_formset = forms.SearchPromotionsFormSet() else: query_form = search_forms.QueryForm() searchpicks_formset = forms.SearchPromotionsFormSet() return TemplateResponse( request, "wagtailsearchpromotions/add.html", { "query_form": query_form, "searchpicks_formset": searchpicks_formset, "form_media": query_form.media + searchpicks_formset.media, }, )
def form_valid(self, form): self.form = form with transaction.atomic(): self.object = self.save_instance() log(instance=self.object, action="wagtail.create") success_message = self.get_success_message(self.object) success_buttons = self.get_success_buttons() if success_message is not None: messages.success(self.request, success_message, buttons=success_buttons) return redirect(self.get_success_url())
def edit(request, user_id): user = get_object_or_404(User, pk=user_id) can_delete = user_can_delete_user(request.user, user) editing_self = request.user == user for fn in hooks.get_hooks("before_edit_user"): result = fn(request, user) if hasattr(result, "status_code"): return result if request.method == "POST": form = get_user_edit_form()(request.POST, request.FILES, instance=user, editing_self=editing_self) if form.is_valid(): with transaction.atomic(): user = form.save() log(user, "wagtail.edit") if user == request.user and "password1" in form.changed_data: # User is changing their own password; need to update their session hash update_session_auth_hash(request, user) messages.success( request, _("User '{0}' updated.").format(user), buttons=[ messages.button( reverse("wagtailusers_users:edit", args=(user.pk, )), _("Edit")) ], ) for fn in hooks.get_hooks("after_edit_user"): result = fn(request, user) if hasattr(result, "status_code"): return result return redirect("wagtailusers_users:index") else: messages.error(request, _("The user could not be saved due to errors.")) else: form = get_user_edit_form()(instance=user, editing_self=editing_self) return TemplateResponse( request, "wagtailusers/users/edit.html", { "user": user, "form": form, "can_delete": can_delete, }, )
def form_valid(self, form): self.form = form with transaction.atomic(): self.object = self.save_instance() log(instance=self.object, action="wagtail.edit") success_message = self.get_success_message() if success_message is not None: messages.success( self.request, success_message, buttons=[ messages.button( reverse(self.edit_url_name, args=(self.object.id, )), _("Edit")) ], ) return redirect(self.get_success_url())
def log_scheduling_action(self): log( instance=self.page, action="wagtail.publish.schedule", user=self.user, data={ "revision": { "id": self.revision.id, "created": self.revision.created_at.strftime("%d %b %Y %H:%M"), "go_live_at": self.page.go_live_at.strftime("%d %b %Y %H:%M"), "has_live_version": self.page.live, } }, revision=self.revision, content_changed=self.changed, )
def edit(request, redirect_id): theredirect = get_object_or_404(models.Redirect, id=redirect_id) if not permission_policy.user_has_permission_for_instance( request.user, "change", theredirect): raise PermissionDenied if request.method == "POST": form = RedirectForm(request.POST, request.FILES, instance=theredirect) if form.is_valid(): with transaction.atomic(): form.save() log(instance=theredirect, action="wagtail.edit") messages.success( request, _("Redirect '{0}' updated.").format(theredirect.title), buttons=[ messages.button( reverse("wagtailredirects:edit", args=(theredirect.id, )), _("Edit"), ) ], ) return redirect("wagtailredirects:index") else: messages.error(request, _("The redirect could not be saved due to errors.")) else: form = RedirectForm(instance=theredirect) return TemplateResponse( request, "wagtailredirects/edit.html", { "redirect": theredirect, "form": form, "user_can_delete": permission_policy.user_has_permission(request.user, "delete"), }, )
def delete(request, query_id): query = get_object_or_404(Query, id=query_id) if request.method == "POST": editors_picks = query.editors_picks.all() with transaction.atomic(): for search_pick in editors_picks: log(search_pick, "wagtail.delete") editors_picks.delete() messages.success(request, _("Editor's picks deleted.")) return redirect("wagtailsearchpromotions:index") return TemplateResponse( request, "wagtailsearchpromotions/confirm_delete.html", { "query": query, }, )
def create(request): for fn in hooks.get_hooks("before_create_user"): result = fn(request) if hasattr(result, "status_code"): return result if request.method == "POST": form = get_user_creation_form()(request.POST, request.FILES) if form.is_valid(): with transaction.atomic(): user = form.save() log(user, "wagtail.create") messages.success( request, _("User '{0}' created.").format(user), buttons=[ messages.button( reverse("wagtailusers_users:edit", args=(user.pk, )), _("Edit")) ], ) for fn in hooks.get_hooks("after_create_user"): result = fn(request, user) if hasattr(result, "status_code"): return result return redirect("wagtailusers_users:index") else: messages.error(request, _("The user could not be created due to errors.")) else: form = get_user_creation_form()() return TemplateResponse( request, "wagtailusers/users/create.html", { "form": form, }, )
def delete(request, redirect_id): theredirect = get_object_or_404(models.Redirect, id=redirect_id) if not permission_policy.user_has_permission_for_instance( request.user, "delete", theredirect): raise PermissionDenied if request.method == "POST": with transaction.atomic(): log(instance=theredirect, action="wagtail.delete") theredirect.delete() messages.success( request, _("Redirect '{0}' deleted.").format(theredirect.title)) return redirect("wagtailredirects:index") return TemplateResponse( request, "wagtailredirects/confirm_delete.html", { "redirect": theredirect, }, )
def create_redirects_from_dataset(dataset, config): errors = [] successes = 0 total = 0 for row in dataset: total += 1 from_link = row[config["from_index"]] to_link = row[config["to_index"]] data = { "old_path": from_link, "redirect_link": to_link, "is_permanent": config["permanent"], } if config["site"]: data["site"] = config["site"].pk form = RedirectForm(data) if not form.is_valid(): error = to_readable_errors(form.errors.as_text()) errors.append([from_link, to_link, error]) continue with transaction.atomic(): redirect = form.save() log(instance=redirect, action="wagtail.create") successes += 1 return { "errors": errors, "errors_count": len(errors), "successes": successes, "total": total, }
def _convert_alias(self, page, log_action, user): page.alias_of_id = None page.save(update_fields=["alias_of_id"], clean=False) # Create an initial revision revision = page.save_revision(user=user, changed=False, clean=False) if page.live: page.live_revision = revision page.save(update_fields=["live_revision"], clean=False) # Log if log_action: log( instance=page, action=log_action, revision=revision, user=user, data={ "page": {"id": page.id, "title": page.get_admin_display_title()}, }, ) return page
def _unpublish_page(self, page, set_expired, commit, user, log_action): """ Unpublish the page by setting ``live`` to ``False``. Does nothing if ``live`` is already ``False`` :param log_action: flag for logging the action. Pass False to skip logging. Can be passed an action string. Defaults to 'wagtail.unpublish' """ if page.live: page.live = False page.has_unpublished_changes = True page.live_revision = None if set_expired: page.expired = True if commit: # using clean=False to bypass validation page.save(clean=False) page_unpublished.send(sender=page.specific_class, instance=page.specific) if log_action: log( instance=page, action=log_action if isinstance(log_action, str) else "wagtail.unpublish", user=user, ) logger.info('Page unpublished: "%s" id=%d', page.title, page.id) page.revisions.update(approved_go_live_at=None) # Unpublish aliases for alias in page.aliases.all(): alias.unpublish()
def save_searchpicks(query, new_query, searchpicks_formset): # Save if searchpicks_formset.is_valid(): # Set sort_order for i, form in enumerate(searchpicks_formset.ordered_forms): form.instance.sort_order = i # Make sure the form is marked as changed so it gets saved with the new order form.has_changed = lambda: True # log deleted items before saving, otherwise we lose their IDs items_for_deletion = [ form.instance for form in searchpicks_formset.deleted_forms if form.instance.pk ] with transaction.atomic(): for search_pick in items_for_deletion: log(search_pick, "wagtail.delete") searchpicks_formset.save() for search_pick in searchpicks_formset.new_objects: log(search_pick, "wagtail.create") # If query was changed, move all search picks to the new query if query != new_query: searchpicks_formset.get_queryset().update(query=new_query) # log all items in the formset as having changed for search_pick, changed_fields in searchpicks_formset.changed_objects: log(search_pick, "wagtail.edit") else: # only log objects with actual changes for search_pick, changed_fields in searchpicks_formset.changed_objects: if changed_fields: log(search_pick, "wagtail.edit") return True else: return False
def account(request): # Fetch the user and profile objects once and pass into each panel # We need to use the same instances for all forms so they don't overwrite each other user = request.user profile = UserProfile.get_for_user(user) # Panels panels = [ NameEmailSettingsPanel(request, user, profile), AvatarSettingsPanel(request, user, profile), NotificationsSettingsPanel(request, user, profile), LocaleSettingsPanel(request, user, profile), ChangePasswordPanel(request, user, profile), ] for fn in hooks.get_hooks("register_account_settings_panel"): panel = fn(request, user, profile) if panel and panel.is_active(): panels.append(panel) panels = [panel for panel in panels if panel.is_active()] # Get tabs and order them tabs = list({panel.tab for panel in panels}) tabs.sort(key=lambda tab: tab.order) # Get dict of tabs to ordered panels panels_by_tab = OrderedDict([(tab, []) for tab in tabs]) for panel in panels: panels_by_tab[panel.tab].append(panel) for tab, tab_panels in panels_by_tab.items(): tab_panels.sort(key=lambda panel: panel.order) panel_forms = [panel.get_form() for panel in panels] if request.method == "POST": if all(form.is_valid() or not form.is_bound for form in panel_forms): with transaction.atomic(): for form in panel_forms: if form.is_bound: form.save() log(user, "wagtail.edit") # Prevent a password change from logging this user out update_session_auth_hash(request, user) # Override the language when creating the success message # If the user has changed their language in this request, the message should # be in the new language, not the existing one with override(profile.get_preferred_language()): messages.success( request, _("Your account settings have been changed successfully!") ) return redirect("wagtailadmin_account") media = Media() for form in panel_forms: media += form.media # Menu items menu_items = [] for fn in hooks.get_hooks("register_account_menu_item"): item = fn(request) if item: menu_items.append(item) return TemplateResponse( request, "wagtailadmin/account/account.html", { "panels_by_tab": panels_by_tab, "menu_items": menu_items, "media": media, }, )
def _publish_page_revision(self, revision, page, user, changed, log_action, previous_revision): from wagtail.models import COMMENTS_RELATION_NAME, PageRevision if page.go_live_at and page.go_live_at > timezone.now(): page.has_unpublished_changes = True # Instead set the approved_go_live_at of this revision revision.approved_go_live_at = page.go_live_at revision.save() # And clear the the approved_go_live_at of any other revisions page.revisions.exclude(id=revision.id).update( approved_go_live_at=None) # if we are updating a currently live page skip the rest if page.live_revision: # Log scheduled publishing if log_action: self.log_scheduling_action() return # if we have a go_live in the future don't make the page live page.live = False else: page.live = True # at this point, the page has unpublished changes if and only if there are newer revisions than this one page.has_unpublished_changes = not revision.is_latest_revision() # If page goes live clear the approved_go_live_at of all revisions page.revisions.update(approved_go_live_at=None) page.expired = False # When a page is published it can't be expired # Set first_published_at, last_published_at and live_revision # if the page is being published now if page.live: now = timezone.now() page.last_published_at = now page.live_revision = revision if page.first_published_at is None: page.first_published_at = now if previous_revision: previous_revision_page = previous_revision.as_page_object() old_page_title = (previous_revision_page.title if page.title != previous_revision_page.title else None) else: try: previous = revision.get_previous() except PageRevision.DoesNotExist: previous = None old_page_title = (previous.page.title if previous and page.title != previous.page.title else None) else: # Unset live_revision if the page is going live in the future page.live_revision = None page.save() for comment in getattr(page, COMMENTS_RELATION_NAME).all().only("position"): comment.save(update_fields=["position"]) revision.submitted_for_moderation = False page.revisions.update(submitted_for_moderation=False) workflow_state = page.current_workflow_state if workflow_state and getattr( settings, "WAGTAIL_WORKFLOW_CANCEL_ON_PUBLISH", True): workflow_state.cancel(user=user) if page.live: page_published.send(sender=page.specific_class, instance=page.specific, revision=revision) # Update alias pages page.update_aliases(revision=revision, user=user, _content=revision.content) if log_action: data = None if previous_revision: data = { "revision": { "id": previous_revision.id, "created": previous_revision.created_at.strftime( "%d %b %Y %H:%M"), } } if old_page_title: data = data or {} data["title"] = { "old": old_page_title, "new": page.title, } log( instance=page, action="wagtail.rename", user=user, data=data, revision=revision, ) log( instance=page, action=log_action if isinstance(log_action, str) else "wagtail.publish", user=user, data=data, revision=revision, content_changed=changed, ) logger.info( 'Page published: "%s" id=%d revision_id=%d', page.title, page.id, revision.id, ) elif page.go_live_at: logger.info( 'Page scheduled for publish: "%s" id=%d revision_id=%d go_live_at=%s', page.title, page.id, revision.id, page.go_live_at.isoformat(), ) if log_action: self.log_scheduling_action()
def _create_alias( self, page, *, recursive, parent, update_slug, update_locale, user, log_action, reset_translation_key, _mpnode_attrs, ): specific_page = page.specific # FIXME: Switch to the same fields that are excluded from copy # We can't do this right now because we can't exclude fields from with_content_json # which we use for updating aliases exclude_fields = [ "id", "path", "depth", "numchild", "url_path", "path", "index_entries", "postgres_index_entries", ] update_attrs = { "alias_of": page, # Aliases don't have revisions so the draft title should always match the live title "draft_title": page.title, # Likewise, an alias page can't have unpublished changes if it's live "has_unpublished_changes": not page.live, } if update_slug: update_attrs["slug"] = update_slug if update_locale: update_attrs["locale"] = update_locale if user: update_attrs["owner"] = user # When we're not copying for translation, we should give the translation_key a new value if reset_translation_key: update_attrs["translation_key"] = uuid.uuid4() alias, child_object_map = _copy(specific_page, update_attrs=update_attrs, exclude_fields=exclude_fields) # Update any translatable child objects for (child_relation, old_pk), child_object in child_object_map.items(): if isinstance(child_object, TranslatableMixin): if update_locale: child_object.locale = update_locale # When we're not copying for translation, # we should give the translation_key a new value for each child object as well. if reset_translation_key: child_object.translation_key = uuid.uuid4() # Save the new page if _mpnode_attrs: # We've got a tree position already reserved. Perform a quick save. alias.path = _mpnode_attrs[0] alias.depth = _mpnode_attrs[1] alias.save(clean=False) else: if parent: alias = parent.add_child(instance=alias) else: alias = page.add_sibling(instance=alias) _mpnode_attrs = (alias.path, alias.depth) _copy_m2m_relations(specific_page, alias, exclude_fields=exclude_fields) # Log if log_action: source_parent = specific_page.get_parent() log( instance=alias, action=log_action, user=user, data={ "page": { "id": alias.id, "title": alias.get_admin_display_title() }, "source": { "id": source_parent.id, "title": source_parent.specific_deferred. get_admin_display_title(), } if source_parent else None, "destination": { "id": parent.id, "title": parent.specific_deferred.get_admin_display_title(), } if parent else None, }, ) if alias.live: # Log the publish log( instance=alias, action="wagtail.publish", user=user, ) logger.info('Page alias created: "%s" id=%d from=%d', alias.title, alias.id, page.id) # Copy child pages if recursive: from wagtail.models import Page numchild = 0 for child_page in page.get_children().specific().iterator(): newdepth = _mpnode_attrs[1] + 1 child_mpnode_attrs = ( Page._get_path(_mpnode_attrs[0], newdepth, numchild), newdepth, ) numchild += 1 self._create_alias( child_page, recursive=True, parent=alias, update_slug=None, update_locale=update_locale, user=user, log_action=log_action, reset_translation_key=reset_translation_key, _mpnode_attrs=child_mpnode_attrs, ) if numchild > 0: alias.numchild = numchild alias.save(clean=False, update_fields=["numchild"]) return alias
def delete_action(self): with transaction.atomic(): log(instance=self.object, action="wagtail.delete") self.object.delete()
def edit(request, app_name, model_name, pk): model = get_model_from_url_params(app_name, model_name) if not user_can_edit_setting_type(request.user, model): raise PermissionDenied setting_type_name = model._meta.verbose_name edit_handler = get_setting_edit_handler(model) form_class = edit_handler.get_form_class() site: Optional[Site] = None site_switcher = None form_id: int = None if issubclass(model, BaseSiteSetting): site = get_object_or_404(Site, pk=pk) form_id = site.pk instance = model.for_site(site) if request.method == "POST": form = form_class( request.POST, request.FILES, instance=instance, for_user=request.user ) if form.is_valid(): with transaction.atomic(): form.save() log(instance, "wagtail.edit") messages.success( request, _("%(setting_type)s updated.") % { "setting_type": capfirst(setting_type_name), "instance": instance, }, ) return redirect("wagtailsettings:edit", app_name, model_name, site.pk) else: messages.validation_error( request, _("The setting could not be saved due to errors."), form ) else: form = form_class(instance=instance, for_user=request.user) edit_handler = edit_handler.get_bound_panel( instance=instance, request=request, form=form ) media = form.media + edit_handler.media # Show a site switcher form if there are multiple sites if Site.objects.count() > 1: site_switcher = SiteSwitchForm(site, model) media += site_switcher.media elif issubclass(model, BaseGenericSetting): queryset = model.base_queryset() # Create the instance if we haven't already. if queryset.count() == 0: model.objects.create() instance = get_object_or_404(model, pk=pk) form_id = instance.pk if request.method == "POST": form = form_class( request.POST, request.FILES, instance=instance, for_user=request.user ) if form.is_valid(): with transaction.atomic(): form.save() log(instance, "wagtail.edit") messages.success( request, _("%(setting_type)s updated.") % { "setting_type": capfirst(setting_type_name), "instance": instance, }, ) return redirect("wagtailsettings:edit", app_name, model_name) else: messages.validation_error( request, _("The setting could not be saved due to errors."), form ) else: form = form_class(instance=instance, for_user=request.user) edit_handler = edit_handler.get_bound_panel( instance=instance, request=request, form=form ) media = form.media + edit_handler.media else: raise NotImplementedError return TemplateResponse( request, "wagtailsettings/edit.html", { "opts": model._meta, "setting_type_name": setting_type_name, "instance": instance, "edit_handler": edit_handler, "form": form, "site": site, "site_switcher": site_switcher, "tabbed": isinstance(edit_handler.panel, TabbedInterface), "media": media, "form_id": form_id, }, )
def delete(request, app_label, model_name, pk=None): model = get_snippet_model_from_url_params(app_label, model_name) permission = get_permission_name("delete", model) if not request.user.has_perm(permission): raise PermissionDenied if pk: instances = [get_object_or_404(model, pk=unquote(pk))] else: ids = request.GET.getlist("id") instances = model.objects.filter(pk__in=ids) for fn in hooks.get_hooks("before_delete_snippet"): result = fn(request, instances) if hasattr(result, "status_code"): return result count = len(instances) if request.method == "POST": with transaction.atomic(): for instance in instances: log(instance=instance, action="wagtail.delete") instance.delete() if count == 1: message_content = _("%(snippet_type)s '%(instance)s' deleted.") % { "snippet_type": capfirst(model._meta.verbose_name), "instance": instance, } else: # This message is only used in plural form, but we'll define it with ngettext so that # languages with multiple plural forms can be handled correctly (or, at least, as # correctly as possible within the limitations of verbose_name_plural...) message_content = ngettext( "%(count)d %(snippet_type)s deleted.", "%(count)d %(snippet_type)s deleted.", count, ) % { "snippet_type": capfirst(model._meta.verbose_name_plural), "count": count, } messages.success(request, message_content) for fn in hooks.get_hooks("after_delete_snippet"): result = fn(request, instances) if hasattr(result, "status_code"): return result return redirect("wagtailsnippets:list", app_label, model_name) return TemplateResponse( request, "wagtailsnippets/snippets/confirm_delete.html", { "model_opts": model._meta, "count": count, "instances": instances, "submit_url": (reverse("wagtailsnippets:delete-multiple", args=(app_label, model_name)) + "?" + urlencode([("id", instance.pk) for instance in instances])), }, )
def edit(request, app_label, model_name, pk): model = get_snippet_model_from_url_params(app_label, model_name) permission = get_permission_name("change", model) if not request.user.has_perm(permission): raise PermissionDenied instance = get_object_or_404(model, pk=unquote(pk)) for fn in hooks.get_hooks("before_edit_snippet"): result = fn(request, instance) if hasattr(result, "status_code"): return result edit_handler = get_snippet_edit_handler(model) form_class = edit_handler.get_form_class() if request.method == "POST": form = form_class(request.POST, request.FILES, instance=instance, for_user=request.user) if form.is_valid(): with transaction.atomic(): form.save() log(instance=instance, action="wagtail.edit") messages.success( request, _("%(snippet_type)s '%(instance)s' updated.") % { "snippet_type": capfirst(model._meta.verbose_name), "instance": instance, }, buttons=[ messages.button( reverse( "wagtailsnippets:edit", args=(app_label, model_name, quote(instance.pk)), ), _("Edit"), ) ], ) for fn in hooks.get_hooks("after_edit_snippet"): result = fn(request, instance) if hasattr(result, "status_code"): return result return redirect("wagtailsnippets:list", app_label, model_name) else: messages.validation_error( request, _("The snippet could not be saved due to errors."), form) else: form = form_class(instance=instance, for_user=request.user) edit_handler = edit_handler.get_bound_panel(instance=instance, request=request, form=form) latest_log_entry = log_registry.get_logs_for_instance(instance).first() action_menu = SnippetActionMenu(request, view="edit", instance=instance) context = { "model_opts": model._meta, "instance": instance, "edit_handler": edit_handler, "form": form, "action_menu": action_menu, "locale": None, "translations": [], "latest_log_entry": latest_log_entry, "media": edit_handler.media + form.media + action_menu.media, } if getattr(settings, "WAGTAIL_I18N_ENABLED", False) and issubclass( model, TranslatableMixin): context.update({ "locale": instance.locale, "translations": [{ "locale": translation.locale, "url": reverse( "wagtailsnippets:edit", args=[app_label, model_name, quote(translation.pk)], ), } for translation in instance.get_translations().select_related( "locale")], }) return TemplateResponse(request, "wagtailsnippets/snippets/edit.html", context)
def form_valid(self, form): response = super().form_valid(form) log(instance=self.instance, action="wagtail.edit") return response
def _copy_page(self, page, to=None, update_attrs=None, exclude_fields=None, _mpnode_attrs=None): specific_page = page.specific exclude_fields = (specific_page.default_exclude_fields_in_copy + specific_page.exclude_fields_in_copy + (exclude_fields or [])) if self.keep_live: base_update_attrs = { "alias_of": None, } else: base_update_attrs = { "live": False, "has_unpublished_changes": True, "live_revision": None, "first_published_at": None, "last_published_at": None, "alias_of": None, } if self.user: base_update_attrs["owner"] = self.user # When we're not copying for translation, we should give the translation_key a new value if self.reset_translation_key: base_update_attrs["translation_key"] = uuid.uuid4() if update_attrs: base_update_attrs.update(update_attrs) page_copy, child_object_map = _copy(specific_page, exclude_fields=exclude_fields, update_attrs=base_update_attrs) # Save copied child objects and run process_child_object on them if we need to for (child_relation, old_pk), child_object in child_object_map.items(): if self.process_child_object: self.process_child_object(specific_page, page_copy, child_relation, child_object) if self.reset_translation_key and isinstance( child_object, TranslatableMixin): child_object.translation_key = self.generate_translation_key( child_object.translation_key) # Save the new page if _mpnode_attrs: # We've got a tree position already reserved. Perform a quick save page_copy.path = _mpnode_attrs[0] page_copy.depth = _mpnode_attrs[1] page_copy.save(clean=False) else: if to: page_copy = to.add_child(instance=page_copy) else: page_copy = page.add_sibling(instance=page_copy) _mpnode_attrs = (page_copy.path, page_copy.depth) _copy_m2m_relations( specific_page, page_copy, exclude_fields=exclude_fields, update_attrs=base_update_attrs, ) # Copy revisions if self.copy_revisions: for revision in page.revisions.all(): revision.pk = None revision.submitted_for_moderation = False revision.approved_go_live_at = None revision.page = page_copy # Update ID fields in content revision_content = revision.content revision_content["pk"] = page_copy.pk for child_relation in get_all_child_relations(specific_page): accessor_name = child_relation.get_accessor_name() try: child_objects = revision_content[accessor_name] except KeyError: # KeyErrors are possible if the revision was created # before this child relation was added to the database continue for child_object in child_objects: child_object[child_relation.field.name] = page_copy.pk # Remap primary key to copied versions # If the primary key is not recognised (eg, the child object has been deleted from the database) # set the primary key to None copied_child_object = child_object_map.get( (child_relation, child_object["pk"])) child_object["pk"] = (copied_child_object.pk if copied_child_object else None) if (self.reset_translation_key and "translation_key" in child_object): child_object[ "translation_key"] = self.generate_translation_key( child_object["translation_key"]) revision.content = revision_content # Save revision.save() # Create a new revision # This code serves a few purposes: # * It makes sure update_attrs gets applied to the latest revision # * It bumps the last_revision_created_at value so the new page gets ordered as if it was just created # * It sets the user of the new revision so it's possible to see who copied the page by looking at its history latest_revision = page_copy.get_latest_revision_as_page() if update_attrs: for field, value in update_attrs.items(): setattr(latest_revision, field, value) latest_revision_as_page_revision = latest_revision.save_revision( user=self.user, changed=False, clean=False) if self.keep_live: page_copy.live_revision = latest_revision_as_page_revision page_copy.last_published_at = latest_revision_as_page_revision.created_at page_copy.first_published_at = latest_revision_as_page_revision.created_at page_copy.save(clean=False) if page_copy.live: page_published.send( sender=page_copy.specific_class, instance=page_copy, revision=latest_revision_as_page_revision, ) # Log if self.log_action: parent = specific_page.get_parent() log( instance=page_copy, action=self.log_action, user=self.user, data={ "page": { "id": page_copy.id, "title": page_copy.get_admin_display_title(), "locale": { "id": page_copy.locale_id, "language_code": page_copy.locale.language_code, }, }, "source": { "id": parent.id, "title": parent.specific_deferred.get_admin_display_title(), } if parent else None, "destination": { "id": to.id, "title": to.specific_deferred.get_admin_display_title(), } if to else None, "keep_live": page_copy.live and self.keep_live, "source_locale": { "id": page.locale_id, "language_code": page.locale.language_code, }, }, ) if page_copy.live and self.keep_live: # Log the publish if the use chose to keep the copied page live log( instance=page_copy, action="wagtail.publish", user=self.user, revision=latest_revision_as_page_revision, ) logger.info('Page copied: "%s" id=%d from=%d', page_copy.title, page_copy.id, page.id) # Copy child pages from wagtail.models import Page if self.recursive: numchild = 0 for child_page in page.get_children().specific(): newdepth = _mpnode_attrs[1] + 1 child_mpnode_attrs = ( Page._get_path(_mpnode_attrs[0], newdepth, numchild), newdepth, ) numchild += 1 self._copy_page(child_page, to=page_copy, _mpnode_attrs=child_mpnode_attrs) if numchild > 0: page_copy.numchild = numchild page_copy.save(clean=False, update_fields=["numchild"]) return page_copy
def create(request, app_label, model_name): model = get_snippet_model_from_url_params(app_label, model_name) permission = get_permission_name("add", model) if not request.user.has_perm(permission): raise PermissionDenied for fn in hooks.get_hooks("before_create_snippet"): result = fn(request, model) if hasattr(result, "status_code"): return result instance = model() # Set locale of the new instance if issubclass(model, TranslatableMixin): selected_locale = request.GET.get("locale") if selected_locale: instance.locale = get_object_or_404(Locale, language_code=selected_locale) else: instance.locale = Locale.get_default() # Make edit handler edit_handler = get_snippet_edit_handler(model) form_class = edit_handler.get_form_class() if request.method == "POST": form = form_class(request.POST, request.FILES, instance=instance, for_user=request.user) if form.is_valid(): with transaction.atomic(): form.save() log(instance=instance, action="wagtail.create") messages.success( request, _("%(snippet_type)s '%(instance)s' created.") % { "snippet_type": capfirst(model._meta.verbose_name), "instance": instance, }, buttons=[ messages.button( reverse( "wagtailsnippets:edit", args=(app_label, model_name, quote(instance.pk)), ), _("Edit"), ) ], ) for fn in hooks.get_hooks("after_create_snippet"): result = fn(request, instance) if hasattr(result, "status_code"): return result urlquery = "" if (isinstance(instance, TranslatableMixin) and instance.locale is not Locale.get_default()): urlquery = "?locale=" + instance.locale.language_code return redirect( reverse("wagtailsnippets:list", args=[app_label, model_name]) + urlquery) else: messages.validation_error( request, _("The snippet could not be created due to errors."), form) else: form = form_class(instance=instance, for_user=request.user) edit_handler = edit_handler.get_bound_panel(request=request, instance=instance, form=form) action_menu = SnippetActionMenu(request, view="create", model=model) context = { "model_opts": model._meta, "edit_handler": edit_handler, "form": form, "action_menu": action_menu, "locale": None, "translations": [], "media": edit_handler.media + form.media + action_menu.media, } if getattr(settings, "WAGTAIL_I18N_ENABLED", False) and issubclass( model, TranslatableMixin): context.update({ "locale": instance.locale, "translations": [{ "locale": locale, "url": reverse("wagtailsnippets:add", args=[app_label, model_name]) + "?locale=" + locale.language_code, } for locale in Locale.objects.all().exclude(id=instance.locale.id) ], }) return TemplateResponse(request, "wagtailsnippets/snippets/create.html", context)