def reject_moderation(request, revision_id):
    revision = get_object_or_404(PageRevision, id=revision_id)
    if not revision.page.permissions_for_user(request.user).can_publish():
        raise PermissionDenied

    if not revision.submitted_for_moderation:
        messages.error(
            request,
            _("The page '{0}' is not currently awaiting moderation.").format(
                revision.page.get_admin_display_title()))
        return redirect('wagtailadmin_home')

    if request.method == 'POST':
        revision.reject_moderation(user=request.user)

        messages.success(request,
                         _("Page '{0}' rejected for publication.").format(
                             revision.page.get_admin_display_title()),
                         buttons=[
                             messages.button(
                                 reverse('wagtailadmin_pages:edit',
                                         args=(revision.page.id, )), _('Edit'))
                         ])

        if not send_notification(revision.id, 'rejected', request.user.pk):
            messages.error(request,
                           _("Failed to send rejection notifications"))

    return redirect('wagtailadmin_home')
def approve_moderation(request, revision_id):
    revision = get_object_or_404(PageRevision, id=revision_id)
    if not revision.page.permissions_for_user(request.user).can_publish():
        raise PermissionDenied

    if not revision.submitted_for_moderation:
        messages.error(
            request,
            _("The page '{0}' is not currently awaiting moderation.").format(
                revision.page.get_admin_display_title()))
        return redirect('wagtailadmin_home')

    if request.method == 'POST':
        revision.approve_moderation(user=request.user)

        message = _("Page '{0}' published.").format(
            revision.page.get_admin_display_title())
        buttons = []
        if revision.page.url is not None:
            buttons.append(
                messages.button(revision.page.url,
                                _('View live'),
                                new_window=True))
        buttons.append(
            messages.button(
                reverse('wagtailadmin_pages:edit', args=(revision.page.id, )),
                _('Edit')))
        messages.success(request, message, buttons=buttons)

        if not send_notification(revision.id, 'approved', request.user.pk):
            messages.error(request, _("Failed to send approval notifications"))

    return redirect('wagtailadmin_home')
Example #3
0
def ingest_content(type_: str):
    """For the given type_:
    * fetch the relevant data from the configured sources
    * for each item of source data:
        * create an appropriate ExternalContent page subclass, as a draft
    * update the last_sync timestamp for each configured source
    * submit all just-created draft pages for moderation
    """

    _now = tz_now()
    model_name = type_

    configs = IngestionConfiguration.objects.filter(integration_type=type_)

    factory_func = _get_factory_func(model_name)

    ingestion_user = User.objects.get(username=INGESTION_USER_USERNAME)

    for config in configs:
        draft_page_revision_buffer = []

        with transaction.atomic():
            data_from_source = fetch_external_data(
                feed_url=config.source_url, last_synced=config.last_sync)
            config.last_sync = _now
            config.save()

            for data in data_from_source:
                data.update(owner=ingestion_user)
                try:
                    draft_page = generate_draft_from_external_data(
                        factory_func=factory_func, data=data)
                except ValidationError as ve:
                    logger.warning("Problem ingesting article from %s: %s",
                                   data, ve)
                else:
                    draft_page.owner = ingestion_user
                    draft_page.save()
                    revision = draft_page.save_revision(
                        submitted_for_moderation=True, user=ingestion_user)
                    draft_page_revision_buffer.append(revision)

        # If the transaction completes, we send the notification emails. If the
        # notifications don't send even tho the data is now set in the DB, it's
        # not the end of the world: the main CMS admin page will still
        # show the items needing approval.
        if settings.NOTIFY_AFTER_INGESTING_CONTENT:
            for revision in draft_page_revision_buffer:
                notification_success = send_notification(
                    page_revision_id=revision.id,
                    notification="submitted",
                    excluded_user_id=ingestion_user.id,
                )
                if not notification_success:
                    logger.warning(
                        "Failed to send notification that %s was created.",
                        revision.page,
                    )
