Beispiel #1
0
    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
Beispiel #2
0
    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)
Beispiel #3
0
    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
Beispiel #4
0
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,
        },
    )
Beispiel #5
0
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,
        },
    )
Beispiel #6
0
 def log_deletion(self, page):
     log(
         instance=page,
         action="wagtail.delete",
         user=self.user,
         deleted=True,
     )
Beispiel #7
0
 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)
Beispiel #8
0
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,
        },
    )
Beispiel #9
0
 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())
Beispiel #10
0
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,
        },
    )
Beispiel #11
0
 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,
     )
Beispiel #13
0
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"),
        },
    )
Beispiel #14
0
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,
        },
    )
Beispiel #15
0
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,
        },
    )
Beispiel #16
0
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,
        },
    )
Beispiel #17
0
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,
    }
Beispiel #18
0
    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
Beispiel #19
0
    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()
Beispiel #20
0
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
Beispiel #21
0
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()
Beispiel #23
0
    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
Beispiel #24
0
 def delete_action(self):
     with transaction.atomic():
         log(instance=self.object, action="wagtail.delete")
         self.object.delete()
Beispiel #25
0
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,
        },
    )
Beispiel #26
0
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])),
        },
    )
Beispiel #27
0
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)
Beispiel #28
0
 def form_valid(self, form):
     response = super().form_valid(form)
     log(instance=self.instance, action="wagtail.edit")
     return response
Beispiel #29
0
    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
Beispiel #30
0
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)