Esempio n. 1
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():
            user = form.save()
            messages.success(request, _("Your details have been updated. You've been logged out for security reasons, "
                                        "please login to continue."))
            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 render(request, 'wagtailusers/users/edit.html', {
        'user': user,
        'form': form,
        'can_delete': can_delete,
    })
Esempio n. 2
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():
            user = form.save()
            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 render(request, 'wagtailusers/users/create.html', {
        'form': form,
    })
Esempio n. 3
0
def delete(request, page_id):
    page = get_object_or_404(Page, id=page_id).specific
    if not page.permissions_for_user(request.user).can_delete():
        raise PermissionDenied

    with transaction.atomic():
        for fn in hooks.get_hooks('before_delete_page'):
            result = fn(request, page)
            if hasattr(result, 'status_code'):
                return result

        next_url = get_valid_next_url_from_request(request)

        if request.method == 'POST':
            parent_id = page.get_parent().id
            page.delete()

            messages.success(request, _("Page '{0}' deleted.").format(page.get_admin_display_title()))

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

            if next_url:
                return redirect(next_url)
            return redirect('wagtailadmin_explore', parent_id)

    return render(request, 'wagtailadmin/pages/confirm_delete.html', {
        'page': page,
        'descendant_count': page.get_descendant_count(),
        'next': next_url,
    })
Esempio n. 4
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():
            user = form.save()
            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 render(request, 'wagtailusers/users/edit.html', {
        'user': user,
        'form': form,
        'can_delete': can_delete,
    })
Esempio n. 5
0
    def test_before_hook(self):
        def before_hook():
            pass

        with self.register_hook('test_hook_name', before_hook, order=-1):
            hook_fns = hooks.get_hooks('test_hook_name')
            self.assertEqual(hook_fns, [before_hook, test_hook])
Esempio n. 6
0
    def test_after_hook(self):
        def after_hook():
            pass

        with self.register_hook('test_hook_name', after_hook, order=1):
            hook_fns = hooks.get_hooks('test_hook_name')
            self.assertEqual(hook_fns, [test_hook, after_hook])
Esempio n. 7
0
def home(request):

    panels = [
        SiteSummaryPanel(request),
        UpgradeNotificationPanel(request),
        PagesForModerationPanel(request),
        RecentEditsPanel(request),
    ]

    for fn in hooks.get_hooks('construct_homepage_panels'):
        fn(request, panels)

    root_page = get_explorable_root_page(request.user)
    if root_page:
        root_site = root_page.get_site()
    else:
        root_site = None

    real_site_name = None
    if root_site:
        real_site_name = root_site.site_name if root_site.site_name else root_site.hostname

    return render(request, "wagtailadmin/home.html", {
        'root_page': root_page,
        'root_site': root_site,
        'site_name': real_site_name if real_site_name else settings.WAGTAIL_SITE_NAME,
        'panels': sorted(panels, key=lambda p: p.order),
        'user': request.user
    })
Esempio n. 8
0
def chooser(request):
    Image = get_image_model()

    if permission_policy.user_has_permission(request.user, 'add'):
        ImageForm = get_image_form(Image)
        uploadform = ImageForm(user=request.user)
    else:
        uploadform = None

    images = Image.objects.order_by('-created_at')

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_image_chooser_queryset'):
        images = hook(images, request)

    if (
        'q' in request.GET or 'p' in request.GET or 'tag' in request.GET or
        'collection_id' in request.GET
    ):
        # this request is triggered from search, pagination or 'popular tags';
        # we will just render the results.html fragment
        collection_id = request.GET.get('collection_id')
        if collection_id:
            images = images.filter(collection=collection_id)

        searchform = SearchForm(request.GET)
        if searchform.is_valid():
            q = searchform.cleaned_data['q']

            images = images.search(q)
            is_searching = True
        else:
            is_searching = False
            q = None

            tag_name = request.GET.get('tag')
            if tag_name:
                images = images.filter(tags__name=tag_name)

        # Pagination
        paginator, images = paginate(request, images, per_page=12)

        return render(request, "wagtailimages/chooser/results.html", {
            'images': images,
            'is_searching': is_searching,
            'query_string': q,
            'will_select_format': request.GET.get('select_format')
        })
    else:
        paginator, images = paginate(request, images, per_page=12)

        context = get_chooser_context(request)
        context.update({
            'images': images,
            'uploadform': uploadform,
        })
        return render_modal_workflow(
            request, 'wagtailimages/chooser/chooser.html', None, context,
            json_data=get_chooser_js_data()
        )
Esempio n. 9
0
def chooser_upload(request):
    Image = get_image_model()
    ImageForm = get_image_form(Image)

    if request.method == 'POST':
        image = Image(uploaded_by_user=request.user)
        form = ImageForm(
            request.POST, request.FILES, instance=image, user=request.user, prefix='image-chooser-upload'
        )

        if form.is_valid():
            # Set image file size
            image.file_size = image.file.size

            # Set image file hash
            image.file.seek(0)
            image._set_file_hash(image.file.read())
            image.file.seek(0)

            form.save()

            # Reindex the image to make sure all tags are indexed
            search_index.insert_or_update_object(image)

            if request.GET.get('select_format'):
                form = ImageInsertionForm(
                    initial={'alt_text': image.default_alt_text}, prefix='image-chooser-insertion'
                )
                return render_modal_workflow(
                    request, 'wagtailimages/chooser/select_format.html', None,
                    {'image': image, 'form': form}, json_data={'step': 'select_format'}
                )
            else:
                # not specifying a format; return the image details now
                return render_modal_workflow(
                    request, None, None,
                    None, json_data={'step': 'image_chosen', 'result': get_image_result_data(image)}
                )
    else:
        form = ImageForm(user=request.user, prefix='image-chooser-upload')

    images = Image.objects.order_by('-created_at')

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_image_chooser_queryset'):
        images = hook(images, request)

    paginator = Paginator(images, per_page=12)
    images = paginator.get_page(request.GET.get('p'))

    context = get_chooser_context(request)
    context.update({
        'images': images,
        'uploadform': form,
    })
    return render_modal_workflow(
        request, 'wagtailimages/chooser/chooser.html', None, context,
        json_data=get_chooser_js_data()
    )
Esempio n. 10
0
def get_permission_panel_classes():
    global _permission_panel_classes
    if _permission_panel_classes is None:
        _permission_panel_classes = [GroupPagePermissionFormSet]
        for fn in hooks.get_hooks('register_group_permission_panel'):
            _permission_panel_classes.append(fn())

    return _permission_panel_classes
Esempio n. 11
0
 def test_registered_permission(self):
     permission = Permission.objects.get_by_natural_key(
         app_label='tests', model='testsetting', codename='change_testsetting')
     for fn in hooks.get_hooks('register_permissions'):
         if permission in fn():
             break
     else:
         self.fail('Change permission for tests.TestSetting not registered')
Esempio n. 12
0
def chooser(request):
    Document = get_document_model()

    if permission_policy.user_has_permission(request.user, 'add'):
        DocumentForm = get_document_form(Document)
        uploadform = DocumentForm(user=request.user)
    else:
        uploadform = None

    documents = Document.objects.all()

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_document_chooser_queryset'):
        documents = hook(documents, request)

    q = None
    if 'q' in request.GET or 'p' in request.GET or 'collection_id' in request.GET:

        collection_id = request.GET.get('collection_id')
        if collection_id:
            documents = documents.filter(collection=collection_id)

        searchform = SearchForm(request.GET)
        if searchform.is_valid():
            q = searchform.cleaned_data['q']

            documents = documents.search(q)
            is_searching = True
        else:
            documents = documents.order_by('-created_at')
            is_searching = False

        # Pagination
        paginator, documents = paginate(request, documents, per_page=10)

        return render(request, "wagtaildocs/chooser/results.html", {
            'documents': documents,
            'query_string': q,
            'is_searching': is_searching,
        })
    else:
        searchform = SearchForm()

        collections = Collection.objects.all()
        if len(collections) < 2:
            collections = None

        documents = documents.order_by('-created_at')
        paginator, documents = paginate(request, documents, per_page=10)

        return render_modal_workflow(request, 'wagtaildocs/chooser/chooser.html', 'wagtaildocs/chooser/chooser.js', {
            'documents': documents,
            'uploadform': uploadform,
            'searchform': searchform,
            'collections': collections,
            'is_searching': False,
            'uploadid': uuid.uuid4(),
        })