Example #4
0
    def send_commenting_notifications(self, changes):
        """
        Sends notifications about any changes to comments to anyone who is subscribed.
        """
        relevant_comment_ids = []
        relevant_comment_ids.extend(
            comment.pk for comment in changes['resolved_comments'])
        relevant_comment_ids.extend(
            comment.pk for comment, replies in changes['new_replies'])

        # Skip if no changes were made
        # Note: We don't email about edited comments so ignore those here
        if (not changes['new_comments'] and not changes['deleted_comments']
                and not changes['resolved_comments']
                and not changes['new_replies']):
            return

        # Get global page comment subscribers
        subscribers = PageSubscription.objects.filter(
            page=self.page, comment_notifications=True).select_related('user')
        global_recipient_users = [
            subscriber.user for subscriber in subscribers
            if subscriber.user != self.request.user
        ]

        # Get subscribers to individual threads
        replies = CommentReply.objects.filter(
            comment_id__in=relevant_comment_ids)
        comments = Comment.objects.filter(id__in=relevant_comment_ids)
        thread_users = get_user_model().objects.exclude(
            pk=self.request.user.pk).exclude(pk__in=subscribers.values_list(
                'user_id', flat=True)).prefetch_related(
                    Prefetch('comment_replies', queryset=replies),
                    Prefetch('comments', queryset=comments)).exclude(
                        Q(comment_replies__isnull=True)
                        & Q(comments__isnull=True))

        # Skip if no recipients
        if not (global_recipient_users or thread_users):
            return
        thread_users = [
            (user,
             set(
                 list(user.comment_replies.values_list('comment_id',
                                                       flat=True)) +
                 list(user.comments.values_list('pk', flat=True))))
            for user in thread_users
        ]
        mailed_users = set()

        for current_user, current_threads in thread_users:
            # We are trying to avoid calling send_notification for each user for performance reasons
            # so group the users receiving the same thread notifications together here
            if current_user in mailed_users:
                continue
            users = [current_user]
            mailed_users.add(current_user)
            for user, threads in thread_users:
                if user not in mailed_users and threads == current_threads:
                    users.append(user)
                    mailed_users.add(user)
            send_notification(
                users, 'updated_comments', {
                    'page':
                    self.page,
                    'editor':
                    self.request.user,
                    'new_comments': [
                        comment for comment in changes['new_comments']
                        if comment.pk in threads
                    ],
                    'resolved_comments': [
                        comment for comment in changes['resolved_comments']
                        if comment.pk in threads
                    ],
                    'deleted_comments': [],
                    'replied_comments': [{
                        'comment': comment,
                        'replies': replies,
                    } for comment, replies in changes['new_replies']
                                         if comment.pk in threads]
                })

        return send_notification(
            global_recipient_users, 'updated_comments', {
                'page':
                self.page,
                'editor':
                self.request.user,
                'new_comments':
                changes['new_comments'],
                'resolved_comments':
                changes['resolved_comments'],
                'deleted_comments':
                changes['deleted_comments'],
                'replied_comments': [{
                    'comment': comment,
                    'replies': replies,
                } for comment, replies in changes['new_replies']]
            })
