diff --git a/wagtail/admin/edit_handlers.py b/wagtail/admin/edit_handlers.py index 555ea8af7a..fe65032a1a 100644 --- a/wagtail/admin/edit_handlers.py +++ b/wagtail/admin/edit_handlers.py @@ -13,6 +13,8 @@ from django.utils.translation import gettext_lazy from taggit.managers import TaggableManager from wagtail.admin import compare, widgets +from wagtail.admin.forms.comments import CommentForm, CommentReplyForm +from wagtail.admin.templatetags.wagtailadmin_tags import user_display_name from wagtail.core.fields import RichTextField from wagtail.core.models import Page from wagtail.core.utils import camelcase_to_underscore, resolve_model_string @@ -59,6 +61,7 @@ def get_form_for_model( } metaclass = type(form_class) + return metaclass(class_name, (form_class,), form_class_attrs) @@ -789,8 +792,65 @@ class PrivacyModalPanel(EditHandler): ) +class CommentPanel(EditHandler): + def required_formsets(self): + # add the comments formset + # we need to pass in the current user for validation on the formset + # this could alternatively be done on the page form itself if we added the + # comments formset there, but we typically only add fields via edit handlers + current_user = getattr(self.request, 'user', None) + + class CommentReplyFormWithRequest(CommentReplyForm): + user = current_user + + class CommentFormWithRequest(CommentForm): + user = current_user + + class Meta: + formsets = { + 'replies': { + 'form': CommentReplyFormWithRequest + } + } + + return { + 'comments': { + 'form': CommentFormWithRequest, + 'fields': ['text', 'contentpath'], + } + } + + template = "wagtailadmin/edit_handlers/comments/comment_panel.html" + js_template = "wagtailadmin/edit_handlers/comments/comment_panel.js" + declarations_template = "wagtailadmin/edit_handlers/comments/comment_declarations.html" + + def html_declarations(self): + return render_to_string(self.declarations_template) + + def render(self): + user = getattr(self.request, 'user', None) + comments_author = { + 'id': user.pk, + 'name': user_display_name(user) + } if user else None + + panel = render_to_string(self.template, { + 'self': self, + 'comments_author': comments_author + }) + js = self.render_js_init() + return widget_with_script(panel, js) + + def render_js_init(self): + return mark_safe(render_to_string(self.js_template, { + 'self': self, + })) + + # Now that we've defined EditHandlers, we can set up wagtailcore.Page to have some. + Page.content_panels = [ + CommentPanel(), FieldPanel('title', classname="full title"), ] diff --git a/wagtail/admin/forms/comments.py b/wagtail/admin/forms/comments.py new file mode 100644 index 0000000000..330aaf5268 --- /dev/null +++ b/wagtail/admin/forms/comments.py @@ -0,0 +1,52 @@ +from django.forms import ValidationError +from django.forms.formsets import DELETION_FIELD_NAME +from django.utils.translation import gettext as _ + +from .models import WagtailAdminModelForm + + +class CommentReplyForm(WagtailAdminModelForm): + user = None + + class Meta: + fields = ('text',) + + def clean(self): + cleaned_data = super().clean() + user = self.user + if self.instance.pk and self.instance.user != user: + # trying to edit someone else's comment reply + if any(field for field in self.changed_data): + # includes DELETION_FIELD_NAME, as users cannot delete each other's individual comment replies + # if deleting a whole thread, this should be done by deleting the parent Comment instead + self.add_error(None, error=ValidationError(_("You cannot edit another user's comment"))) + return cleaned_data + + def save(self, *args, **kwargs): + if not self.instance.pk: + self.instance.user = self.user + return super().save(*args, **kwargs) + + +class CommentForm(WagtailAdminModelForm): + """ + This is designed to be subclassed and have the user overidden to enable user-based validation within the edit handler system + """ + user = None + + def clean(self): + cleaned_data = super().clean() + user = self.user + + if self.instance.pk and self.instance.user != user: + # trying to edit someone else's comment + if any(field for field in self.changed_data if field != DELETION_FIELD_NAME): + # users can delete each other's base comments, as this is just the "resolve" action + self.add_error(None, error=ValidationError(_("You cannot edit another user's comment"))) + + return cleaned_data + + def save(self, *args, **kwargs): + if not self.instance.pk: + self.instance.user = self.user + return super().save(*args, **kwargs) diff --git a/wagtail/admin/forms/pages.py b/wagtail/admin/forms/pages.py index 3cd12a291d..a4f834b180 100644 --- a/wagtail/admin/forms/pages.py +++ b/wagtail/admin/forms/pages.py @@ -114,7 +114,6 @@ class WagtailAdminPageForm(WagtailAdminModelForm): self.parent_page = parent_page def clean(self): - cleaned_data = super().clean() if 'slug' in self.cleaned_data: if not Page._slug_is_available( diff --git a/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_declarations.html b/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_declarations.html new file mode 100644 index 0000000000..7f667e98df --- /dev/null +++ b/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_declarations.html @@ -0,0 +1,2 @@ +{% load wagtailadmin_tags %} + \ No newline at end of file diff --git a/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_panel.html b/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_panel.html new file mode 100644 index 0000000000..6b157e11d5 --- /dev/null +++ b/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_panel.html @@ -0,0 +1,6 @@ +{% load wagtailadmin_tags %} +
+
+
+{{ comments_author|json_script:"comments-author" }} + \ No newline at end of file diff --git a/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_panel.js b/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_panel.js new file mode 100644 index 0000000000..25237ae9b6 --- /dev/null +++ b/wagtail/admin/templates/wagtailadmin/edit_handlers/comments/comment_panel.js @@ -0,0 +1 @@ +window.comments.initComments([]); diff --git a/wagtail/admin/templates/wagtailadmin/pages/_editor_js.html b/wagtail/admin/templates/wagtailadmin/pages/_editor_js.html index ecfc7481cf..7a65f5cfac 100644 --- a/wagtail/admin/templates/wagtailadmin/pages/_editor_js.html +++ b/wagtail/admin/templates/wagtailadmin/pages/_editor_js.html @@ -27,6 +27,4 @@ - - {% hook_output 'insert_editor_js' %} diff --git a/wagtail/admin/templates/wagtailadmin/pages/edit.html b/wagtail/admin/templates/wagtailadmin/pages/edit.html index 5817a8432a..6df49ca1c0 100644 --- a/wagtail/admin/templates/wagtailadmin/pages/edit.html +++ b/wagtail/admin/templates/wagtailadmin/pages/edit.html @@ -116,8 +116,6 @@ Additional HTML code that edit handlers define through 'html_declarations'. (Technically this isn't JavaScript, but it will generally be data that exists for JavaScript to work with...) {% endcomment %} {{ edit_handler.html_declarations }} - {{ comments_author|json_script:"comments-author" }} - - {% endblock %} diff --git a/wagtail/admin/views/pages/edit.py b/wagtail/admin/views/pages/edit.py index 4c924b84b4..848414f7a6 100644 --- a/wagtail/admin/views/pages/edit.py +++ b/wagtail/admin/views/pages/edit.py @@ -14,7 +14,6 @@ from django.views.generic.base import ContextMixin, TemplateResponseMixin, View from wagtail.admin import messages from wagtail.admin.action_menu import PageActionMenu -from wagtail.admin.templatetags.wagtailadmin_tags import user_display_name from wagtail.admin.views.generic import HookResponseMixin from wagtail.admin.views.pages.utils import get_valid_next_url_from_request from wagtail.core.exceptions import PageClassNotFoundError @@ -533,10 +532,6 @@ class EditView(TemplateResponseMixin, ContextMixin, HookResponseMixin, View): 'publishing_will_cancel_workflow': self.workflow_tasks and getattr(settings, 'WAGTAIL_WORKFLOW_CANCEL_ON_PUBLISH', True), 'locale': None, 'translations': [], - 'comments_author': { - 'id': self.request.user.pk, # TODO: move into the comments widget/edit handler when created - 'name': user_display_name(self.request.user) - }, }) if getattr(settings, 'WAGTAIL_I18N_ENABLED', False):