Esempio n. 13
0
    def _search_for_operations(cls):
        if cls._registered_operations is not None:
            return

        operations = []
        for fn in hooks.get_hooks('register_image_operations'):
            operations.extend(fn())

        cls._registered_operations = dict(operations)
Esempio n. 14
0
def hook_output(hook_name):
    """
    Example: {% hook_output 'insert_editor_css' %}
    Whenever we have a hook whose functions take no parameters and return a string, this tag can be used
    to output the concatenation of all of those return values onto the page.
    Note that the output is not escaped - it is the hook function's responsibility to escape unsafe content.
    """
    snippets = [fn() for fn in hooks.get_hooks(hook_name)]
    return mark_safe(''.join(snippets))
Esempio n. 15
0
def serve(request, document_id, document_filename):
    Document = get_document_model()
    doc = get_object_or_404(Document, id=document_id)

    # We want to ensure that the document filename provided in the URL matches the one associated with the considered
    # document_id. If not we can't be sure that the document the user wants to access is the one corresponding to the
    # <document_id, document_filename> pair.
    if doc.filename != document_filename:
        raise Http404('This document does not match the given filename.')

    for fn in hooks.get_hooks('before_serve_document'):
        result = fn(doc, request)
        if isinstance(result, HttpResponse):
            return result

    # Send document_served signal
    document_served.send(sender=Document, instance=doc, request=request)

    try:
        local_path = doc.file.path
    except NotImplementedError:
        local_path = None

    if local_path:

        # Use wagtail.utils.sendfile to serve the file;
        # this provides support for mimetypes, if-modified-since and django-sendfile backends

        if hasattr(settings, 'SENDFILE_BACKEND'):
            return sendfile(request, local_path, attachment=True, attachment_filename=doc.filename)
        else:
            # Fallback to streaming backend if user hasn't specified SENDFILE_BACKEND
            return sendfile(
                request,
                local_path,
                attachment=True,
                attachment_filename=doc.filename,
                backend=sendfile_streaming_backend.sendfile
            )

    else:

        # We are using a storage backend which does not expose filesystem paths
        # (e.g. storages.backends.s3boto.S3BotoStorage).
        # Fall back on pre-sendfile behaviour of reading the file content and serving it
        # as a StreamingHttpResponse

        wrapper = FileWrapper(doc.file)
        response = StreamingHttpResponse(wrapper, content_type='application/octet-stream')

        response['Content-Disposition'] = 'attachment; filename=%s' % doc.filename

        # FIXME: storage backends are not guaranteed to implement 'size'
        response['Content-Length'] = doc.file.size

        return response
Esempio n. 16
0
    def filter_queryset(self, request, queryset, view):
        if request.GET.get('for_explorer'):
            if not hasattr(queryset, '_filtered_by_child_of'):
                raise BadRequestError("filtering by for_explorer without child_of is not supported")

            parent_page = queryset._filtered_by_child_of
            for hook in hooks.get_hooks('construct_explorer_page_queryset'):
                queryset = hook(parent_page, queryset, request)

        return queryset
Esempio n. 17
0
def account(request):
    items = []

    for fn in hooks.get_hooks('register_account_menu_item'):
        item = fn(request)
        if item:
            items.append(item)

    return render(request, 'wagtailadmin/account/account.html', {
        'items': items,
    })
Esempio n. 18
0
def get_forms_for_user(user):
    """
    Return a queryset of form pages that this user is allowed to access the submissions for
    """
    editable_forms = UserPagePermissionsProxy(user).editable_pages()
    editable_forms = editable_forms.filter(content_type__in=get_form_types())

    # Apply hooks
    for fn in hooks.get_hooks('filter_form_submissions_for_user'):
        editable_forms = fn(user, editable_forms)

    return editable_forms
Esempio n. 19
0
    def get_collection_contents(self):
        collection_contents = [
            hook(self.object)
            for hook in hooks.get_hooks('describe_collection_contents')
        ]

        # filter out any hook responses that report that the collection is empty
        # (by returning None, or a dict with 'count': 0)
        def is_nonempty(item_type):
            return item_type and item_type['count'] > 0

        return list(filter(is_nonempty, collection_contents))
Esempio n. 20
0
def delete(request, user_id):
    user = get_object_or_404(User, pk=user_id)

    if not user_can_delete_user(request.user, user):
        return permission_denied(request)

    for fn in hooks.get_hooks('before_delete_user'):
        result = fn(request, user)
        if hasattr(result, 'status_code'):
            return result
    if request.method == 'POST':
        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 render(request, "wagtailusers/users/confirm_delete.html", {
        'user': user,
    })
Esempio n. 21
0
    def render_html(self, request):
        menu_items = self.menu_items_for_request(request)

        # provide a hook for modifying the menu, if construct_hook_name has been set
        if self.construct_hook_name:
            for fn in hooks.get_hooks(self.construct_hook_name):
                fn(request, menu_items)

        rendered_menu_items = []
        for item in sorted(menu_items, key=lambda i: i.order):
            rendered_menu_items.append(item.render_html(request))

        return mark_safe(''.join(rendered_menu_items))
Esempio n. 22
0
def move_confirm(request, page_to_move_id, destination_id):
    page_to_move = get_object_or_404(Page, id=page_to_move_id).specific
    destination = get_object_or_404(Page, id=destination_id)
    if not page_to_move.permissions_for_user(request.user).can_move_to(destination):
        raise PermissionDenied

    if not Page._slug_is_available(page_to_move.slug, destination, page=page_to_move):
        messages.error(
            request,
            _("The slug '{0}' is already in use at the selected parent page. Make sure the slug is unique and try again".format(page_to_move.slug))
        )
        return redirect('wagtailadmin_pages:move_choose_destination', page_to_move.id, destination.id)

    for fn in hooks.get_hooks('before_move_page'):
        result = fn(request, page_to_move, destination)
        if hasattr(result, 'status_code'):
            return result

    if request.method == 'POST':
        # any invalid moves *should* be caught by the permission check above,
        # so don't bother to catch InvalidMoveToDescendant
        page_to_move.move(destination, pos='last-child')

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

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

        return redirect('wagtailadmin_explore', destination.id)

    return render(request, 'wagtailadmin/pages/confirm_move.html', {
        'page_to_move': page_to_move,
        'destination': destination,
    })
Esempio n. 23
0
    def __init__(self, request, **kwargs):
        self.request = request
        self.context = kwargs
        self.context['user_page_permissions'] = UserPagePermissionsProxy(self.request.user)

        self.menu_items = [
            menu_item
            for menu_item in _get_base_page_action_menu_items()
            if menu_item.is_shown(self.request, self.context)
        ]

        self.menu_items.sort(key=lambda item: item.order)

        for hook in hooks.get_hooks('construct_page_action_menu'):
            hook(self.menu_items, self.request, self.context)
Esempio n. 24
0
    def render_html(self, request):
        menu_items = self.menu_items_for_request(request)

        # provide a hook for modifying the menu, if construct_hook_name has been set
        if self.construct_hook_name:
            for fn in hooks.get_hooks(self.construct_hook_name):
                fn(request, menu_items)

        rendered_menu_items = []
        for item in sorted(menu_items, key=lambda i: i.order):
            try:
                rendered_menu_items.append(item.render_html(request))
            except TypeError:
                # fallback for older render_html methods that don't accept a request arg
                rendered_menu_items.append(item.render_html(request))

        return mark_safe(''.join(rendered_menu_items))
Esempio n. 25
0
def _get_base_page_action_menu_items():
    """
    Retrieve the global list of menu items for the page action menu,
    which may then be customised on a per-request basis
    """
    global BASE_PAGE_ACTION_MENU_ITEMS

    if BASE_PAGE_ACTION_MENU_ITEMS is None:
        BASE_PAGE_ACTION_MENU_ITEMS = [
            UnpublishMenuItem(order=10),
            DeleteMenuItem(order=20),
            PublishMenuItem(order=30),
            SubmitForModerationMenuItem(order=40),
        ]
        for hook in hooks.get_hooks('register_page_action_menu_item'):
            BASE_PAGE_ACTION_MENU_ITEMS.append(hook())

    return BASE_PAGE_ACTION_MENU_ITEMS
Esempio n. 26
0
    def __init__(self, converter_rules):
        self.converter_rules = converter_rules
        self.element_rules = BASE_WHITELIST_RULES.copy()
        for rule in self.converter_rules:
            if isinstance(rule, WhitelistRule):
                self.element_rules[rule.element] = rule.handler

        # apply legacy construct_whitelister_element_rules hooks to the assembled list
        construct_whitelist_hooks = hooks.get_hooks('construct_whitelister_element_rules')
        if construct_whitelist_hooks:
            warnings.warn(
                'The construct_whitelister_element_rules hook is deprecated and will be removed '
                'in Wagtail 2.2. Use register_rich_text_features instead',
                RemovedInWagtail22Warning
            )

            for fn in construct_whitelist_hooks:
                self.element_rules.update(fn())