Example #5
0
    def send_commenting_notifications(self):
        """
        Sends notifications about any changes to comments to anyone who is subscribed.
        """
        # Skip if this page does not have CommentPanel enabled
        if 'comments' not in self.form.formsets:
            return

        # Get changes
        comments_formset = self.form.formsets['comments']
        new_comments = comments_formset.new_objects
        deleted_comments = comments_formset.deleted_objects
        relevant_comment_ids = []

        # Assume any changed comments that are resolved were only just resolved
        resolved_comments = []
        for changed_comment, changed_fields in comments_formset.changed_objects:
            if changed_comment.resolved_at and 'resolved' in changed_fields:
                resolved_comments.append(changed_comment)
                relevant_comment_ids.append(changed_comment.pk)

        replied_comments = []
        for comment_form in comments_formset.forms:
            replies = getattr(comment_form.formsets['replies'], 'new_objects',
                              [])
            if replies:
                replied_comments.append({
                    'comment': comment_form.instance,
                    'replies': replies
                })
                relevant_comment_ids.append(comment_form.instance.pk)

        # Skip if no changes were made
        if not new_comments and not deleted_comments and not resolved_comments and not replied_comments:
            return

        # Get global page comment subscribers
        subscribers = PageSubscription.objects.filter(
            page=self.page, comment_notifications=True).select_related('user')
        global_recipient_users = [
            subscriber.user for subscriber in subscribers
            if subscriber.user != self.request.user
        ]

        # Get subscribers to individual threads
        replies = CommentReply.objects.filter(
            comment_id__in=relevant_comment_ids)
        comments = Comment.objects.filter(id__in=relevant_comment_ids)
        thread_users = get_user_model().objects.exclude(
            pk=self.request.user.pk).exclude(pk__in=subscribers.values_list(
                'user_id', flat=True)).prefetch_related(
                    Prefetch('comment_replies', queryset=replies),
                    Prefetch('comments', queryset=comments)).exclude(
                        Q(comment_replies__isnull=True)
                        & Q(comments__isnull=True))

        # Skip if no recipients
        if not (global_recipient_users or thread_users):
            return
        thread_users = [
            (user,
             set(
                 list(user.comment_replies.values_list('comment_id',
                                                       flat=True)) +
                 list(user.comments.values_list('pk', flat=True))))
            for user in thread_users
        ]
        mailed_users = set()

        for current_user, current_threads in thread_users:
            # We are trying to avoid calling send_notification for each user for performance reasons
            # so group the users receiving the same thread notifications together here
            if current_user in mailed_users:
                continue
            users = [current_user]
            mailed_users.add(current_user)
            for user, threads in thread_users:
                if user not in mailed_users and threads == current_threads:
                    users.append(user)
                    mailed_users.add(user)
            send_notification(
                users, 'updated_comments', {
                    'page':
                    self.page,
                    'editor':
                    self.request.user,
                    'new_comments': [
                        comment
                        for comment in new_comments if comment.pk in threads
                    ],
                    'resolved_comments': [
                        comment for comment in resolved_comments
                        if comment.pk in threads
                    ],
                    'deleted_comments': [],
                    'replied_comments': [
                        comment for comment in replied_comments
                        if comment['comment'].pk in threads
                    ]
                })

        return send_notification(
            global_recipient_users, 'updated_comments', {
                'page': self.page,
                'editor': self.request.user,
                'new_comments': new_comments,
                'resolved_comments': resolved_comments,
                'deleted_comments': deleted_comments,
                'replied_comments': replied_comments
            })
