def explorer_breadcrumb( context, page, page_perms=None, include_self=True, use_next_template=False, trailing_breadcrumb_title=None, ): user = context["request"].user # find the closest common ancestor of the pages that this user has direct explore permission # (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb cca = get_explorable_root_page(user) if not cca: return {"pages": Page.objects.none()} return { "pages": page.get_ancestors(inclusive=include_self) .descendant_of(cca, inclusive=True) .specific(), "current_page": page, "page_perms": page_perms, "use_next_template": use_next_template, "trailing_breadcrumb_title": trailing_breadcrumb_title, # Only used in collapsible breadcrumb templates }
def get_context_data(self, **kwargs): request = self.request panels = [ SiteSummaryPanel(request), UpgradeNotificationPanel(request), PagesForModerationPanel(request), RecentEditsPanel(request), RecentActivityPanel(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 { '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 }
def render_component(self, request): start_page = get_explorable_root_page(request.user) if start_page: return PageExplorerMenuItemComponent(self.name, self.label, self.url, start_page.id, icon_name=self.icon_name, classnames=self.classnames) else: return super().render_component(request)
def explorer_breadcrumb( context, page, page_perms=None, include_self=True, trailing_arrow=False, show_header_buttons=False, ): user = context["request"].user # find the closest common ancestor of the pages that this user has direct explore permission # (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb cca = get_explorable_root_page(user) if not cca: return {"pages": Page.objects.none()} return { "pages": page.get_ancestors(inclusive=include_self).descendant_of( cca, inclusive=True).specific(), "current_page": page, "page_perms": page_perms, "trailing_arrow": trailing_arrow, "show_header_buttons": show_header_buttons, }
def get_context(self): root_page = get_explorable_root_page(self.request.user) if root_page: page_count = Page.objects.descendant_of(root_page, inclusive=True).count() if root_page.is_root(): # If the root page the user has access to is the Wagtail root, # subtract one from this count because the root is not a real page. page_count -= 1 # If precisely one site exists, link to its homepage rather than the # tree root, to discourage people from trying to create pages as siblings # of the homepage (#1883) try: root_page = Site.objects.get().root_page except (Site.DoesNotExist, Site.MultipleObjectsReturned): pass else: page_count = 0 return { 'root_page': root_page, 'total_pages': page_count, }
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 })
def get_context(self, request): context = super().get_context(request) start_page = get_explorable_root_page(request.user) if start_page: context['start_page_id'] = start_page.id return context
def test_nonadmin_sees_pages_below_closest_common_ancestor(self): User = get_user_model() user = User.objects.get(username='******') # Josh has permissions for /example-home/content/page-1 and /example-home/other-content, # of which the closest common ancestor is /example-home. self.assertEqual(get_explorable_root_page(user).id, 4) for page in get_pages_with_direct_explore_permission(user): self.assertIn(page.id, [6, 8])
def test_nonadmin_sees_pages_below_closest_common_ancestor(self): User = get_user_model() user = User.objects.get(email="*****@*****.**") # Josh has permissions for /example-home/content/page-1 and /example-home/other-content, # of which the closest common ancestor is /example-home. self.assertEqual(get_explorable_root_page(user).id, 4) for page in get_pages_with_direct_explore_permission(user): self.assertIn(page.id, [6, 8])
def get_context(self, request): context = super().get_context(request) start_page = get_explorable_root_page(request.user) if start_page: context['start_page_id'] = start_page.id return context
def test_nonadmin_sees_only_explorable_pages(self): # Sam has permissions for /home and /example-home/content/page-1 , of which the closest # common ancestor is root; we don't show root in the menu, so the top level will consist # of 'home' and 'example-home' (but not the sibling 'home-2', which Sam doesn't have # permission on) User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user).id, 1) for page in get_pages_with_direct_explore_permission(user): self.assertIn(page.id, [2, 6])
def test_nonadmin_sees_only_explorable_pages(self): # Sam has permissions for /home and /example-home/content/page-1 , of which the closest # common ancestor is root; we don't show root in the menu, so the top level will consist # of 'home' and 'example-home' (but not the sibling 'home-2', which Sam doesn't have # permission on) User = get_user_model() user = User.objects.get(email="*****@*****.**") self.assertEqual(get_explorable_root_page(user).id, 1) for page in get_pages_with_direct_explore_permission(user): self.assertIn(page.id, [2, 6])
def move_breadcrumb(context, page_to_move, viewed_page): user = context["request"].user cca = get_explorable_root_page(user) if not cca: return {"pages": Page.objects.none()} return { "pages": viewed_page.get_ancestors(inclusive=True) .descendant_of(cca, inclusive=True) .specific(), "page_to_move_id": page_to_move.id, }
def explorer_breadcrumb(context, page, include_self=False): user = context['request'].user # find the closest common ancestor of the pages that this user has direct explore permission # (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb cca = get_explorable_root_page(user) if not cca: return {'pages': Page.objects.none()} return { 'pages': page.get_ancestors(inclusive=include_self).descendant_of(cca, inclusive=True).specific() }
def explorer_breadcrumb(context, page, include_self=False): user = context['request'].user # find the closest common ancestor of the pages that this user has direct explore permission # (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb cca = get_explorable_root_page(user) if not cca: return {'pages': Page.objects.none()} return { 'pages': page.get_ancestors(inclusive=include_self).descendant_of(cca, inclusive=True).specific() }
def get_site_for_user(user): root_page = get_explorable_root_page(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 { 'root_page': root_page, 'root_site': root_site, 'site_name': real_site_name if real_site_name else settings.WAGTAIL_SITE_NAME, }
def test_page_move_default_destination(self): response = self.client.get( reverse('wagtailadmin_pages:move', args=(self.test_page_b.id, ))) self.assertEqual(response.status_code, 200) # The default destination is the parent of the page being moved self.assertEqual(response.context["viewed_page"].specific, self.section_c) cca = get_explorable_root_page(self.user) destinations = self.section_c.get_ancestors().descendant_of(cca) self.assertTrue(destinations.exists()) for destination_page in destinations: move_url = reverse('wagtailadmin_pages:move_choose_destination', args=(self.test_page_b.id, destination_page.id)) self.assertContains(response, move_url)
def get_context(self): # If there is a single site, link to the homepage of that site # Otherwise, if there are multiple sites, link to the root page try: site = Site.objects.get() #root = site.root_page root = get_explorable_root_page(self.request.user) single_site = True total_pages = UserPagePermissionsProxy( self.request.user).editable_pages().count() except (Site.DoesNotExist, Site.MultipleObjectsReturned): root = None single_site = False return { 'single_site': single_site, 'root_page': root, #'total_pages': Page.objects.count() - 1, # subtract 1 because the root node is not a real page 'total_pages': total_pages, }
def test_admins_see_all_pages(self): User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user).id, 1)
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")) show_ordering_column = request.GET.get("ordering") == "ord" context = { "parent_page": parent_page.specific, "ordering": ordering, "pages": pages, "do_paginate": do_paginate, "locale": None, "translations": [], "show_ordering_column": show_ordering_column, "show_bulk_actions": not show_ordering_column, "show_locale_labels": False, } if getattr(settings, "WAGTAIL_I18N_ENABLED", False): if 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")], }) else: context["show_locale_labels"] = True return TemplateResponse(request, "wagtailadmin/pages/index.html", context)
def test_nonadmin_sees_leaf_page_at_root_level(self): User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user).id, 6)
def test_nav_root_for_nonadmin_is_closest_common_ancestor(self): User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user).id, 2)
def test_nav_root_for_nonadmin_is_closest_common_ancestor(self): User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user).id, 2)
def test_nonadmin_with_no_page_perms_cannot_explore(self): User = get_user_model() user = User.objects.get(email="*****@*****.**") self.assertIsNone(get_explorable_root_page(user))
def test_nonadmin_sees_leaf_page_at_root_level(self): User = get_user_model() user = User.objects.get(email="*****@*****.**") self.assertEqual(get_explorable_root_page(user).id, 6)
def test_nonadmin_sees_leaf_page_at_root_level(self): User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user).id, 6)
def test_nav_root_for_nonadmin_is_closest_common_ancestor(self): User = get_user_model() user = User.objects.get(email="*****@*****.**") self.assertEqual(get_explorable_root_page(user).id, 2)
def test_admins_see_all_pages(self): User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user).id, 1)
def test_nonadmin_with_no_page_perms_cannot_explore(self): User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user), None)
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 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, })
def test_nonadmin_with_no_page_perms_cannot_explore(self): User = get_user_model() user = User.objects.get(username='******') self.assertEqual(get_explorable_root_page(user), None)
def get_root_page(self): return get_explorable_root_page(self.request.user)
def test_admins_see_all_pages(self): User = get_user_model() user = User.objects.get(email="*****@*****.**") self.assertEqual(get_explorable_root_page(user).id, 1)
def edit_translation(request, translation, instance): if isinstance(instance, Page): # Page # Note: Edit permission is already checked by the edit page view page_perms = instance.permissions_for_user(request.user) is_live = instance.live is_locked = instance.locked if instance.live_revision: last_published_at = instance.live_revision.created_at last_published_by = instance.live_revision.user else: last_published_at = instance.last_published_at last_published_by = None if instance.live: live_url = instance.full_url else: live_url = None can_publish = page_perms.can_publish() can_unpublish = page_perms.can_unpublish() can_lock = page_perms.can_lock() can_unlock = page_perms.can_unlock() can_delete = page_perms.can_delete() else: # Snippet # Note: Edit permission is already checked by the edit snippet view is_live = True is_locked = False last_published_at = None last_published_by = None live_url = None can_publish = True can_unpublish = False can_lock = False can_unlock = False can_delete = request.user.has_perm( get_permission_name('delete', instance.__class__)) source_instance = translation.source.get_source_instance() if request.method == 'POST': if request.POST.get('action') == 'publish': if isinstance(instance, Page): if not page_perms.can_publish(): raise PermissionDenied try: translation.save_target(user=request.user, publish=True) except ValidationError: messages.error( request, _("New validation errors were found when publishing '{object}' in {locale}. Please fix them or click publish again to ignore these translations for now." ).format( object=str(instance), locale=translation.target_locale.get_display_name())) else: # Refresh instance to title in success message is up to date instance.refresh_from_db() string_segments = translation.source.stringsegment_set.all( ).order_by('order') string_translations = string_segments.get_translations( translation.target_locale) # Using annotate_translation as this ignores errors by default (so both errors and missing segments treated the same) if string_segments.annotate_translation( translation.target_locale).filter( translation__isnull=True).exists(): # One or more strings had an error messages.warning( request, _("Published '{object}' in {locale} with missing translations - see below." ).format(object=str(instance), locale=translation.target_locale. get_display_name())) else: messages.success( request, _("Published '{object}' in {locale}.").format( object=str(instance), locale=translation.target_locale.get_display_name( ))) return redirect(request.path) string_segments = translation.source.stringsegment_set.all().order_by( 'order') string_translations = string_segments.get_translations( translation.target_locale) overridable_segments = translation.source.overridablesegment_set.all( ).order_by('order') segment_overrides = overridable_segments.get_overrides( translation.target_locale) related_object_segments = translation.source.relatedobjectsegment_set.all( ).order_by('order') tab_helper = TabHelper(source_instance) breadcrumb = [] title_segment_id = None if isinstance(instance, Page): # find the closest common ancestor of the pages that this user has direct explore permission # (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb cca = get_explorable_root_page(request.user) if cca: breadcrumb = [{ 'id': page.id, 'isRoot': page.is_root(), 'title': page.title, 'exploreUrl': reverse('wagtailadmin_explore_root') if page.is_root() else reverse('wagtailadmin_explore', args=[page.id]), } for page in instance.get_ancestors( inclusive=False).descendant_of(cca, inclusive=True)] # Set to the ID of a string segment that represents the title. # If this segment has a translation, the title will be replaced with that translation. try: title_segment_id = string_segments.get(context__path='title').id except StringSegment.DoesNotExist: pass machine_translator = None translator = get_machine_translator() if translator and translator.can_translate(translation.source.locale, translation.target_locale): machine_translator = { 'name': translator.display_name, 'url': reverse('wagtail_localize:machine_translate', args=[translation.id]), } string_segment_data = [{ 'type': 'string', 'id': segment.id, 'contentPath': segment.context.path, 'source': segment.string.data, 'location': get_segment_location_info(source_instance, tab_helper, segment.context.path), 'editUrl': reverse('wagtail_localize:edit_string_translation', kwargs={ 'translation_id': translation.id, 'string_segment_id': segment.id }), 'order': segment.order, } for segment in string_segments] syncronised_value_segment_data = [{ 'type': 'synchronised_value', 'id': segment.id, 'contentPath': segment.context.path, 'location': get_segment_location_info(source_instance, tab_helper, segment.context.path, widget=True), 'value': segment.data, 'editUrl': reverse('wagtail_localize:edit_override', kwargs={ 'translation_id': translation.id, 'overridable_segment_id': segment.id }), 'order': segment.order, } for segment in overridable_segments] def get_source_object_info(segment): instance = segment.get_source_instance() if isinstance(instance, Page): return { 'title': str(instance), 'isLive': instance.live, 'liveUrl': instance.full_url, 'editUrl': reverse('wagtailadmin_pages:edit', args=[instance.id]), 'createTranslationRequestUrl': reverse('wagtail_localize:submit_page_translation', args=[instance.id]), } else: return { 'title': str(instance), 'isLive': True, 'editUrl': reverse('wagtailsnippets:edit', args=[ instance._meta.app_label, instance._meta.model_name, quote(instance.id) ]), 'createTranslationRequestUrl': reverse('wagtail_localize:submit_snippet_translation', args=[ instance._meta.app_label, instance._meta.model_name, quote(instance.id) ]), } def get_dest_object_info(segment): instance = segment.object.get_instance_or_none( translation.target_locale) if not instance: return if isinstance(instance, Page): return { 'title': str(instance), 'isLive': instance.live, 'liveUrl': instance.full_url, 'editUrl': reverse('wagtailadmin_pages:edit', args=[instance.id]), } else: return { 'title': str(instance), 'isLive': True, 'editUrl': reverse('wagtailsnippets:edit', args=[ instance._meta.app_label, instance._meta.model_name, quote(instance.id) ]), } def get_translation_progress(segment, locale): try: translation = Translation.objects.get( source__object_id=segment.object_id, target_locale=locale, enabled=True) except Translation.DoesNotExist: return None total_segments, translated_segments = translation.get_progress() return { 'totalSegments': total_segments, 'translatedSegments': translated_segments, } related_object_segment_data = [{ 'type': 'related_object', 'id': segment.id, 'contentPath': segment.context.path, 'location': get_segment_location_info(source_instance, tab_helper, segment.context.path), 'order': segment.order, 'source': get_source_object_info(segment), 'dest': get_dest_object_info(segment), 'translationProgress': get_translation_progress(segment, translation.target_locale), } for segment in related_object_segments] segments = string_segment_data + syncronised_value_segment_data + related_object_segment_data segments.sort(key=lambda segment: segment['order']) return render( request, 'wagtail_localize/admin/edit_translation.html', { # These props are passed directly to the TranslationEditor react component 'props': json.dumps( { 'object': { 'title': str(instance), 'titleSegmentId': title_segment_id, 'isLive': is_live, 'isLocked': is_locked, 'lastPublishedDate': last_published_at.strftime(DATE_FORMAT) if last_published_at is not None else None, 'lastPublishedBy': UserSerializer(last_published_by).data if last_published_by is not None else None, 'liveUrl': live_url, }, 'breadcrumb': breadcrumb, 'tabs': tab_helper.tabs_with_slugs, 'sourceLocale': { 'code': translation.source.locale.language_code, 'displayName': translation.source.locale.get_display_name(), }, 'locale': { 'code': translation.target_locale.language_code, 'displayName': translation.target_locale.get_display_name(), }, 'translations': [{ 'title': str(translated_instance), 'locale': { 'code': translated_instance.locale.language_code, 'displayName': translated_instance.locale.get_display_name(), }, 'editUrl': reverse('wagtailadmin_pages:edit', args=[translated_instance.id]) if isinstance( translated_instance, Page) else reverse('wagtailsnippets:edit', args=[ translated_instance._meta.app_label, translated_instance._meta.model_name, quote(translated_instance.id) ]), } for translated_instance in instance.get_translations(). select_related('locale')], 'perms': { 'canPublish': can_publish, 'canUnpublish': can_unpublish, 'canLock': can_lock, 'canUnlock': can_unlock, 'canDelete': can_delete, }, 'links': { 'downloadPofile': reverse('wagtail_localize:download_pofile', args=[translation.id]), 'uploadPofile': reverse('wagtail_localize:upload_pofile', args=[translation.id]), 'unpublishUrl': reverse('wagtailadmin_pages:unpublish', args=[instance.id]) if isinstance( instance, Page) else None, 'lockUrl': reverse('wagtailadmin_pages:lock', args=[instance.id]) if isinstance(instance, Page) else None, 'unlockUrl': reverse('wagtailadmin_pages:unlock', args=[instance.id]) if isinstance( instance, Page) else None, 'deleteUrl': reverse('wagtailadmin_pages:delete', args=[instance.id]) if isinstance( instance, Page) else reverse( 'wagtailsnippets:delete', args=[ instance._meta.app_label, instance._meta.model_name, quote(instance.pk) ]), 'stopTranslationUrl': reverse('wagtail_localize:stop_translation', args=[translation.id]), }, 'previewModes': [{ 'mode': mode, 'label': label, 'url': reverse('wagtail_localize:preview_translation', args=[translation.id]) if mode == instance.default_preview_mode else reverse( 'wagtail_localize:preview_translation', args=[translation.id, mode]), } for mode, label in (instance.preview_modes if isinstance( instance, Page) else [])], 'machineTranslator': machine_translator, 'segments': segments, # We serialize the translation data using Django REST Framework. # This gives us a consistent representation with the APIs so we # can dynamically update translations in the view. 'initialStringTranslations': StringTranslationSerializer(string_translations, many=True, context={ 'translation_source': translation.source }).data, 'initialOverrides': SegmentOverrideSerializer(segment_overrides, many=True, context={ 'translation_source': translation.source }).data, }, cls=DjangoJSONEncoder) })
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) # 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)
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 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 or pages.count() < 100: # Retrieve pages in their most specific form, so that custom # get_admin_display_title and get_url_parts methods on subclasses are respected. # However, skip this on unpaginated listings with >100 child pages as this could # be a significant performance hit. (This should only happen on the reorder view, # and hopefully no-one is having to do manual reordering on listings that large...) pages = pages.specific(defer=True) # 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, })
def edit_translation(request, translation, instance): if isinstance(instance, Page): # Page # Note: Edit permission is already checked by the edit page view page_perms = instance.permissions_for_user(request.user) is_live = instance.live is_locked = instance.locked if instance.live_revision: last_published_at = instance.live_revision.created_at last_published_by = instance.live_revision.user else: last_published_at = instance.last_published_at last_published_by = None if instance.live: live_url = instance.full_url else: live_url = None can_publish = page_perms.can_publish() can_unpublish = page_perms.can_unpublish() can_lock = page_perms.can_lock() can_unlock = page_perms.can_unlock() can_delete = page_perms.can_delete() else: # Snippet # Note: Edit permission is already checked by the edit snippet view is_live = True is_locked = False last_published_at = None last_published_by = None live_url = None can_publish = True can_unpublish = False can_lock = False can_unlock = False can_delete = request.user.has_perm( get_permission_name("delete", instance.__class__) ) source_instance = translation.source.get_source_instance() if request.method == "POST": if request.POST.get("action") == "publish": if isinstance(instance, Page): if not page_perms.can_publish(): raise PermissionDenied try: translation.save_target(user=request.user, publish=True) except ValidationError: messages.error( request, _( "New validation errors were found when publishing '{object}' in {locale}. Please fix them or click publish again to ignore these translations for now." ).format( object=str(instance), locale=translation.target_locale.get_display_name(), ), ) else: # Refresh instance to title in success message is up to date instance.refresh_from_db() string_segments = translation.source.stringsegment_set.all().order_by( "order" ) string_translations = string_segments.get_translations( translation.target_locale ) # Using annotate_translation as this ignores errors by default (so both errors and missing segments treated the same) if ( string_segments.annotate_translation(translation.target_locale) .filter(translation__isnull=True) .exists() ): # One or more strings had an error messages.warning( request, _( "Published '{object}' in {locale} with missing translations - see below." ).format( object=str(instance), locale=translation.target_locale.get_display_name(), ), ) else: messages.success( request, _("Published '{object}' in {locale}.").format( object=str(instance), locale=translation.target_locale.get_display_name(), ), ) return redirect(request.path) string_segments = translation.source.stringsegment_set.all().order_by("order") string_translations = string_segments.get_translations(translation.target_locale) overridable_segments = translation.source.overridablesegment_set.all().order_by( "order" ) segment_overrides = overridable_segments.get_overrides(translation.target_locale) related_object_segments = ( translation.source.relatedobjectsegment_set.all().order_by("order") ) tab_helper = TabHelper(source_instance) breadcrumb = [] title_segment_id = None if isinstance(instance, Page): # find the closest common ancestor of the pages that this user has direct explore permission # (i.e. add/edit/publish/lock) over; this will be the root of the breadcrumb cca = get_explorable_root_page(request.user) if cca: breadcrumb = [ { "id": page.id, "isRoot": page.is_root(), "title": page.title, "exploreUrl": reverse("wagtailadmin_explore_root") if page.is_root() else reverse("wagtailadmin_explore", args=[page.id]), } for page in instance.get_ancestors(inclusive=False).descendant_of( cca, inclusive=True ) ] # Set to the ID of a string segment that represents the title. # If this segment has a translation, the title will be replaced with that translation. try: title_segment_id = string_segments.get(context__path="title").id except StringSegment.DoesNotExist: pass machine_translator = None translator = get_machine_translator() if translator and translator.can_translate( translation.source.locale, translation.target_locale ): machine_translator = { "name": translator.display_name, "url": reverse("wagtail_localize:machine_translate", args=[translation.id]), } segments = [] for segment in string_segments: try: location_info = get_segment_location_info( source_instance, tab_helper, segment.context.path, segment.context.get_field_path(source_instance), ) except FieldHasNoEditPanelError: continue segments.append( { "type": "string", "id": segment.id, "contentPath": segment.context.path, "source": segment.string.data, "location": location_info, "editUrl": reverse( "wagtail_localize:edit_string_translation", kwargs={ "translation_id": translation.id, "string_segment_id": segment.id, }, ), "order": segment.order, } ) for segment in overridable_segments: try: location_info = get_segment_location_info( source_instance, tab_helper, segment.context.path, segment.context.get_field_path(source_instance), widget=True, ) except FieldHasNoEditPanelError: continue segments.append( { "type": "synchronised_value", "id": segment.id, "contentPath": segment.context.path, "location": location_info, "value": segment.data, "editUrl": reverse( "wagtail_localize:edit_override", kwargs={ "translation_id": translation.id, "overridable_segment_id": segment.id, }, ), "order": segment.order, } ) def get_source_object_info(segment): instance = segment.get_source_instance() if isinstance(instance, Page): return { "title": str(instance), "isLive": instance.live, "liveUrl": instance.full_url, "editUrl": reverse("wagtailadmin_pages:edit", args=[instance.id]), "createTranslationRequestUrl": reverse( "wagtail_localize:submit_page_translation", args=[instance.id] ), } else: return { "title": str(instance), "isLive": True, "editUrl": reverse( "wagtailsnippets:edit", args=[ instance._meta.app_label, instance._meta.model_name, quote(instance.id), ], ), "createTranslationRequestUrl": reverse( "wagtail_localize:submit_snippet_translation", args=[ instance._meta.app_label, instance._meta.model_name, quote(instance.id), ], ), } def get_dest_object_info(segment): instance = segment.object.get_instance_or_none(translation.target_locale) if not instance: return if isinstance(instance, Page): return { "title": str(instance), "isLive": instance.live, "liveUrl": instance.full_url, "editUrl": reverse("wagtailadmin_pages:edit", args=[instance.id]), } else: return { "title": str(instance), "isLive": True, "editUrl": reverse( "wagtailsnippets:edit", args=[ instance._meta.app_label, instance._meta.model_name, quote(instance.id), ], ), } def get_translation_progress(segment, locale): try: translation = Translation.objects.get( source__object_id=segment.object_id, target_locale=locale, enabled=True ) except Translation.DoesNotExist: return None total_segments, translated_segments = translation.get_progress() return { "totalSegments": total_segments, "translatedSegments": translated_segments, } for segment in related_object_segments: try: location_info = get_segment_location_info( source_instance, tab_helper, segment.context.path, segment.context.get_field_path(source_instance), ) except FieldHasNoEditPanelError: continue segments.append( { "type": "related_object", "id": segment.id, "contentPath": segment.context.path, "location": location_info, "order": segment.order, "source": get_source_object_info(segment), "dest": get_dest_object_info(segment), "translationProgress": get_translation_progress( segment, translation.target_locale ), } ) # Order segments by how they appear in the content panels # segment['location']['order'] is the content panel ordering # segment['order'] is the model field ordering # User's expect segments to follow the panel ordering as that's the ordering # that is used in the page editor of the source page. However, segments that # come from the same streamfield/inline panel are given the same value for # panel ordering, so we need to order by model field ordering as well (all # segments have a unique value for model field ordering) segments.sort(key=lambda segment: (segment["location"]["order"], segment["order"])) # Display a warning to the user if the schema of the source model has been updated since the source was last updated if translation.source.schema_out_of_date(): messages.warning( request, _( "The data model for '{model_name}' has been changed since the last translation sync. " "If any new fields have been added recently, these may not be visible until the next translation sync." ).format(model_name=capfirst(source_instance._meta.verbose_name)), ) return render( request, "wagtail_localize/admin/edit_translation.html", { "translation": translation, # These props are passed directly to the TranslationEditor react component "props": json.dumps( { "adminBaseUrl": reverse("wagtailadmin_home"), "object": { "title": str(instance), "titleSegmentId": title_segment_id, "isLive": is_live, "isLocked": is_locked, "lastPublishedDate": last_published_at.strftime(DATE_FORMAT) if last_published_at is not None else None, "lastPublishedBy": UserSerializer(last_published_by).data if last_published_by is not None else None, "liveUrl": live_url, }, "breadcrumb": breadcrumb, "tabs": tab_helper.tabs_with_slugs, "sourceLocale": { "code": translation.source.locale.language_code, "displayName": translation.source.locale.get_display_name(), }, "locale": { "code": translation.target_locale.language_code, "displayName": translation.target_locale.get_display_name(), }, "translations": [ { "title": str(translated_instance), "locale": { "code": translated_instance.locale.language_code, "displayName": translated_instance.locale.get_display_name(), }, "editUrl": reverse( "wagtailadmin_pages:edit", args=[translated_instance.id] ) if isinstance(translated_instance, Page) else reverse( "wagtailsnippets:edit", args=[ translated_instance._meta.app_label, translated_instance._meta.model_name, quote(translated_instance.id), ], ), } for translated_instance in instance.get_translations().select_related( "locale" ) ], "perms": { "canPublish": can_publish, "canUnpublish": can_unpublish, "canLock": can_lock, "canUnlock": can_unlock, "canDelete": can_delete, }, "links": { "downloadPofile": reverse( "wagtail_localize:download_pofile", args=[translation.id] ), "uploadPofile": reverse( "wagtail_localize:upload_pofile", args=[translation.id] ), "unpublishUrl": reverse( "wagtailadmin_pages:unpublish", args=[instance.id] ) if isinstance(instance, Page) else None, "lockUrl": reverse( "wagtailadmin_pages:lock", args=[instance.id] ) if isinstance(instance, Page) else None, "unlockUrl": reverse( "wagtailadmin_pages:unlock", args=[instance.id] ) if isinstance(instance, Page) else None, "deleteUrl": reverse( "wagtailadmin_pages:delete", args=[instance.id] ) if isinstance(instance, Page) else reverse( "wagtailsnippets:delete", args=[ instance._meta.app_label, instance._meta.model_name, quote(instance.pk), ], ), "stopTranslationUrl": reverse( "wagtail_localize:stop_translation", args=[translation.id] ), }, "previewModes": [ { "mode": mode, "label": label, "url": reverse( "wagtail_localize:preview_translation", args=[translation.id], ) if mode == instance.default_preview_mode else reverse( "wagtail_localize:preview_translation", args=[translation.id, mode], ), } for mode, label in ( instance.preview_modes if isinstance(instance, Page) else [] ) ], "machineTranslator": machine_translator, "segments": segments, # We serialize the translation data using Django REST Framework. # This gives us a consistent representation with the APIs so we # can dynamically update translations in the view. "initialStringTranslations": StringTranslationSerializer( string_translations, many=True, context={"translation_source": translation.source}, ).data, "initialOverrides": SegmentOverrideSerializer( segment_overrides, many=True, context={"translation_source": translation.source}, ).data, }, cls=DjangoJSONEncoder, ), }, )