Esempio n. 27
0
def for_frontend(request, page_id):
    items = [
        EditPageItem(Page.objects.get(id=page_id)),
        AddPageItem(Page.objects.get(id=page_id)),
    ]

    for fn in hooks.get_hooks('construct_wagtail_userbar'):
        fn(request, items)

    # Render the items
    rendered_items = [item.render(request) for item in items]

    # Remove any unrendered items
    rendered_items = [item for item in rendered_items if item]

    # Render the edit bird
    return render(request, 'wagtailadmin/userbar/base.html', {
        'items': rendered_items,
    })
Esempio n. 28
0
def for_moderation(request, revision_id):
    items = [
        EditPageItem(PageRevision.objects.get(id=revision_id).page),
        AddPageItem(PageRevision.objects.get(id=revision_id).page),
        ApproveModerationEditPageItem(PageRevision.objects.get(id=revision_id)),
        RejectModerationEditPageItem(PageRevision.objects.get(id=revision_id)),
    ]

    for fn in hooks.get_hooks('construct_wagtail_userbar'):
        fn(request, items)

    # Render the items
    rendered_items = [item.render(request) for item in items]

    # Remove any unrendered items
    rendered_items = [item for item in rendered_items if item]

    # Render the edit bird
    return render(request, 'wagtailadmin/userbar/base.html', {
        'items': rendered_items,
    })
Esempio n. 29
0
def search(request, parent_page_id=None):
    # A missing or empty page_type parameter indicates 'all page types' (i.e. descendants of wagtailcore.page)
    page_type_string = request.GET.get('page_type') or 'wagtailcore.page'

    try:
        desired_classes = page_models_from_string(page_type_string)
    except (ValueError, LookupError):
        raise Http404

    pages = Page.objects.all()
    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_page_chooser_queryset'):
        pages = hook(pages, request)

    search_form = SearchForm(request.GET)
    if search_form.is_valid() and search_form.cleaned_data['q']:
        pages = pages.exclude(
            depth=1  # never include root
        )
        pages = filter_page_type(pages, desired_classes)
        pages = pages.specific()
        pages = pages.search(search_form.cleaned_data['q'])
    else:
        pages = pages.none()

    paginator = Paginator(pages, per_page=25)
    pages = paginator.get_page(request.GET.get('p'))

    for page in pages:
        page.can_choose = True

    return render(
        request, 'wagtailadmin/chooser/_search_results.html',
        shared_context(request, {
            'searchform': search_form,
            'pages': pages,
            'page_type_string': page_type_string,
        })
    )
Esempio n. 30
0
def archive(request, page_id):
    page = get_object_or_404(Page, id=page_id)
    if not page.permissions_for_user(request.user).can_archive():
        raise PermissionDenied

    if request.method == 'POST':
        parent_id = page.get_parent().id
        page.archive()

        messages.success(request, _("Page '{0}' archived.").format(page.title))

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

        return redirect('wagtailadmin_explore', parent_id)

    return render(request, 'wagtailadmin/pages/confirm_archive.html', {
        'page': page,
        'descendant_count': page.get_descendant_count()
    })
Esempio n. 31
0
def copy(request, page_id):
    page = Page.objects.get(id=page_id)

    # Parent page defaults to parent of source page
    parent_page = page.get_parent()

    # Check if the user has permission to publish subpages on the parent
    can_publish = parent_page.permissions_for_user(
        request.user).can_publish_subpage()

    # Create the form
    form = CopyForm(request.POST or None,
                    user=request.user,
                    page=page,
                    can_publish=can_publish)

    next_url = get_valid_next_url_from_request(request)

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

    # Check if user is submitting
    if request.method == 'POST':
        # Prefill parent_page in case the form is invalid (as prepopulated value for the form field,
        # because ModelChoiceField seems to not fall back to the user given value)
        parent_page = Page.objects.get(id=request.POST['new_parent_page'])

        if form.is_valid():
            # Receive the parent page (this should never be empty)
            if form.cleaned_data['new_parent_page']:
                parent_page = form.cleaned_data['new_parent_page']

            if not page.permissions_for_user(request.user).can_copy_to(
                    parent_page, form.cleaned_data.get('copy_subpages')):
                raise PermissionDenied

            # Re-check if the user has permission to publish subpages on the new parent
            can_publish = parent_page.permissions_for_user(
                request.user).can_publish_subpage()

            # Copy the page
            new_page = page.copy(
                recursive=form.cleaned_data.get('copy_subpages'),
                to=parent_page,
                update_attrs={
                    'title': form.cleaned_data['new_title'],
                    'slug': form.cleaned_data['new_slug'],
                },
                keep_live=(can_publish
                           and form.cleaned_data.get('publish_copies')),
                user=request.user,
            )

            # Give a success message back to the user
            if form.cleaned_data.get('copy_subpages'):
                messages.success(
                    request,
                    _("Page '{0}' and {1} subpages copied.").format(
                        page.get_admin_display_title(),
                        new_page.get_descendants().count()))
            else:
                messages.success(
                    request,
                    _("Page '{0}' copied.").format(
                        page.get_admin_display_title()))

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

            # Redirect to explore of parent page
            if next_url:
                return redirect(next_url)
            return redirect('wagtailadmin_explore', parent_page.id)

    return render(request, 'wagtailadmin/pages/copy.html', {
        'page': page,
        'form': form,
        'next': next_url,
    })
Esempio n. 32
0
from wagtail.core import hooks

REGISTERED_COLLECTIONS = {}

for fn in hooks.get_hooks('rai_document_collection'):
    collection = fn()
    REGISTERED_COLLECTIONS.update(collection)

REGISTERED_ONDEMAND_DOCUMENTS = {}

for fn in hooks.get_hooks('rai_document_on_demand'):
    key, doc = fn()
    relation = doc.relation.__name__

    docs = REGISTERED_ONDEMAND_DOCUMENTS.get(relation, {})
    docs.update({key: doc})
    REGISTERED_ONDEMAND_DOCUMENTS.update({relation: docs})
Esempio n. 33
0
 def get_base_page_queryset(self):
     qs = Page.objects.filter(live=True, expired=False, show_in_menus=True)
     # allow hooks to modify the queryset
     for hook in hooks.get_hooks('menus_modify_base_page_queryset'):
         qs = hook(qs, **self.common_hook_kwargs)
     return qs
Esempio n. 34
0
def edit(request, page_id):
    latest_revision = get_object_or_404(Page, id=page_id).get_latest_revision()
    page = get_object_or_404(Page, id=page_id).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()
    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)

            edit_handler = edit_handler.bind_to_instance(instance=page,
                                                         form=form)
            errors_debug = (repr(edit_handler.form.errors) + repr(
                [(name, formset.errors)
                 for (name, formset) in edit_handler.form.formsets.items()
                 if formset.errors]))
            has_unsaved_changes = True
    else:
        form = form_class(instance=page, parent_page=parent)
        edit_handler = edit_handler.bind_to_instance(instance=page, form=form)
        has_unsaved_changes = False

    # 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)

    # Page status needs to present the version of the page containing the correct live URL
    if page.has_unpublished_changes:
        page_for_status = latest_revision.page.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,
            'preview_modes': page.preview_modes,
            'form': form,
            'next': next_url,
            'has_unsaved_changes': has_unsaved_changes,
        })