Example #6
0
def edit(request, page_id):
    real_page_record = get_object_or_404(Page, id=page_id)
    latest_revision = real_page_record.get_latest_revision()
    page = real_page_record.get_latest_revision_as_page()
    parent = page.get_parent()

    content_type = ContentType.objects.get_for_model(page)
    page_class = content_type.model_class()

    page_perms = page.permissions_for_user(request.user)
    if not page_perms.can_edit():
        raise PermissionDenied

    for fn in hooks.get_hooks('before_edit_page'):
        result = fn(request, page)
        if hasattr(result, 'status_code'):
            return result

    edit_handler = page_class.get_edit_handler()
    edit_handler = edit_handler.bind_to(instance=page, request=request)
    form_class = edit_handler.get_form_class()

    next_url = get_valid_next_url_from_request(request)

    errors_debug = None

    if request.method == 'POST':
        form = form_class(request.POST, request.FILES, instance=page,
                          parent_page=parent)

        if form.is_valid() and not page.locked:
            page = form.save(commit=False)

            is_publishing = bool(request.POST.get('action-publish')) and page_perms.can_publish()
            is_submitting = bool(request.POST.get('action-submit'))
            is_reverting = bool(request.POST.get('revision'))

            # If a revision ID was passed in the form, get that revision so its
            # date can be referenced in notification messages
            if is_reverting:
                previous_revision = get_object_or_404(page.revisions, id=request.POST.get('revision'))

            # Save revision
            revision = page.save_revision(
                user=request.user,
                submitted_for_moderation=is_submitting,
            )
            # store submitted go_live_at for messaging below
            go_live_at = page.go_live_at

            # Publish
            if is_publishing:
                revision.publish()
                # Need to reload the page because the URL may have changed, and we
                # need the up-to-date URL for the "View Live" button.
                page = page.specific_class.objects.get(pk=page.pk)

            # Notifications
            if is_publishing:
                if go_live_at and go_live_at > timezone.now():
                    # Page has been scheduled for publishing in the future

                    if is_reverting:
                        message = _(
                            "Revision from {0} of page '{1}' has been scheduled for publishing."
                        ).format(
                            previous_revision.created_at.strftime("%d %b %Y %H:%M"),
                            page.get_admin_display_title()
                        )
                    else:
                        if page.live:
                            message = _(
                                "Page '{0}' is live and this revision has been scheduled for publishing."
                            ).format(
                                page.get_admin_display_title()
                            )
                        else:
                            message = _(
                                "Page '{0}' has been scheduled for publishing."
                            ).format(
                                page.get_admin_display_title()
                            )

                    messages.success(request, message, buttons=[
                        messages.button(
                            reverse('wagtailadmin_pages:edit', args=(page.id,)),
                            _('Edit')
                        )
                    ])

                else:
                    # Page is being published now

                    if is_reverting:
                        message = _(
                            "Revision from {0} of page '{1}' has been published."
                        ).format(
                            previous_revision.created_at.strftime("%d %b %Y %H:%M"),
                            page.get_admin_display_title()
                        )
                    else:
                        message = _(
                            "Page '{0}' has been published."
                        ).format(
                            page.get_admin_display_title()
                        )

                    buttons = []
                    if page.url is not None:
                        buttons.append(messages.button(page.url, _('View live'), new_window=True))
                    buttons.append(messages.button(reverse('wagtailadmin_pages:edit', args=(page_id,)), _('Edit')))
                    messages.success(request, message, buttons=buttons)

            elif is_submitting:

                message = _(
                    "Page '{0}' has been submitted for moderation."
                ).format(
                    page.get_admin_display_title()
                )

                messages.success(request, message, buttons=[
                    messages.button(
                        reverse('wagtailadmin_pages:view_draft', args=(page_id,)),
                        _('View draft'),
                        new_window=True
                    ),
                    messages.button(
                        reverse('wagtailadmin_pages:edit', args=(page_id,)),
                        _('Edit')
                    )
                ])

                if not send_notification(page.get_latest_revision().id, 'submitted', request.user.pk):
                    messages.error(request, _("Failed to send notifications to moderators"))

            else:  # Saving

                if is_reverting:
                    message = _(
                        "Page '{0}' has been replaced with revision from {1}."
                    ).format(
                        page.get_admin_display_title(),
                        previous_revision.created_at.strftime("%d %b %Y %H:%M")
                    )
                else:
                    message = _(
                        "Page '{0}' has been updated."
                    ).format(
                        page.get_admin_display_title()
                    )

                messages.success(request, message)

            for fn in hooks.get_hooks('after_edit_page'):
                result = fn(request, page)
                if hasattr(result, 'status_code'):
                    return result

            if is_publishing or is_submitting:
                # we're done here - redirect back to the explorer
                if next_url:
                    # redirect back to 'next' url if present
                    return redirect(next_url)
                # redirect back to the explorer
                return redirect('wagtailadmin_explore', page.get_parent().id)
            else:
                # Just saving - remain on edit page for further edits
                target_url = reverse('wagtailadmin_pages:edit', args=[page.id])
                if next_url:
                    # Ensure the 'next' url is passed through again if present
                    target_url += '?next=%s' % urlquote(next_url)
                return redirect(target_url)
        else:
            if page.locked:
                messages.error(request, _("The page could not be saved as it is locked"))
            else:
                messages.validation_error(
                    request, _("The page could not be saved due to validation errors"), form
                )
            errors_debug = (
                repr(form.errors)
                + repr([
                    (name, formset.errors)
                    for (name, formset) in form.formsets.items()
                    if formset.errors
                ])
            )
            has_unsaved_changes = True
    else:
        form = form_class(instance=page, parent_page=parent)
        has_unsaved_changes = False

    edit_handler = edit_handler.bind_to(form=form)

    # Check for revisions still undergoing moderation and warn
    if latest_revision and latest_revision.submitted_for_moderation:
        buttons = []

        if page.live:
            buttons.append(messages.button(
                reverse('wagtailadmin_pages:revisions_compare', args=(page.id, 'live', latest_revision.id)),
                _('Compare with live version')
            ))

        messages.warning(request, _("This page is currently awaiting moderation"), buttons=buttons)

    if page.live and page.has_unpublished_changes:
        # Page status needs to present the version of the page containing the correct live URL
        page_for_status = real_page_record.specific
    else:
        page_for_status = page

    return render(request, 'wagtailadmin/pages/edit.html', {
        'page': page,
        'page_for_status': page_for_status,
        'content_type': content_type,
        'edit_handler': edit_handler,
        'errors_debug': errors_debug,
        'action_menu': PageActionMenu(request, view='edit', page=page),
        'preview_modes': page.preview_modes,
        'form': form,
        'next': next_url,
        'has_unsaved_changes': has_unsaved_changes,
    })
