1from django import forms 2from django.conf import settings 3from django.utils import timezone 4from django.utils.translation import gettext as _ 5from django.utils.translation import ngettext 6 7from wagtail.admin import widgets 8from wagtail.core.models import Page, PageViewRestriction 9 10from .models import WagtailAdminModelForm 11from .view_restrictions import BaseViewRestrictionForm 12 13 14class CopyForm(forms.Form): 15 def __init__(self, *args, **kwargs): 16 # CopyPage must be passed a 'page' kwarg indicating the page to be copied 17 self.page = kwargs.pop('page') 18 self.user = kwargs.pop('user', None) 19 can_publish = kwargs.pop('can_publish') 20 super().__init__(*args, **kwargs) 21 self.fields['new_title'] = forms.CharField(initial=self.page.title, label=_("New title")) 22 allow_unicode = getattr(settings, 'WAGTAIL_ALLOW_UNICODE_SLUGS', True) 23 self.fields['new_slug'] = forms.SlugField(initial=self.page.slug, label=_("New slug"), allow_unicode=allow_unicode) 24 self.fields['new_parent_page'] = forms.ModelChoiceField( 25 initial=self.page.get_parent(), 26 queryset=Page.objects.all(), 27 widget=widgets.AdminPageChooser(can_choose_root=True, user_perms='copy_to'), 28 label=_("New parent page"), 29 help_text=_("This copy will be a child of this given parent page.") 30 ) 31 pages_to_copy = self.page.get_descendants(inclusive=True) 32 subpage_count = pages_to_copy.count() - 1 33 if subpage_count > 0: 34 self.fields['copy_subpages'] = forms.BooleanField( 35 required=False, initial=True, label=_("Copy subpages"), 36 help_text=ngettext( 37 "This will copy %(count)s subpage.", 38 "This will copy %(count)s subpages.", 39 subpage_count) % {'count': subpage_count}) 40 41 if can_publish: 42 pages_to_publish_count = pages_to_copy.live().count() 43 if pages_to_publish_count > 0: 44 # In the specific case that there are no subpages, customise the field label and help text 45 if subpage_count == 0: 46 label = _("Publish copied page") 47 help_text = _("This page is live. Would you like to publish its copy as well?") 48 else: 49 label = _("Publish copies") 50 help_text = ngettext( 51 "%(count)s of the pages being copied is live. Would you like to publish its copy?", 52 "%(count)s of the pages being copied are live. Would you like to publish their copies?", 53 pages_to_publish_count) % {'count': pages_to_publish_count} 54 55 self.fields['publish_copies'] = forms.BooleanField( 56 required=False, initial=True, label=label, help_text=help_text 57 ) 58 59 # Note that only users who can publish in the new parent page can create an alias. 60 # This is because alias pages must always match their original page's state. 61 self.fields['alias'] = forms.BooleanField( 62 required=False, initial=False, label=_("Alias"), 63 help_text=_("Keep the new pages updated with future changes") 64 ) 65 66 def clean(self): 67 cleaned_data = super().clean() 68 69 # Make sure the slug isn't already in use 70 slug = cleaned_data.get('new_slug') 71 72 # New parent page given in form or parent of source, if parent_page is empty 73 parent_page = cleaned_data.get('new_parent_page') or self.page.get_parent() 74 75 # check if user is allowed to create a page at given location. 76 if not parent_page.permissions_for_user(self.user).can_add_subpage(): 77 self._errors['new_parent_page'] = self.error_class([ 78 _("You do not have permission to copy to page \"%(page_title)s\"") % {'page_title': parent_page.specific_deferred.get_admin_display_title()} 79 ]) 80 81 # Count the pages with the same slug within the context of our copy's parent page 82 if slug and parent_page.get_children().filter(slug=slug).count(): 83 self._errors['new_slug'] = self.error_class( 84 [_("This slug is already in use within the context of its parent page \"%s\"") % parent_page] 85 ) 86 # The slug is no longer valid, hence remove it from cleaned_data 87 del cleaned_data['new_slug'] 88 89 # Don't allow recursive copies into self 90 if cleaned_data.get('copy_subpages') and (self.page == parent_page or parent_page.is_descendant_of(self.page)): 91 self._errors['new_parent_page'] = self.error_class( 92 [_("You cannot copy a page into itself when copying subpages")] 93 ) 94 95 return cleaned_data 96 97 98class PageViewRestrictionForm(BaseViewRestrictionForm): 99 100 class Meta: 101 model = PageViewRestriction 102 fields = ('restriction_type', 'password', 'groups') 103 104 105class WagtailAdminPageForm(WagtailAdminModelForm): 106 comment_notifications = forms.BooleanField(widget=forms.CheckboxInput(), required=False) 107 108 # Could be set to False by a subclass constructed by TabbedInterface 109 show_comments_toggle = True 110 111 class Meta: 112 # (dealing with Treebeard's tree-related fields that really should have 113 # been editable=False) 114 exclude = ['content_type', 'path', 'depth', 'numchild'] 115 116 def __init__(self, data=None, files=None, parent_page=None, subscription=None, *args, **kwargs): 117 self.subscription = subscription 118 119 initial = kwargs.pop('initial', {}) 120 if self.subscription: 121 initial['comment_notifications'] = subscription.comment_notifications 122 123 super().__init__(data, files, *args, initial=initial, **kwargs) 124 125 self.parent_page = parent_page 126 127 if not self.show_comments_toggle: 128 del self.fields['comment_notifications'] 129 130 def save(self, commit=True): 131 # Save comment notifications updates to PageSubscription 132 if self.show_comments_toggle and self.subscription: 133 self.subscription.comment_notifications = self.cleaned_data['comment_notifications'] 134 if commit: 135 self.subscription.save() 136 137 return super().save(commit=commit) 138 139 def is_valid(self): 140 comments = self.formsets.get('comments') 141 # Remove the comments formset if the management form is invalid 142 if comments: 143 try: 144 # As of Django 3.2, this no longer raises an error 145 has_form = comments.management_form.is_valid() 146 except forms.ValidationError: 147 has_form = False 148 if not has_form: 149 del self.formsets['comments'] 150 return super().is_valid() 151 152 def clean(self): 153 cleaned_data = super().clean() 154 if 'slug' in self.cleaned_data: 155 if not Page._slug_is_available( 156 cleaned_data['slug'], self.parent_page, self.instance 157 ): 158 self.add_error('slug', forms.ValidationError(_("This slug is already in use"))) 159 160 # Check scheduled publishing fields 161 go_live_at = cleaned_data.get('go_live_at') 162 expire_at = cleaned_data.get('expire_at') 163 164 # Go live must be before expire 165 if go_live_at and expire_at: 166 if go_live_at > expire_at: 167 msg = _('Go live date/time must be before expiry date/time') 168 self.add_error('go_live_at', forms.ValidationError(msg)) 169 self.add_error('expire_at', forms.ValidationError(msg)) 170 171 # Expire at must be in the future 172 if expire_at and expire_at < timezone.now(): 173 self.add_error('expire_at', forms.ValidationError(_('Expiry date/time must be in the future'))) 174 175 # Don't allow an existing first_published_at to be unset by clearing the field 176 if 'first_published_at' in cleaned_data and not cleaned_data['first_published_at']: 177 del cleaned_data['first_published_at'] 178 179 return cleaned_data 180