Esempio n. 35
0
def chooser(request):
    Image = get_image_model()

    if permission_policy.user_has_permission(request.user, 'add'):
        ImageForm = get_image_form(Image)
        uploadform = ImageForm(user=request.user,
                               prefix='image-chooser-upload')
    else:
        uploadform = None

    images = Image.objects.order_by('-created_at')

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_image_chooser_queryset'):
        images = hook(images, request)

    if ('q' in request.GET or 'p' in request.GET or 'tag' in request.GET
            or 'collection_id' in request.GET):
        # this request is triggered from search, pagination or 'popular tags';
        # we will just render the results.html fragment
        collection_id = request.GET.get('collection_id')
        if collection_id:
            images = images.filter(collection=collection_id)

        searchform = SearchForm(request.GET)
        if searchform.is_valid():
            q = searchform.cleaned_data['q']

            images = images.search(q)
            is_searching = True
        else:
            is_searching = False
            q = None

            tag_name = request.GET.get('tag')
            if tag_name:
                images = images.filter(tags__name=tag_name)

        # Pagination
        paginator = Paginator(images, per_page=CHOOSER_PAGE_SIZE)
        images = paginator.get_page(request.GET.get('p'))

        return TemplateResponse(
            request, "wagtailimages/chooser/results.html", {
                'images': images,
                'is_searching': is_searching,
                'query_string': q,
                'will_select_format': request.GET.get('select_format')
            })
    else:
        paginator = Paginator(images, per_page=CHOOSER_PAGE_SIZE)
        images = paginator.get_page(request.GET.get('p'))

        context = get_chooser_context(request)
        context.update({
            'images': images,
            'uploadform': uploadform,
        })
        return render_modal_workflow(request,
                                     'wagtailimages/chooser/chooser.html',
                                     None,
                                     context,
                                     json_data=get_chooser_js_data())
Esempio n. 36
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.registered_permissions = Permission.objects.none()
     for fn in hooks.get_hooks('register_permissions'):
         self.registered_permissions = self.registered_permissions | fn()
     self.fields['permissions'].queryset = self.registered_permissions.select_related('content_type')
Esempio n. 37
0
def index(request, parent_page_id=None):
    if parent_page_id:
        parent_page = get_object_or_404(Page, id=parent_page_id).specific
    else:
        parent_page = Page.get_first_root_node().specific

    pages = parent_page.get_children().prefetch_related(
        'content_type', 'sites_rooted_here')

    # Get page ordering
    ordering = request.GET.get('ordering', '-latest_revision_created_at')
    if ordering not in [
            'title', '-title', 'content_type', '-content_type', 'live',
            '-live', 'latest_revision_created_at',
            '-latest_revision_created_at', 'ord'
    ]:
        ordering = '-latest_revision_created_at'

    if ordering == 'ord':
        # preserve the native ordering from get_children()
        pass
    elif ordering == 'latest_revision_created_at':
        # order by oldest revision first.
        # Special case NULL entries - these should go at the top of the list.
        # Do this by annotating with Count('latest_revision_created_at'),
        # which returns 0 for these
        pages = pages.annotate(
            null_position=Count('latest_revision_created_at')).order_by(
                'null_position', 'latest_revision_created_at')
    elif ordering == '-latest_revision_created_at':
        # order by oldest revision first.
        # Special case NULL entries - these should go at the end of the list.
        pages = pages.annotate(
            null_position=Count('latest_revision_created_at')).order_by(
                '-null_position', '-latest_revision_created_at')
    else:
        pages = pages.order_by(ordering)

    # Don't paginate if sorting by page order - all pages must be shown to
    # allow drag-and-drop reordering
    do_paginate = ordering != 'ord'

    if do_paginate:
        # Retrieve pages in their most specific form.
        # Only do this for paginated listings, as this could potentially be a
        # very expensive operation when performed on a large queryset.
        pages = pages.specific()

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_explorer_page_queryset'):
        pages = hook(parent_page, pages, request)

    # Pagination
    if do_paginate:
        paginator, pages = paginate(request, pages, per_page=50)

    return render(
        request, 'wagtailadmin/pages/index.html', {
            'parent_page': parent_page.specific,
            'ordering': ordering,
            'pagination_query_params': "ordering=%s" % ordering,
            'pages': pages,
            'do_paginate': do_paginate,
        })
Esempio n. 38
0
 def registered_menu_items(self):
     if self._registered_menu_items is None:
         self._registered_menu_items = [
             fn() for fn in hooks.get_hooks(self.register_hook_name)
         ]
     return self._registered_menu_items
Esempio n. 39
0
def chooser(request):
    Image = get_image_model()

    if permission_policy.user_has_permission(request.user, 'add'):
        ImageForm = get_image_form(Image)
        uploadform = ImageForm(user=request.user)
    else:
        uploadform = None

    images = Image.objects.order_by('-created_at')

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_image_chooser_queryset'):
        images = hook(images, request)

    q = None
    if (
        'q' in request.GET or 'p' in request.GET or 'tag' in request.GET or
        'collection_id' in request.GET
    ):
        # this request is triggered from search, pagination or 'popular tags';
        # we will just render the results.html fragment
        collection_id = request.GET.get('collection_id')
        if collection_id:
            images = images.filter(collection=collection_id)

        searchform = SearchForm(request.GET)
        if searchform.is_valid():
            q = searchform.cleaned_data['q']

            images = images.search(q)
            is_searching = True
        else:
            is_searching = False

            tag_name = request.GET.get('tag')
            if tag_name:
                images = images.filter(tags__name=tag_name)

        # Pagination
        paginator, images = paginate(request, images, per_page=12)

        return render(request, "wagtailimages/chooser/results.html", {
            'images': images,
            'is_searching': is_searching,
            'query_string': q,
            'will_select_format': request.GET.get('select_format')
        })
    else:
        searchform = SearchForm()

        collections = Collection.objects.all()
        if len(collections) < 2:
            collections = None

        paginator, images = paginate(request, images, per_page=12)

        return render_modal_workflow(request, 'wagtailimages/chooser/chooser.html', None, {
            'images': images,
            'uploadform': uploadform,
            'searchform': searchform,
            'is_searching': False,
            'query_string': q,
            'will_select_format': request.GET.get('select_format'),
            'popular_tags': popular_tags_for_model(Image),
            'collections': collections,
        }, json_data=get_chooser_context())
Esempio n. 40
0
def chooser_upload(request):
    Image = get_image_model()
    ImageForm = get_image_form(Image)

    if request.method == 'POST':
        image = Image(uploaded_by_user=request.user)
        form = ImageForm(request.POST,
                         request.FILES,
                         instance=image,
                         user=request.user)

        if form.is_valid():
            # Set image file size
            image.file_size = image.file.size

            # Set image file hash
            image.file.seek(0)
            image._set_file_hash(image.file.read())
            image.file.seek(0)

            form.save()

            # Reindex the image to make sure all tags are indexed
            search_index.insert_or_update_object(image)

            if request.GET.get('select_format'):
                form = ImageInsertionForm(
                    initial={'alt_text': image.default_alt_text})
                return render_modal_workflow(
                    request,
                    'wagtailimages/chooser/select_format.html',
                    None, {
                        'image': image,
                        'form': form
                    },
                    json_data={'step': 'select_format'})
            else:
                # not specifying a format; return the image details now
                return render_modal_workflow(request,
                                             None,
                                             None,
                                             None,
                                             json_data={
                                                 'step':
                                                 'image_chosen',
                                                 'result':
                                                 get_image_result_data(image)
                                             })
    else:
        form = ImageForm(user=request.user)

    images = Image.objects.order_by('-created_at')

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_image_chooser_queryset'):
        images = hook(images, request)

    paginator, images = paginate(request, images, per_page=12)

    context = get_chooser_context(request)
    context.update({
        'images': images,
        'uploadform': form,
    })
    return render_modal_workflow(request,
                                 'wagtailimages/chooser/chooser.html',
                                 None,
                                 context,
                                 json_data=get_chooser_js_data())
Esempio n. 41
0
        name='wagtailadmin_account_change_email'),
    url(r'^account/notification_preferences/$',
        account.notification_preferences,
        name='wagtailadmin_account_notification_preferences'),
    url(r'^account/language_preferences/$',
        account.language_preferences,
        name='wagtailadmin_account_language_preferences'),
    url(r'^account/current_time_zone/$',
        account.current_time_zone,
        name='wagtailadmin_account_current_time_zone'),
    url(r'^logout/$', account.LogoutView.as_view(),
        name='wagtailadmin_logout'),
]

# Import additional urlpatterns from any apps that define a register_admin_urls hook
for fn in hooks.get_hooks('register_admin_urls'):
    urls = fn()
    if urls:
        urlpatterns += urls

# Add "wagtailadmin.access_admin" permission check
urlpatterns = decorate_urlpatterns(urlpatterns, require_admin_access)