Example #7
0
def create(request, content_type_app_name, content_type_model_name, parent_page_id):
    parent_page = get_object_or_404(Page, id=parent_page_id).specific
    parent_page_perms = parent_page.permissions_for_user(request.user)
    if not parent_page_perms.can_add_subpage():
        raise PermissionDenied

    try:
        content_type = ContentType.objects.get_by_natural_key(content_type_app_name, content_type_model_name)
    except ContentType.DoesNotExist:
        raise Http404

    # Get class
    page_class = content_type.model_class()

    # Make sure the class is a descendant of Page
    if not issubclass(page_class, Page):
        raise Http404

    # page must be in the list of allowed subpage types for this parent ID
    if page_class not in parent_page.creatable_subpage_models():
        raise PermissionDenied

    if not page_class.can_create_at(parent_page):
        raise PermissionDenied

    for fn in hooks.get_hooks('before_create_page'):
        result = fn(request, parent_page, page_class)
        if hasattr(result, 'status_code'):
            return result

    page = page_class(owner=request.user)
    edit_handler = page_class.get_edit_handler()
    edit_handler = edit_handler.bind_to(request=request, instance=page)
    form_class = edit_handler.get_form_class()

    next_url = get_valid_next_url_from_request(request)

    if request.method == 'POST':
        form = form_class(request.POST, request.FILES, instance=page,
                          parent_page=parent_page)

        if form.is_valid():
            page = form.save(commit=False)

            is_publishing = bool(request.POST.get('action-publish')) and parent_page_perms.can_publish_subpage()
            is_submitting = bool(request.POST.get('action-submit'))

            if not is_publishing:
                page.live = False

            # Save page
            parent_page.add_child(instance=page)

            # Save revision
            revision = page.save_revision(
                user=request.user,
                submitted_for_moderation=is_submitting,
            )

            # Publish
            if is_publishing:
                revision.publish()

            # Notifications
            if is_publishing:
                if page.go_live_at and page.go_live_at > timezone.now():
                    messages.success(request, _("Page '{0}' created and scheduled for publishing.").format(page.get_admin_display_title()), buttons=[
                        messages.button(reverse('wagtailadmin_pages:edit', args=(page.id,)), _('Edit'))
                    ])
                else:
                    buttons = []
                    if page.url is not None:
                        buttons.append(messages.button(page.url, _('View live'), new_window=True))
                    buttons.append(messages.button(reverse('wagtailadmin_pages:edit', args=(page.id,)), _('Edit')))
                    messages.success(request, _("Page '{0}' created and published.").format(page.get_admin_display_title()), buttons=buttons)
            elif is_submitting:
                messages.success(
                    request,
                    _("Page '{0}' created and submitted for moderation.").format(page.get_admin_display_title()),
                    buttons=[
                        messages.button(
                            reverse('wagtailadmin_pages:view_draft', args=(page.id,)),
                            _('View draft'),
                            new_window=True
                        ),
                        messages.button(
                            reverse('wagtailadmin_pages:edit', args=(page.id,)),
                            _('Edit')
                        )
                    ]
                )
                if not send_notification(page.get_latest_revision().id, 'submitted', request.user.pk):
                    messages.error(request, _("Failed to send notifications to moderators"))
            else:
                messages.success(request, _("Page '{0}' created.").format(page.get_admin_display_title()))

            for fn in hooks.get_hooks('after_create_page'):
                result = fn(request, page)
                if hasattr(result, 'status_code'):
                    return result

            if is_publishing or is_submitting:
                # we're done here
                if next_url:
                    # redirect back to 'next' url if present
                    return redirect(next_url)
                # redirect back to the explorer
                return redirect('wagtailadmin_explore', page.get_parent().id)
            else:
                # Just saving - remain on edit page for further edits
                target_url = reverse('wagtailadmin_pages:edit', args=[page.id])
                if next_url:
                    # Ensure the 'next' url is passed through again if present
                    target_url += '?next=%s' % urlquote(next_url)
                return redirect(target_url)
        else:
            messages.validation_error(
                request, _("The page could not be created due to validation errors"), form
            )
            has_unsaved_changes = True
    else:
        signals.init_new_page.send(sender=create, page=page, parent=parent_page)
        form = form_class(instance=page, parent_page=parent_page)
        has_unsaved_changes = False

    edit_handler = edit_handler.bind_to(form=form)

    return render(request, 'wagtailadmin/pages/create.html', {
        'content_type': content_type,
        'page_class': page_class,
        'parent_page': parent_page,
        'edit_handler': edit_handler,
        'action_menu': PageActionMenu(request, view='create', parent_page=parent_page),
        'preview_modes': page.preview_modes,
        'form': form,
        'next': next_url,
        'has_unsaved_changes': has_unsaved_changes,
    })
