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')
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, )
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']] })
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 })
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, })
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, })
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"]], }, )