# These url patterns do not require an authenticated admin user
urlpatterns += [
    url(r'^login/$', account.LoginView.as_view(), name='wagtailadmin_login'),

    # These two URLs have the "permission_required" decorator applied directly
    # as they need to fail with a 403 error rather than redirect to the login page
    url(r'^userbar/(\d+)/$',
        userbar.for_frontend,
Esempio n. 42
0
def chooser(request):
    Document = get_document_model()

    if permission_policy.user_has_permission(request.user, 'add'):
        DocumentForm = get_document_form(Document)
        uploadform = DocumentForm(user=request.user, prefix='document-chooser-upload')
    else:
        uploadform = None

    documents = Document.objects.all()

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_document_chooser_queryset'):
        documents = hook(documents, request)

    q = None
    if 'q' in request.GET or 'p' in request.GET or 'collection_id' in request.GET:

        collection_id = request.GET.get('collection_id')
        if collection_id:
            documents = documents.filter(collection=collection_id)
        documents_exist = documents.exists()

        searchform = SearchForm(request.GET)
        if searchform.is_valid():
            q = searchform.cleaned_data['q']

            documents = documents.search(q)
            is_searching = True
        else:
            documents = documents.order_by('-created_at')
            is_searching = False

        # Pagination
        paginator = Paginator(documents, per_page=10)
        documents = paginator.get_page(request.GET.get('p'))

        return render(request, "wagtaildocs/chooser/results.html", {
            'documents': documents,
            'documents_exist': documents_exist,
            'uploadform': uploadform,
            'query_string': q,
            'is_searching': is_searching,
            'collection_id': collection_id,
        })
    else:
        searchform = SearchForm()

        collections = Collection.objects.all()
        if len(collections) < 2:
            collections = None
        else:
            collections = Collection.order_for_display(collections)

        documents = documents.order_by('-created_at')
        documents_exist = documents.exists()
        paginator = Paginator(documents, per_page=10)
        documents = paginator.get_page(request.GET.get('p'))

        return render_modal_workflow(request, 'wagtaildocs/chooser/chooser.html', None, {
            'documents': documents,
            'documents_exist': documents_exist,
            'uploadform': uploadform,
            'searchform': searchform,
            'collections': collections,
            'is_searching': False,
        }, json_data=get_chooser_context())
Esempio n. 43
0
def page_listing_buttons(context, page, page_perms, is_parent=False):
    button_hooks = hooks.get_hooks('register_page_listing_buttons')
    buttons = sorted(itertools.chain.from_iterable(
        hook(page, page_perms, is_parent)
        for hook in button_hooks))
    return {'page': page, 'buttons': buttons}
Esempio n. 44
0
def before_copy_page(request, page):
    parent_page = page.get_parent()
    can_publish = parent_page.permissions_for_user(
        request.user).can_publish_subpage()
    form = PatchedCopyForm(request.POST or None,
                           user=request.user,
                           page=page,
                           can_publish=can_publish)
    next_url = get_valid_next_url_from_request(request)

    if request.method == 'POST':
        # Prefill parent_page in case the form is invalid (as prepopulated value for the form field,
        # because ModelChoiceField seems to not fall back to the user given value)
        parent_page = Page.objects.get(id=request.POST['new_parent_page'])

        if form.is_valid():
            # Receive the parent page (this should never be empty)
            if form.cleaned_data['new_parent_page']:
                parent_page = form.cleaned_data['new_parent_page']

            if not page.permissions_for_user(request.user).can_copy_to(
                    parent_page, form.cleaned_data.get('copy_subpages')):
                raise PermissionDenied

            # Re-check if the user has permission to publish subpages on the new parent
            can_publish = parent_page.permissions_for_user(
                request.user).can_publish_subpage()

            update_attrs = {}
            for code, name in settings.LANGUAGES:
                slug = build_localized_fieldname('slug', code)
                title = build_localized_fieldname('title', code)
                update_attrs[slug] = form.cleaned_data["new_{}".format(slug)]
                update_attrs[title] = form.cleaned_data["new_{}".format(title)]

            # Copy the page
            new_page = page.copy(
                recursive=form.cleaned_data.get('copy_subpages'),
                to=parent_page,
                update_attrs=update_attrs,
                keep_live=(can_publish
                           and form.cleaned_data.get('publish_copies')),
                user=request.user,
            )

            # Give a success message back to the user
            if form.cleaned_data.get('copy_subpages'):
                messages.success(
                    request,
                    _("Page '{0}' and {1} subpages copied.").format(
                        page.get_admin_display_title(),
                        new_page.get_descendants().count()))
            else:
                messages.success(
                    request,
                    _("Page '{0}' copied.").format(
                        page.get_admin_display_title()))

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

            # Redirect to explore of parent page
            if next_url:
                return redirect(next_url)
            return redirect('wagtailadmin_explore', parent_page.id)

    return render(request, 'modeltranslation_copy.html', {
        'page': page,
        'form': form,
        'next': next_url
    })
Esempio n. 45
0
def chooser(request):
    media_files = permission_policy.instances_user_has_any_permission_for(
        request.user, ["change", "delete"])

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks("construct_media_chooser_queryset"):
        media_files = hook(media_files, request)

    if permission_policy.user_has_permission(request.user, "add"):
        Media = get_media_model()
        MediaForm = get_media_form(Media)
        uploadform = MediaForm(user=request.user,
                               prefix="media-chooser-upload")
    else:
        uploadform = None

    q = None
    is_searching = False
    if "q" in request.GET or "p" in request.GET or "collection_id" in request.GET:
        collection_id = request.GET.get("collection_id")
        if collection_id:
            media_files = media_files.filter(collection=collection_id)

        searchform = SearchForm(request.GET)
        if searchform.is_valid():
            q = searchform.cleaned_data["q"]

            media_files = media_files.search(q)
            is_searching = True
        else:
            media_files = media_files.order_by("-created_at")
            is_searching = False

        # Pagination
        paginator, media_files = paginate(request, media_files, per_page=10)

        return render(
            request,
            "wagtailmedia/chooser/results.html",
            {
                "media_files": media_files,
                "query_string": q,
                "is_searching": is_searching,
                "pagination_template": pagination_template,
            },
        )
    else:
        searchform = SearchForm()

        collections = Collection.objects.all()
        if len(collections) < 2:
            collections = None

        media_files = media_files.order_by("-created_at")
        paginator, media_files = paginate(request, media_files, per_page=10)

    return render_modal_workflow(
        request,
        "wagtailmedia/chooser/chooser.html",
        None,
        {
            "media_files": media_files,
            "searchform": searchform,
            "collections": collections,
            "uploadform": uploadform,
            "is_searching": False,
            "pagination_template": pagination_template,
        },
        json_data={
            "step": "chooser",
            "error_label": "Server Error",
            "error_message":
            "Report this error to your webmaster with the following information:",
            "tag_autocomplete_url": reverse("wagtailadmin_tag_autocomplete"),
        },
    )
Esempio n. 46
0
 def __init__(self, request):
     self.request = request
     self.summary_items = []
     for fn in hooks.get_hooks('construct_homepage_summary_items'):
         fn(request, self.summary_items)
Esempio n. 47
0
def chooser_upload(request, media_type):
    Media = get_media_model()
    MediaForm = get_media_form(Media)

    if request.method == "POST":
        media = Media(uploaded_by_user=request.user, type=media_type)
        form = MediaForm(
            request.POST,
            request.FILES,
            instance=media,
            user=request.user,
            prefix="media-chooser-upload",
        )
        if form.is_valid():
            form.save()

            # Reindex the media entry to make sure all tags are indexed
            for backend in get_search_backends():
                backend.add(media)

            return render_modal_workflow(
                request,
                None,
                None,
                None,
                json_data={
                    "step": "media_chosen",
                    "result": get_media_json(media)
                },
            )

    media_files = permission_policy.instances_user_has_any_permission_for(
        request.user, ["change", "delete"])

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks("construct_media_chooser_queryset"):
        media_files = hook(media_files, request)

    searchform = SearchForm()

    collections = Collection.objects.all()
    if len(collections) < 2:
        collections = None

    media_files = media_files.order_by("-created_at")
    paginator, media_files = paginate(request, media_files, per_page=10)

    context = {
        "media_files": media_files,
        "searchform": searchform,
        "collections": collections,
        "uploadform": form,
        "is_searching": False,
        "pagination_template": pagination_template,
        "media_type": media_type,
    }
    return render_modal_workflow(
        request,
        "wagtailmedia/chooser/chooser.html",
        None,
        context,
        json_data={"step": "chooser"},
    )
Esempio n. 48
0
def serve(request, document_id, document_filename):
    Document = get_document_model()
    doc = get_object_or_404(Document, id=document_id)

    # We want to ensure that the document filename provided in the URL matches the one associated with the considered
    # document_id. If not we can't be sure that the document the user wants to access is the one corresponding to the
    # <document_id, document_filename> pair.
    if doc.filename != document_filename:
        raise Http404('This document does not match the given filename.')

    for fn in hooks.get_hooks('before_serve_document'):
        result = fn(doc, request)
        if isinstance(result, HttpResponse):
            return result

    # Send document_served signal
    document_served.send(sender=Document, instance=doc, request=request)

    try:
        local_path = doc.file.path
    except NotImplementedError:
        local_path = None

    try:
        direct_url = doc.file.url
    except NotImplementedError:
        direct_url = None

    serve_method = getattr(settings, 'WAGTAILDOCS_SERVE_METHOD', None)

    # If no serve method has been specified, select an appropriate default for the storage backend:
    # redirect for remote storages (i.e. ones that provide a url but not a local path) and
    # serve_view for all other cases
    if serve_method is None:
        if direct_url and not local_path:
            serve_method = 'redirect'
        else:
            serve_method = 'serve_view'

    if serve_method in ('redirect', 'direct') and direct_url:
        # Serve the file by redirecting to the URL provided by the underlying storage;
        # this saves the cost of delivering the file via Python.
        # For serve_method == 'direct', this view should not normally be reached
        # (the document URL as used in links should point directly to the storage URL instead)
        # but we handle it as a redirect to provide sensible fallback /
        # backwards compatibility behaviour.
        return redirect(direct_url)

    if local_path:

        # Use wagtail.utils.sendfile to serve the file;
        # this provides support for mimetypes, if-modified-since and django-sendfile backends

        if hasattr(settings, 'SENDFILE_BACKEND'):
            return sendfile(request, local_path, attachment=True, attachment_filename=doc.filename)
        else:
            # Fallback to streaming backend if user hasn't specified SENDFILE_BACKEND
            return sendfile(
                request,
                local_path,
                attachment=True,
                attachment_filename=doc.filename,
                backend=sendfile_streaming_backend.sendfile
            )

    else:

        # We are using a storage backend which does not expose filesystem paths
        # (e.g. storages.backends.s3boto.S3BotoStorage) AND the developer has not allowed
        # redirecting to the file url directly.
        # Fall back on pre-sendfile behaviour of reading the file content and serving it
        # as a StreamingHttpResponse

        wrapper = FileWrapper(doc.file)
        response = StreamingHttpResponse(wrapper, content_type='application/octet-stream')

        response['Content-Disposition'] = 'attachment; filename=%s' % doc.filename

        # FIXME: storage backends are not guaranteed to implement 'size'
        response['Content-Length'] = doc.file.size

        return response
Esempio n. 49
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()
    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)
            edit_handler = edit_handler.bind_to_instance(instance=page,
                                                         form=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)
        edit_handler = edit_handler.bind_to_instance(instance=page, form=form)
        has_unsaved_changes = False

    return render(
        request, 'wagtailadmin/pages/create.html', {
            'content_type': content_type,
            'page_class': page_class,
            'parent_page': parent_page,
            'edit_handler': edit_handler,
            'preview_modes': page.preview_modes,
            'form': form,
            'next': next_url,
            'has_unsaved_changes': has_unsaved_changes,
        })
Esempio n. 50
0
 def dropdown_buttons(self):
     button_hooks = hooks.get_hooks(self.hook_name)
     return sorted(itertools.chain.from_iterable(
         hook(self.page, self.page_perms, self.is_parent)
         for hook in button_hooks))
Esempio n. 51
0
 def _scan_for_features(self):
     for fn in hooks.get_hooks('register_rich_text_features'):
         fn(self)
     self.has_scanned_for_features = True
Esempio n. 52
0
def custom_admin_round_copy_view(request, page):
    # Custom view to handle copied Round pages.
    # https://github.com/wagtail/wagtail/blob/124827911463f0cb959edbb9d8d5685578540bd3/wagtail/admin/views/pages.py#L824

    # Parent page defaults to parent of source page
    parent_page = page.get_parent()

    # Check if the user has permission to publish subpages on the parent
    can_publish = parent_page.permissions_for_user(
        request.user).can_publish_subpage()

    form = CopyForm(request.POST or None,
                    user=request.user,
                    page=page,
                    can_publish=can_publish)

    next_url = get_valid_next_url_from_request(request)

    # Prefill parent_page in case the form is invalid (as prepopulated value for the form field,
    # because ModelChoiceField seems to not fall back to the user given value)
    parent_page = Page.objects.get(id=request.POST['new_parent_page'])

    if form.is_valid():
        # Receive the parent page (this should never be empty)
        if form.cleaned_data['new_parent_page']:
            parent_page = form.cleaned_data['new_parent_page']

        if not page.permissions_for_user(request.user).can_copy_to(
                parent_page, form.cleaned_data.get('copy_subpages')):
            raise PermissionDenied

        # Re-check if the user has permission to publish subpages on the new parent
        can_publish = parent_page.permissions_for_user(
            request.user).can_publish_subpage()

        # Copy the page
        new_page = page.copy(
            recursive=form.cleaned_data.get('copy_subpages'),
            to=parent_page,
            update_attrs={
                'title': form.cleaned_data['new_title'],
                'slug': form.cleaned_data['new_slug'],
                'start_date': None,
                'end_date': None,
            },
            keep_live=(can_publish
                       and form.cleaned_data.get('publish_copies')),
            user=request.user,
        )

        messages.info(
            request,
            _(("Please select the date in the copied page. "
               "Newly copied pages have NONE value for the start and end date"
               )))

        # Give a success message back to the user
        if form.cleaned_data.get('copy_subpages'):
            messages.success(
                request,
                _("Page '{0}' and {1} subpages copied.").format(
                    page.get_admin_display_title(),
                    new_page.get_descendants().count()))
        else:
            messages.success(
                request,
                _("Page '{0}' copied.").format(page.get_admin_display_title()))

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

        # Redirect to explore of parent page
        if next_url:
            return redirect(next_url)
        return redirect('wagtailadmin_explore', parent_page.id)
Esempio n. 53
0
from django.conf.urls import url

from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.core import hooks

from .views import PagesAdminAPIViewSet

admin_api = WagtailAPIRouter('wagtailadmin_api')
admin_api.register_endpoint('pages', PagesAdminAPIViewSet)

for fn in hooks.get_hooks('construct_admin_api'):
    fn(admin_api)

urlpatterns = [
    url(r'^main/', admin_api.urls),
]
Esempio n. 54
0
def index(request, parent_page_id=None):
    if parent_page_id:
        parent_page = get_object_or_404(Page, id=parent_page_id)
    else:
        parent_page = Page.get_first_root_node()

    # This will always succeed because of the @user_passes_test above.
    root_page = get_explorable_root_page(request.user)

    # If this page isn't a descendant of the user's explorable root page,
    # then redirect to that explorable root page instead.
    if not (
        parent_page.pk == root_page.pk
        or parent_page.is_descendant_of(root_page)
    ):
        return redirect('wagtailadmin_explore', root_page.pk)

    parent_page = parent_page.specific

    user_perms = UserPagePermissionsProxy(request.user)
    pages = (
        parent_page.get_children().prefetch_related(
            "content_type", "sites_rooted_here"
        )
        & user_perms.explorable_pages()
    )

    # Get page ordering
    ordering = request.GET.get('ordering', '-latest_revision_created_at')
    if ordering not in [
        'title',
        '-title',
        'content_type',
        '-content_type',
        'live', '-live',
        'latest_revision_created_at',
        '-latest_revision_created_at',
        'ord'
    ]:
        ordering = '-latest_revision_created_at'

    if ordering == 'ord':
        # preserve the native ordering from get_children()
        pass
    elif ordering == 'latest_revision_created_at':
        # order by oldest revision first.
        # Special case NULL entries - these should go at the top of the list.
        # Do this by annotating with Count('latest_revision_created_at'),
        # which returns 0 for these
        pages = pages.annotate(
            null_position=Count('latest_revision_created_at')
        ).order_by('null_position', 'latest_revision_created_at')
    elif ordering == '-latest_revision_created_at':
        # order by oldest revision first.
        # Special case NULL entries - these should go at the end of the list.
        pages = pages.annotate(
            null_position=Count('latest_revision_created_at')
        ).order_by('-null_position', '-latest_revision_created_at')
    else:
        pages = pages.order_by(ordering)

    # Don't paginate if sorting by page order - all pages must be shown to
    # allow drag-and-drop reordering
    do_paginate = ordering != 'ord'

    # We want specific page instances, but do not need streamfield values here
    pages = pages.defer_streamfields().specific()

    # allow hooks defer_streamfieldsyset
    for hook in hooks.get_hooks('construct_explorer_page_queryset'):
        pages = hook(parent_page, pages, request)

    # Annotate queryset with various states to be used later for performance optimisations
    if getattr(settings, 'WAGTAIL_WORKFLOW_ENABLED', True):
        pages = pages.prefetch_workflow_states()

    pages = pages.annotate_site_root_state().annotate_approved_schedule()

    # Pagination
    if do_paginate:
        paginator = Paginator(pages, per_page=50)
        pages = paginator.get_page(request.GET.get('p'))

    context = {
        'parent_page': parent_page.specific,
        'ordering': ordering,
        'pagination_query_params': "ordering=%s" % ordering,
        'pages': pages,
        'do_paginate': do_paginate,
        'locale': None,
        'translations': [],
    }

    if getattr(settings, 'WAGTAIL_I18N_ENABLED', False) and not parent_page.is_root():
        context.update({
            'locale': parent_page.locale,
            'translations': [
                {
                    'locale': translation.locale,
                    'url': reverse('wagtailadmin_explore', args=[translation.id]),
                }
                for translation in parent_page.get_translations().only('id', 'locale').select_related('locale')
            ],
        })

    return TemplateResponse(request, 'wagtailadmin/pages/index.html', context)
Esempio n. 55
0
    def process_response(self, request: WSGIRequest, response: HttpResponse) -> HttpResponse:
        if not wagtailcache_settings.WAGTAIL_CACHE:
            return response

        if getattr(request, "_wagtailcache_skip", False):
            # If we should skip this response, add header and return.
            _patch_header(response, Status.SKIP)
            return response

        if not getattr(request, "_wagtailcache_update", False):
            # We don't need to update the cache, just return.
            return response

        # Check if the response is cacheable
        # Don't cache private or no-cache responses.
        # Do cache 200, 301, 302, 304, and 404 codes so that wagtail doesn't
        #   have to repeatedly look up these URLs in the database.
        # Don't cache streaming responses.
        is_cacheable = \
            CacheControl.NOCACHE.value not in response.get("Cache-Control", "") and \
            CacheControl.PRIVATE.value not in response.get("Cache-Control", "") and \
            response.status_code in (200, 301, 302, 304, 404) and \
            not response.streaming
        # Don't cache 200 responses that set a user-specific cookie in response to
        # a cookie-less request (e.g. CSRF tokens).
        if is_cacheable and response.status_code == 200:
            is_cacheable = not (
                not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie')
            )

        # Allow the user to override our caching decision.
        for fn in hooks.get_hooks('is_response_cacheable'):
            result = fn(response, is_cacheable)
            if isinstance(result, bool):
                is_cacheable = result

        # If we are not allowed to cache the response, just return.
        if not is_cacheable:
            # Add a response header to indicate this was intentionally not cached.
            _patch_header(response, Status.SKIP)
            return response

        # Try to get the timeout from the "max-age" section of the "Cache-
        # Control" header before reverting to using the cache's default.
        timeout = get_max_age(response)
        if timeout is None:
            timeout = self._wagcache.default_timeout
        patch_response_headers(response, timeout)
        if timeout:
            cache_key = learn_cache_key(request, response, timeout, None, cache=self._wagcache)
            if isinstance(response, SimpleTemplateResponse):
                response.add_post_render_callback(
                    lambda r: self._wagcache.set(cache_key, r, timeout)
                )
            else:
                self._wagcache.set(cache_key, response, timeout)

            # Add a response header to indicate this was a cache miss.
            _patch_header(response, Status.MISS)

        return response
Esempio n. 56
0
 def populate(self):
     for fn in hooks.get_hooks('register_admin_viewset'):
         viewset = fn()
         self.register(viewset)
Esempio n. 57
0
def browse(request, parent_page_id=None):
    # A missing or empty page_type parameter indicates 'all page types'
    # (i.e. descendants of wagtailcore.page)
    page_type_string = request.GET.get('page_type') or 'wagtailcore.page'
    user_perm = request.GET.get('user_perms', False)

    try:
        desired_classes = page_models_from_string(page_type_string)
    except (ValueError, LookupError):
        raise Http404

    # Find parent page
    if parent_page_id:
        parent_page = get_object_or_404(Page, id=parent_page_id)
    elif desired_classes == (Page,):
        # Just use the root page
        parent_page = Page.get_first_root_node()
    else:
        # Find the highest common ancestor for the specific classes passed in
        # In many cases, such as selecting an EventPage under an EventIndex,
        # this will help the administrator find their page quicker.
        all_desired_pages = Page.objects.all().type(*desired_classes)
        parent_page = all_desired_pages.first_common_ancestor()

    parent_page = parent_page.specific

    # Get children of parent page (without streamfields)
    pages = parent_page.get_children().defer_streamfields().specific()

    # allow hooks to modify the queryset
    for hook in hooks.get_hooks('construct_page_chooser_queryset'):
        pages = hook(pages, request)

    # Filter them by page type
    if desired_classes != (Page,):
        # restrict the page listing to just those pages that:
        # - are of the given content type (taking into account class inheritance)
        # - or can be navigated into (i.e. have children)
        choosable_pages = pages.type(*desired_classes)
        descendable_pages = pages.filter(numchild__gt=0)
        pages = choosable_pages | descendable_pages

    can_choose_root = request.GET.get('can_choose_root', False)

    # Do permission lookups for this user now, instead of for every page.
    permission_proxy = UserPagePermissionsProxy(request.user)

    # Parent page can be chosen if it is a instance of desired_classes
    parent_page.can_choose = can_choose_page(
        parent_page, permission_proxy, desired_classes, can_choose_root, user_perm)

    # Pagination
    # We apply pagination first so we don't need to walk the entire list
    # in the block below
    paginator = Paginator(pages, per_page=25)
    pages = paginator.get_page(request.GET.get('p'))

    # Annotate each page with can_choose/can_decend flags
    for page in pages:
        page.can_choose = can_choose_page(page, permission_proxy, desired_classes, can_choose_root, user_perm)
        page.can_descend = page.get_children_count()

    # Render
    context = shared_context(request, {
        'parent_page': parent_page,
        'parent_page_id': parent_page.pk,
        'pages': pages,
        'search_form': SearchForm(),
        'page_type_string': page_type_string,
        'page_type_names': [desired_class.get_verbose_name() for desired_class in desired_classes],
        'page_types_restricted': (page_type_string != 'wagtailcore.page')
    })

    return render_modal_workflow(
        request,
        'wagtailadmin/chooser/browse.html', None,
        context,
        json_data={'step': 'browse', 'parent_page_id': context['parent_page_id']},
    )
Esempio n. 58
0
def user_listing_buttons(context, user):
    button_hooks = hooks.get_hooks('register_user_listing_buttons')
    buttons = sorted(itertools.chain.from_iterable(
        hook(context, user)
        for hook in button_hooks))
    return {'user': user, 'buttons': buttons}
Esempio n. 59
0
    def __init__(self, request, **kwargs):
        self.request = request
        self.context = kwargs
        self.context['request'] = request
        page = self.context.get('page')
        user_page_permissions = UserPagePermissionsProxy(self.request.user)
        self.context['user_page_permissions'] = user_page_permissions
        if page:
            self.context[
                'user_page_permissions_tester'] = user_page_permissions.for_page(
                    page)

        self.menu_items = []

        if page:
            task = page.current_workflow_task
            current_workflow_state = page.current_workflow_state
            is_final_task = current_workflow_state and current_workflow_state.is_at_final_task
            if task:
                actions = task.get_actions(page, request.user)
                workflow_menu_items = []
                for name, label, launch_modal in actions:
                    icon_name = 'edit'
                    if name == "approve":
                        if is_final_task and not getattr(
                                settings,
                                'WAGTAIL_WORKFLOW_REQUIRE_REAPPROVAL_ON_EDIT',
                                False):
                            label = _("%(label)s and Publish") % {
                                'label': label
                            }
                        icon_name = 'success'

                    item = WorkflowMenuItem(name,
                                            label,
                                            launch_modal,
                                            icon_name=icon_name)

                    if requires_request_arg(item.is_shown):
                        warn(
                            "%s.is_shown should no longer take a 'request' argument. "
                            "See https://docs.wagtail.io/en/stable/releases/2.15.html#template-components-2-15"
                            % type(item).__name__,
                            category=RemovedInWagtail217Warning)
                        is_shown = item.is_shown(self.request, self.context)
                    else:
                        is_shown = item.is_shown(self.context)

                    if is_shown:
                        workflow_menu_items.append(item)
                self.menu_items.extend(workflow_menu_items)

        for menu_item in _get_base_page_action_menu_items():
            if requires_request_arg(menu_item.is_shown):
                warn(
                    "%s.is_shown should no longer take a 'request' argument. "
                    "See https://docs.wagtail.io/en/stable/releases/2.15.html#template-components-2-15"
                    % type(menu_item).__name__,
                    category=RemovedInWagtail217Warning)
                is_shown = menu_item.is_shown(self.request, self.context)
            else:
                is_shown = menu_item.is_shown(self.context)

            if is_shown:
                self.menu_items.append(menu_item)

        self.menu_items.sort(key=lambda item: item.order)

        for hook in hooks.get_hooks('construct_page_action_menu'):
            hook(self.menu_items, self.request, self.context)

        try:
            self.default_item = self.menu_items.pop(0)
        except IndexError:
            self.default_item = None
Esempio n. 60
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()
    content_type = real_page_record.cached_content_type
    page_class = real_page_record.specific_class

    if page_class is None:
        raise PageClassNotFoundError(
            f"The page '{real_page_record}' cannot be edited because the "
            f"model class used to create it ({content_type.app_label}."
            f"{content_type.model}) can no longer be found in the codebase. "
            "This usually happens as a result of switching between git "
            "branches without running migrations to trigger the removal of "
            "unused ContentTypes. To edit the page, you will need to switch "
            "back to a branch where the model class is still present."
        )

    page = real_page_record.get_latest_revision_as_page()
    parent = page.get_parent()

    page_perms = page.permissions_for_user(request.user)

    if not page_perms.can_edit():
        raise PermissionDenied

    next_url = get_valid_next_url_from_request(request)

    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()

    if request.method == 'GET':
        if page_perms.user_has_lock():
            if page.locked_at:
                lock_message = format_html(_("<b>Page '{}' was locked</b> by <b>you</b> on <b>{}</b>."), page.get_admin_display_title(), page.locked_at.strftime("%d %b %Y %H:%M"))
            else:
                lock_message = format_html(_("<b>Page '{}' is locked</b> by <b>you</b>."), page.get_admin_display_title())

            lock_message += format_html(
                '<span class="buttons"><button class="button button-small button-secondary" data-locking-action="{}">{}</button></span>',
                reverse('wagtailadmin_pages:unlock', args=(page.id,)),
                _("Unlock")
            )
            messages.warning(request, lock_message, extra_tags='lock')

        elif page.locked and page_perms.page_locked():
            # the page can also be locked at a permissions level if in a workflow, on a task the user is not a reviewer for
            # this should be indicated separately
            if page.locked_by and page.locked_at:
                lock_message = format_html(_("<b>Page '{}' was locked</b> by <b>{}</b> on <b>{}</b>."), page.get_admin_display_title(), str(page.locked_by), page.locked_at.strftime("%d %b %Y %H:%M"))
            else:
                # Page was probably locked with an old version of Wagtail, or a script
                lock_message = format_html(_("<b>Page '{}' is locked</b>."), page.get_admin_display_title())

            if page_perms.can_unlock():
                lock_message += format_html(
                    '<span class="buttons"><button class="button button-small button-secondary" data-locking-action="{}">{}</button></span>',
                    reverse('wagtailadmin_pages:unlock', args=(page.id,)),
                    _("Unlock")
                )
            messages.error(request, lock_message, extra_tags='lock')

        if page.current_workflow_state:
            workflow_state = page.current_workflow_state
            workflow = workflow_state.workflow
            workflow_tasks = workflow_state.all_tasks_with_status()
            task = workflow_state.current_task_state.task
            if (
                workflow_state.status != WorkflowState.STATUS_NEEDS_CHANGES
                and task.specific.page_locked_for_user(page, request.user)
            ):
                # Check for revisions still undergoing moderation and warn
                if len(workflow_tasks) == 1:
                    # If only one task in workflow, show simple message
                    workflow_info = _("This page is currently awaiting moderation.")
                else:
                    workflow_info = format_html(
                        _("This page is awaiting <b>'{}'</b> in the <b>'{}'</b> workflow."),
                        task.name, workflow.name
                    )
                messages.error(request, mark_safe(workflow_info + " " + _("Only reviewers for this task can edit the page.")),
                               extra_tags="lock")
    # Check for revisions still undergoing moderation and warn - this is for the old moderation system
    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)

    # Show current workflow state if set, default to last workflow state
    workflow_state = page.current_workflow_state or page.workflow_states.order_by('created_at').last()
    if workflow_state:
        workflow_tasks = workflow_state.all_tasks_with_status()
    else:
        workflow_tasks = []

    errors_debug = None

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

        is_publishing = False
        is_submitting = False
        is_restarting_workflow = False
        is_reverting = False
        is_saving = False
        is_cancelling_workflow = bool(request.POST.get('action-cancel-workflow')) and workflow_state and workflow_state.user_can_cancel(request.user)
        if is_cancelling_workflow:
            workflow_state.cancel(user=request.user)
            # do this here so even if the page is locked due to not having permissions, the original submitter can still cancel the workflow

        if form.is_valid() and not page_perms.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')) and page_perms.can_submit_for_moderation()
            is_restarting_workflow = bool(request.POST.get('action-restart-workflow')) and page_perms.can_submit_for_moderation() and workflow_state and workflow_state.user_can_cancel(request.user)
            is_reverting = bool(request.POST.get('revision'))

            is_performing_workflow_action = bool(request.POST.get('action-workflow-action'))
            if is_performing_workflow_action:
                workflow_action = request.POST['workflow-action-name']
                available_actions = page.current_workflow_task.get_actions(page, request.user)
                available_action_names = [name for name, verbose_name, modal in available_actions]
                if workflow_action not in available_action_names:
                    # prevent this action
                    is_performing_workflow_action = False

            is_saving = True
            has_content_changes = form.has_changed()

            if is_restarting_workflow:
                workflow_state.cancel(user=request.user)

            # 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'))

            if is_performing_workflow_action and not has_content_changes:
                # don't save a new revision, as we're just going to update the page's
                # workflow state with no content changes
                revision = latest_revision
            else:
                # Save revision
                revision = page.save_revision(
                    user=request.user,
                    log_action=True,  # Always log the new revision on edit
                    previous_revision=(previous_revision if is_reverting else None)
                )

            # store submitted go_live_at for messaging below
            go_live_at = page.go_live_at

            # Publish
            if is_publishing:
                for fn in hooks.get_hooks('before_publish_page'):
                    result = fn(request, page)
                    if hasattr(result, 'status_code'):
                        return result

                revision.publish(
                    user=request.user,
                    changed=has_content_changes,
                    previous_revision=(previous_revision if is_reverting else None)
                )

                # 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)

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

            # Submit
            if is_submitting or is_restarting_workflow:
                if workflow_state and workflow_state.status == WorkflowState.STATUS_NEEDS_CHANGES:
                    # If the workflow was in the needs changes state, resume the existing workflow on submission
                    workflow_state.resume(request.user)
                else:
                    # Otherwise start a new workflow
                    workflow = page.get_workflow()
                    workflow.start(page, request.user)

            if is_performing_workflow_action:
                extra_workflow_data_json = request.POST.get('workflow-action-extra-data', '{}')
                extra_workflow_data = json.loads(extra_workflow_data_json)
                page.current_workflow_task.on_action(page.current_workflow_task_state, request.user, workflow_action, **extra_workflow_data)

        # 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 = _(
                        "Version 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 version 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 = _(
                        "Version 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')
                )
            ])

        elif is_cancelling_workflow:
            message = _(
                "Workflow on page '{0}' has been cancelled."
            ).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')
                )
            ])

        elif is_restarting_workflow:

            message = _(
                "Workflow on page '{0}' has been restarted."
            ).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')
                )
            ])

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

            messages.success(request, message)
        elif is_saving:
            message = _(
                "Page '{0}' has been updated."
            ).format(
                page.get_admin_display_title()
            )

            messages.success(request, message)

        if is_saving:
            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 or is_restarting_workflow or is_performing_workflow_action:
                # 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_perms.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 TemplateResponse(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,
        'page_locked': page_perms.page_locked(),
        'workflow_state': workflow_state if workflow_state and workflow_state.is_active else None,
        'current_task_state': page.current_workflow_task_state,
        'publishing_will_cancel_workflow': workflow_tasks and getattr(settings, 'WAGTAIL_WORKFLOW_CANCEL_ON_PUBLISH', True)
    })