Example #8
0
    def send_commenting_notifications(self, changes):
        """
        Sends notifications about any changes to comments to anyone who is subscribed.
        """
        relevant_comment_ids = []
        relevant_comment_ids.extend(
            comment.pk for comment in changes["resolved_comments"])
        relevant_comment_ids.extend(
            comment.pk for comment, replies in changes["new_replies"])

        # Skip if no changes were made
        # Note: We don't email about edited comments so ignore those here
        if (not changes["new_comments"] and not changes["deleted_comments"]
                and not changes["resolved_comments"]
                and not changes["new_replies"]):
            return

        # Get global page comment subscribers
        subscribers = PageSubscription.objects.filter(
            page=self.page, comment_notifications=True).select_related("user")
        global_recipient_users = [
            subscriber.user for subscriber in subscribers
            if subscriber.user != self.request.user
        ]

        # Get subscribers to individual threads
        replies = CommentReply.objects.filter(
            comment_id__in=relevant_comment_ids)
        comments = Comment.objects.filter(id__in=relevant_comment_ids)
        thread_users = (get_user_model().objects.exclude(
            pk=self.request.user.pk
        ).exclude(pk__in=subscribers.values_list("user_id", flat=True)).filter(
            Q(comment_replies__comment_id__in=relevant_comment_ids)
            | Q(**{
                ("%s__pk__in" % COMMENTS_RELATION_NAME): relevant_comment_ids
            })).prefetch_related(
                Prefetch("comment_replies", queryset=replies),
                Prefetch(COMMENTS_RELATION_NAME, queryset=comments),
            ))

        # Skip if no recipients
        if not (global_recipient_users or thread_users):
            return
        thread_users = [(
            user,
            set(
                list(user.comment_replies.values_list("comment_id", flat=True))
                + list(
                    getattr(user, COMMENTS_RELATION_NAME).values_list(
                        "pk", flat=True))),
        ) for user in thread_users]
        mailed_users = set()

        for current_user, current_threads in thread_users:
            # We are trying to avoid calling send_notification for each user for performance reasons
            # so group the users receiving the same thread notifications together here
            if current_user in mailed_users:
                continue
            users = [current_user]
            mailed_users.add(current_user)
            for user, threads in thread_users:
                if user not in mailed_users and threads == current_threads:
                    users.append(user)
                    mailed_users.add(user)
            send_notification(
                users,
                "updated_comments",
                {
                    "page":
                    self.page,
                    "editor":
                    self.request.user,
                    "new_comments": [
                        comment for comment in changes["new_comments"]
                        if comment.pk in threads
                    ],
                    "resolved_comments": [
                        comment for comment in changes["resolved_comments"]
                        if comment.pk in threads
                    ],
                    "deleted_comments": [],
                    "replied_comments": [{
                        "comment": comment,
                        "replies": replies,
                    } for comment, replies in changes["new_replies"]
                                         if comment.pk in threads],
                },
            )

        return send_notification(
            global_recipient_users,
            "updated_comments",
            {
                "page":
                self.page,
                "editor":
                self.request.user,
                "new_comments":
                changes["new_comments"],
                "resolved_comments":
                changes["resolved_comments"],
                "deleted_comments":
                changes["deleted_comments"],
                "replied_comments": [{
                    "comment": comment,
                    "replies": replies,
                } for comment, replies in changes["new_replies"]],
            },
        )