Add block types report (#11041)
* Add block types report * Annotate content_types to query * Render page types blocks are used on * Render block name and status * Show remaining page types in tooltip * Update gitignore * Custom css for report * Remove not needed !important * Lint * Fix malformed HTML * Rename tag block * Add tests to tags * Add more test cases * Lint * Lint * Lint * Annotate content_types to query * Render page types blocks are used on * Show remaining page types in tooltip * Custom css for report * Lint * Rename tag block * Improve tags code readability * Better code blocks nomenclature * Use defaultdict * Only count live pages
This commit is contained in:
Родитель
9aa4d3bcd2
Коммит
1b9a749ff0
|
@ -92,6 +92,7 @@ celerybeat-schedule
|
||||||
|
|
||||||
# virtualenv
|
# virtualenv
|
||||||
venv/
|
venv/
|
||||||
|
.venv/
|
||||||
ENV/
|
ENV/
|
||||||
dockerpythonvenv/
|
dockerpythonvenv/
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
.w-block-report-title {
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
|
text-align: start;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-block-report-tippy {
|
||||||
|
appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: inherit;
|
||||||
|
text-align: start;
|
||||||
|
line-height: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-style: dotted;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-content a {
|
||||||
|
color: var(--w-color-white);
|
||||||
|
text-decoration: dotted;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-style: dotted;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 1px;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
from django import template
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.html import format_html, mark_safe
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def render_content_type(content_type):
|
||||||
|
"""Render a Content Type with a link to the content type's admin page"""
|
||||||
|
return format_html(
|
||||||
|
"<a href='{}'>{}</a>",
|
||||||
|
reverse(
|
||||||
|
"wagtailadmin_pages:type_use",
|
||||||
|
kwargs={
|
||||||
|
"content_type_app_name": content_type.app_label,
|
||||||
|
"content_type_model_name": content_type.model,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
content_type.name.title(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag
|
||||||
|
def render_content_types(content_types):
|
||||||
|
"""Render a list of content types"""
|
||||||
|
return mark_safe(", ".join([render_content_type(content_type) for content_type in content_types]))
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag("tags/reports/page_types_block.html")
|
||||||
|
def page_types_block(content_types):
|
||||||
|
content_types_hidden = []
|
||||||
|
count_hidden = 0
|
||||||
|
if len(content_types) > 3:
|
||||||
|
content_types_hidden = content_types[3:]
|
||||||
|
content_types = content_types[:3]
|
||||||
|
count_hidden = len(content_types_hidden)
|
||||||
|
return {
|
||||||
|
"content_types_shown": content_types,
|
||||||
|
"content_types_hidden": content_types_hidden,
|
||||||
|
"count_hidden": count_hidden,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag("tags/reports/block_name.html")
|
||||||
|
def block_name(page_block):
|
||||||
|
full_name = page_block["block"]
|
||||||
|
short_name = full_name.split(".")[-1]
|
||||||
|
return {
|
||||||
|
"full_name": full_name,
|
||||||
|
"short_name": short_name,
|
||||||
|
}
|
|
@ -0,0 +1,260 @@
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.urls import reverse
|
||||||
|
from factory import Faker
|
||||||
|
from wagtailinventory.helpers import create_page_inventory, delete_page_inventory
|
||||||
|
|
||||||
|
from networkapi.reports.views import BlockTypesReportView
|
||||||
|
from networkapi.utility.faker import StreamfieldProvider
|
||||||
|
from networkapi.wagtailpages.factory.campaign_page import CampaignPageFactory
|
||||||
|
from networkapi.wagtailpages.factory.opportunity import OpportunityPageFactory
|
||||||
|
from networkapi.wagtailpages.factory.primary_page import PrimaryPageFactory
|
||||||
|
from networkapi.wagtailpages.tests.base import WagtailpagesTestCase
|
||||||
|
|
||||||
|
Faker.add_provider(StreamfieldProvider)
|
||||||
|
|
||||||
|
|
||||||
|
class PatchedPrimaryPageFactory(PrimaryPageFactory):
|
||||||
|
body = Faker("streamfield", fields=["paragraph", "image", "airtable"])
|
||||||
|
|
||||||
|
|
||||||
|
class PatchedCampaignPageFactory(CampaignPageFactory):
|
||||||
|
body = Faker("streamfield", fields=["paragraph", "image"])
|
||||||
|
|
||||||
|
|
||||||
|
class PatchedOpportunityPageFactory(OpportunityPageFactory):
|
||||||
|
body = Faker("streamfield", fields=["paragraph"])
|
||||||
|
|
||||||
|
|
||||||
|
class BlockTypesReportViewTest(WagtailpagesTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.view = BlockTypesReportView()
|
||||||
|
User = get_user_model()
|
||||||
|
self.user = User.objects.create_superuser("admin-user", "admin@example.com", "password")
|
||||||
|
self.client.force_login(self.user)
|
||||||
|
|
||||||
|
def test_view(self):
|
||||||
|
"""Tests that the queryset is correct."""
|
||||||
|
# Create some pages with custom and standard blocks
|
||||||
|
primary_page = PatchedPrimaryPageFactory(parent=self.homepage)
|
||||||
|
campaign_page = PatchedCampaignPageFactory(parent=self.homepage)
|
||||||
|
opportunity_page = PatchedOpportunityPageFactory(parent=self.homepage)
|
||||||
|
|
||||||
|
# Update `wagtailinventory`'s index
|
||||||
|
create_page_inventory(primary_page)
|
||||||
|
create_page_inventory(campaign_page)
|
||||||
|
create_page_inventory(opportunity_page)
|
||||||
|
|
||||||
|
# Request the view
|
||||||
|
response = self.client.get(reverse("block_types_report"))
|
||||||
|
|
||||||
|
# Get the objects:
|
||||||
|
object_list = response.context["object_list"]
|
||||||
|
|
||||||
|
# The first, most used, should be the RichTextBlock created on all three pages
|
||||||
|
block = object_list[0]
|
||||||
|
self.assertEqual(block["block"], "wagtail.blocks.field_block.RichTextBlock")
|
||||||
|
self.assertEqual(block["count"], 3)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual(
|
||||||
|
[primary_page.content_type, campaign_page.content_type, opportunity_page.content_type],
|
||||||
|
block["content_types"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Two pages have crated ImageBlocks
|
||||||
|
# Each ImageBlock has a ImageChooserBlock and a CharBlock
|
||||||
|
# These should be in alphabetical order since they all have the same count
|
||||||
|
block = object_list[1]
|
||||||
|
self.assertEqual(
|
||||||
|
block["block"], "networkapi.wagtailpages.pagemodels.customblocks.annotated_image_block.AnnotatedImageBlock"
|
||||||
|
)
|
||||||
|
self.assertEqual(block["count"], 2)
|
||||||
|
self.assertEqual(block["type_label"], "Custom")
|
||||||
|
self.assertTrue(block["is_custom_block"])
|
||||||
|
self.assertListEqual([primary_page.content_type, campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[2]
|
||||||
|
self.assertEqual(
|
||||||
|
block["block"], "networkapi.wagtailpages.pagemodels.customblocks.annotated_image_block.RadioSelectBlock"
|
||||||
|
)
|
||||||
|
self.assertEqual(block["count"], 2)
|
||||||
|
self.assertEqual(block["type_label"], "Custom")
|
||||||
|
self.assertTrue(block["is_custom_block"])
|
||||||
|
self.assertListEqual([primary_page.content_type, campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[3]
|
||||||
|
self.assertEqual(block["block"], "wagtail.blocks.field_block.CharBlock")
|
||||||
|
self.assertEqual(block["count"], 2)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual([primary_page.content_type, campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[4]
|
||||||
|
self.assertEqual(block["block"], "wagtail.images.blocks.ImageChooserBlock")
|
||||||
|
self.assertEqual(block["count"], 2)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual([primary_page.content_type, campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
# FInally, we have the AirTableBlock, use only in one page
|
||||||
|
# This one is made of a URLBlock and a IntegerBlock
|
||||||
|
block = object_list[5]
|
||||||
|
self.assertEqual(
|
||||||
|
block["block"], "networkapi.wagtailpages.pagemodels.customblocks.airtable_block.AirTableBlock"
|
||||||
|
)
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Custom")
|
||||||
|
self.assertTrue(block["is_custom_block"])
|
||||||
|
self.assertListEqual([primary_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[6]
|
||||||
|
self.assertEqual(block["block"], "wagtail.blocks.field_block.IntegerBlock")
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual([primary_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[7]
|
||||||
|
self.assertEqual(block["block"], "wagtail.blocks.field_block.URLBlock")
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual([primary_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
def test_page_unpublished(self):
|
||||||
|
"""Tests that the queryset is updated when a page is unpublished"""
|
||||||
|
# Create some pages with custom and standard blocks
|
||||||
|
primary_page = PatchedPrimaryPageFactory(parent=self.homepage)
|
||||||
|
campaign_page = PatchedCampaignPageFactory(parent=self.homepage)
|
||||||
|
opportunity_page = PatchedOpportunityPageFactory(parent=self.homepage)
|
||||||
|
|
||||||
|
# Update `wagtailinventory`'s index
|
||||||
|
create_page_inventory(primary_page)
|
||||||
|
create_page_inventory(campaign_page)
|
||||||
|
create_page_inventory(opportunity_page)
|
||||||
|
|
||||||
|
# Unpublish primary page
|
||||||
|
primary_page.unpublish()
|
||||||
|
|
||||||
|
# Request the view
|
||||||
|
response = self.client.get(reverse("block_types_report"))
|
||||||
|
|
||||||
|
# Get the objects:
|
||||||
|
object_list = response.context["object_list"]
|
||||||
|
|
||||||
|
# The first, most used, should be the RichTextBlock created on the two live pages
|
||||||
|
block = object_list[0]
|
||||||
|
self.assertEqual(block["block"], "wagtail.blocks.field_block.RichTextBlock")
|
||||||
|
self.assertEqual(block["count"], 2)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual(
|
||||||
|
[campaign_page.content_type, opportunity_page.content_type],
|
||||||
|
block["content_types"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Two pages have crated ImageBlocks
|
||||||
|
# Each ImageBlock has a ImageChooserBlock and a CharBlock
|
||||||
|
# These should be in alphabetical order since they all have the same count
|
||||||
|
block = object_list[1]
|
||||||
|
self.assertEqual(
|
||||||
|
block["block"], "networkapi.wagtailpages.pagemodels.customblocks.annotated_image_block.AnnotatedImageBlock"
|
||||||
|
)
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Custom")
|
||||||
|
self.assertTrue(block["is_custom_block"])
|
||||||
|
self.assertListEqual([campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[2]
|
||||||
|
self.assertEqual(
|
||||||
|
block["block"], "networkapi.wagtailpages.pagemodels.customblocks.annotated_image_block.RadioSelectBlock"
|
||||||
|
)
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Custom")
|
||||||
|
self.assertTrue(block["is_custom_block"])
|
||||||
|
self.assertListEqual([campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[3]
|
||||||
|
self.assertEqual(block["block"], "wagtail.blocks.field_block.CharBlock")
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual([campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[4]
|
||||||
|
self.assertEqual(block["block"], "wagtail.images.blocks.ImageChooserBlock")
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual([campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
def test_page_deleted(self):
|
||||||
|
"""Tests that the queryset is updated when a page is deleted"""
|
||||||
|
# Create some pages with custom and standard blocks
|
||||||
|
primary_page = PatchedPrimaryPageFactory(parent=self.homepage)
|
||||||
|
campaign_page = PatchedCampaignPageFactory(parent=self.homepage)
|
||||||
|
opportunity_page = PatchedOpportunityPageFactory(parent=self.homepage)
|
||||||
|
|
||||||
|
# Update `wagtailinventory`'s index
|
||||||
|
create_page_inventory(primary_page)
|
||||||
|
create_page_inventory(campaign_page)
|
||||||
|
create_page_inventory(opportunity_page)
|
||||||
|
|
||||||
|
# Delete primary page
|
||||||
|
primary_page.delete()
|
||||||
|
|
||||||
|
# Update the inventory
|
||||||
|
delete_page_inventory(primary_page)
|
||||||
|
|
||||||
|
# Request the view
|
||||||
|
response = self.client.get(reverse("block_types_report"))
|
||||||
|
|
||||||
|
# Get the objects:
|
||||||
|
object_list = response.context["object_list"]
|
||||||
|
|
||||||
|
# The first, most used, should be the RichTextBlock created on the two live pages
|
||||||
|
block = object_list[0]
|
||||||
|
self.assertEqual(block["block"], "wagtail.blocks.field_block.RichTextBlock")
|
||||||
|
self.assertEqual(block["count"], 2)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual(
|
||||||
|
[campaign_page.content_type, opportunity_page.content_type],
|
||||||
|
block["content_types"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Two pages have crated ImageBlocks
|
||||||
|
# Each ImageBlock has a ImageChooserBlock and a CharBlock
|
||||||
|
# These should be in alphabetical order since they all have the same count
|
||||||
|
block = object_list[1]
|
||||||
|
self.assertEqual(
|
||||||
|
block["block"], "networkapi.wagtailpages.pagemodels.customblocks.annotated_image_block.AnnotatedImageBlock"
|
||||||
|
)
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Custom")
|
||||||
|
self.assertTrue(block["is_custom_block"])
|
||||||
|
self.assertListEqual([campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[2]
|
||||||
|
self.assertEqual(
|
||||||
|
block["block"], "networkapi.wagtailpages.pagemodels.customblocks.annotated_image_block.RadioSelectBlock"
|
||||||
|
)
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Custom")
|
||||||
|
self.assertTrue(block["is_custom_block"])
|
||||||
|
self.assertListEqual([campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[3]
|
||||||
|
self.assertEqual(block["block"], "wagtail.blocks.field_block.CharBlock")
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual([campaign_page.content_type], block["content_types"])
|
||||||
|
|
||||||
|
block = object_list[4]
|
||||||
|
self.assertEqual(block["block"], "wagtail.images.blocks.ImageChooserBlock")
|
||||||
|
self.assertEqual(block["count"], 1)
|
||||||
|
self.assertEqual(block["type_label"], "Core")
|
||||||
|
self.assertFalse(block["is_custom_block"])
|
||||||
|
self.assertListEqual([campaign_page.content_type], block["content_types"])
|
|
@ -0,0 +1,108 @@
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.html import format_html, mark_safe
|
||||||
|
|
||||||
|
from networkapi.reports.templatetags import report_tags
|
||||||
|
|
||||||
|
|
||||||
|
class RenderContentTypeTagTests(SimpleTestCase):
|
||||||
|
def test_render_content_type(self):
|
||||||
|
content_type = ContentType(app_label="myapp", model="mymodel")
|
||||||
|
expected_link = reverse(
|
||||||
|
"wagtailadmin_pages:type_use",
|
||||||
|
kwargs={
|
||||||
|
"content_type_app_name": content_type.app_label,
|
||||||
|
"content_type_model_name": content_type.model,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expected_name = "Mymodel"
|
||||||
|
|
||||||
|
result = report_tags.render_content_type(content_type)
|
||||||
|
|
||||||
|
self.assertHTMLEqual(result, format_html("<a href='{}'>{}</a>", expected_link, expected_name))
|
||||||
|
self.assertIsInstance(result, str)
|
||||||
|
self.assertTrue(mark_safe(result))
|
||||||
|
|
||||||
|
|
||||||
|
class RenderContentTypesTagTests(SimpleTestCase):
|
||||||
|
def test_render_list_of_content_types(self):
|
||||||
|
content_types = [
|
||||||
|
ContentType(app_label="myapp", model="mymodel1"),
|
||||||
|
ContentType(app_label="myapp", model="mymodel2"),
|
||||||
|
ContentType(app_label="myapp", model="mymodel3"),
|
||||||
|
]
|
||||||
|
expected_link_1 = reverse(
|
||||||
|
"wagtailadmin_pages:type_use",
|
||||||
|
kwargs={
|
||||||
|
"content_type_app_name": content_types[0].app_label,
|
||||||
|
"content_type_model_name": content_types[0].model,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expected_link_2 = reverse(
|
||||||
|
"wagtailadmin_pages:type_use",
|
||||||
|
kwargs={
|
||||||
|
"content_type_app_name": content_types[1].app_label,
|
||||||
|
"content_type_model_name": content_types[1].model,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expected_link_3 = reverse(
|
||||||
|
"wagtailadmin_pages:type_use",
|
||||||
|
kwargs={
|
||||||
|
"content_type_app_name": content_types[2].app_label,
|
||||||
|
"content_type_model_name": content_types[2].model,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_html = """
|
||||||
|
<a href='{}'>{}</a>, <a href='{}'>{}</a>, <a href='{}'>{}</a>
|
||||||
|
""".format(
|
||||||
|
expected_link_1,
|
||||||
|
"Mymodel1",
|
||||||
|
expected_link_2,
|
||||||
|
"Mymodel2",
|
||||||
|
expected_link_3,
|
||||||
|
"Mymodel3",
|
||||||
|
)
|
||||||
|
|
||||||
|
result = report_tags.render_content_types(content_types)
|
||||||
|
|
||||||
|
self.assertHTMLEqual(result, expected_html)
|
||||||
|
self.assertIsInstance(result, str)
|
||||||
|
self.assertTrue(mark_safe(result))
|
||||||
|
|
||||||
|
|
||||||
|
class PageTypesBlockTagTests(SimpleTestCase):
|
||||||
|
def test_page_types_block(self):
|
||||||
|
content_types = [
|
||||||
|
ContentType(app_label="myapp", model="mymodel1"),
|
||||||
|
ContentType(app_label="myapp", model="mymodel2"),
|
||||||
|
ContentType(app_label="myapp", model="mymodel3"),
|
||||||
|
ContentType(app_label="myapp", model="mymodel4"),
|
||||||
|
]
|
||||||
|
|
||||||
|
result = report_tags.page_types_block(content_types)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, dict)
|
||||||
|
self.assertIn("content_types_shown", result)
|
||||||
|
self.assertIn("content_types_hidden", result)
|
||||||
|
self.assertIn("count_hidden", result)
|
||||||
|
self.assertEqual(result["content_types_shown"], content_types[:3])
|
||||||
|
self.assertEqual(result["content_types_hidden"], content_types[3:])
|
||||||
|
self.assertEqual(result["count_hidden"], 1)
|
||||||
|
|
||||||
|
|
||||||
|
class BlockNameTagTests(SimpleTestCase):
|
||||||
|
def test_block_name(self):
|
||||||
|
page_block = {
|
||||||
|
"block": "myapp.mymodel.MyBlock",
|
||||||
|
"count": 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
result = report_tags.block_name(page_block)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, dict)
|
||||||
|
self.assertIn("full_name", result)
|
||||||
|
self.assertIn("short_name", result)
|
||||||
|
self.assertEqual(result["full_name"], "myapp.mymodel.MyBlock")
|
||||||
|
self.assertEqual(result["short_name"], "MyBlock")
|
|
@ -1,11 +1,14 @@
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
import django_filters
|
import django_filters
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.db.models import Count, OuterRef, Q, Subquery
|
from django.db.models import BooleanField, Case, Count, OuterRef, Q, Subquery, When
|
||||||
from wagtail.admin.filters import WagtailFilterSet
|
from wagtail.admin.filters import WagtailFilterSet
|
||||||
from wagtail.admin.views.reports import ReportView
|
from wagtail.admin.views.reports import ReportView
|
||||||
from wagtail.coreutils import get_content_languages
|
from wagtail.coreutils import get_content_languages
|
||||||
from wagtail.models import ContentType, Page, PageLogEntry, get_page_models
|
from wagtail.models import ContentType, Page, PageLogEntry, get_page_models
|
||||||
from wagtail.users.utils import get_deleted_user_display_name
|
from wagtail.users.utils import get_deleted_user_display_name
|
||||||
|
from wagtailinventory.models import PageBlock
|
||||||
|
|
||||||
|
|
||||||
def _get_locale_choices():
|
def _get_locale_choices():
|
||||||
|
@ -46,7 +49,7 @@ class PageTypesReportFilterSet(WagtailFilterSet):
|
||||||
|
|
||||||
|
|
||||||
class PageTypesReportView(ReportView):
|
class PageTypesReportView(ReportView):
|
||||||
title = "Page Types Report"
|
title = "Page types report"
|
||||||
template_name = "pages/reports/page_types_report.html"
|
template_name = "pages/reports/page_types_report.html"
|
||||||
header_icon = "doc-empty-inverse"
|
header_icon = "doc-empty-inverse"
|
||||||
|
|
||||||
|
@ -93,3 +96,44 @@ class PageTypesReportView(ReportView):
|
||||||
queryset = queryset.order_by("-count", "app_label", "model")
|
queryset = queryset.order_by("-count", "app_label", "model")
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class BlockTypesReportView(ReportView):
|
||||||
|
title = "Block types report"
|
||||||
|
template_name = "pages/reports/block_types_report.html"
|
||||||
|
header_icon = "placeholder"
|
||||||
|
|
||||||
|
def decorate_paginated_queryset(self, object_list):
|
||||||
|
# Build a cache map of PageBlock's block name to content types
|
||||||
|
page_blocks = PageBlock.objects.all().prefetch_related("page__content_type")
|
||||||
|
blocks_to_content_types = defaultdict(list)
|
||||||
|
for page_block in page_blocks:
|
||||||
|
if page_block.page.live and (
|
||||||
|
page_block.page.content_type not in blocks_to_content_types[page_block.block]
|
||||||
|
):
|
||||||
|
blocks_to_content_types[page_block.block].append(page_block.page.content_type)
|
||||||
|
|
||||||
|
# Get the content_types for each block name
|
||||||
|
for block_report_item in object_list:
|
||||||
|
content_types = blocks_to_content_types.get(block_report_item["block"], [])
|
||||||
|
block_report_item["content_types"] = content_types
|
||||||
|
block_report_item["type_label"] = "Custom" if block_report_item["is_custom_block"] else "Core"
|
||||||
|
|
||||||
|
return object_list
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
queryset = (
|
||||||
|
PageBlock.objects.all()
|
||||||
|
.values("block")
|
||||||
|
.annotate(
|
||||||
|
count=Count("page", filter=Q(page__live=True)),
|
||||||
|
is_custom_block=Case(
|
||||||
|
When(block__startswith="wagtail.", then=False), default=True, output_field=BooleanField()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.queryset = queryset
|
||||||
|
|
||||||
|
queryset = queryset.order_by("-count", "block")
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
|
@ -2,13 +2,13 @@ from django.urls import path, reverse
|
||||||
from wagtail import hooks
|
from wagtail import hooks
|
||||||
from wagtail.admin.menu import AdminOnlyMenuItem
|
from wagtail.admin.menu import AdminOnlyMenuItem
|
||||||
|
|
||||||
from .views import PageTypesReportView
|
from .views import BlockTypesReportView, PageTypesReportView
|
||||||
|
|
||||||
|
|
||||||
@hooks.register("register_reports_menu_item")
|
@hooks.register("register_reports_menu_item")
|
||||||
def register_page_types_report_menu_item():
|
def register_page_types_report_menu_item():
|
||||||
return AdminOnlyMenuItem(
|
return AdminOnlyMenuItem(
|
||||||
"Page Types", reverse("page_types_report"), icon_name=PageTypesReportView.header_icon, order=700
|
"Page types", reverse("page_types_report"), icon_name=PageTypesReportView.header_icon, order=700
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,3 +17,17 @@ def register_page_types_report_url():
|
||||||
return [
|
return [
|
||||||
path("reports/page-types-report/", PageTypesReportView.as_view(), name="page_types_report"),
|
path("reports/page-types-report/", PageTypesReportView.as_view(), name="page_types_report"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register("register_reports_menu_item")
|
||||||
|
def register_block_types_report_menu_item():
|
||||||
|
return AdminOnlyMenuItem(
|
||||||
|
"Block types", reverse("block_types_report"), icon_name=BlockTypesReportView.header_icon, order=701
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@hooks.register("register_admin_urls")
|
||||||
|
def register_block_types_report_url():
|
||||||
|
return [
|
||||||
|
path("reports/block-types-report/", BlockTypesReportView.as_view(), name="block_types_report"),
|
||||||
|
]
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends "wagtailadmin/reports/base_report.html" %}
|
||||||
|
{% load wagtailadmin_tags static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'css/reports.css' %}">
|
||||||
|
{% endblock extra_css %}
|
||||||
|
|
||||||
|
{% block results %}
|
||||||
|
{% with object_list as block_report_items %}
|
||||||
|
<div id="page-results">
|
||||||
|
{% if block_report_items %}
|
||||||
|
{% block listing %}
|
||||||
|
{% include "pages/reports/include/_list_block_types.html" %}
|
||||||
|
{% endblock listing %}
|
||||||
|
{% else %}
|
||||||
|
{% block no_results %}
|
||||||
|
<p>No block types match this report's criteria.</p>
|
||||||
|
{% endblock no_results %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock results %}
|
|
@ -0,0 +1,54 @@
|
||||||
|
{% load wagtailadmin_tags wagtailcore_tags report_tags %}
|
||||||
|
|
||||||
|
<table class="listing {% block table_classname %}{% endblock table_classname %}">
|
||||||
|
<col width="25%" />
|
||||||
|
<col width="10%" />
|
||||||
|
<col width="10%" />
|
||||||
|
<col />
|
||||||
|
<thead>
|
||||||
|
{% block post_parent_page_headers %}
|
||||||
|
<tr class="table-headers">
|
||||||
|
<th class="title">
|
||||||
|
Block
|
||||||
|
</th>
|
||||||
|
<th class="app-label">
|
||||||
|
Pages
|
||||||
|
</th>
|
||||||
|
<th class="app-label">
|
||||||
|
Type
|
||||||
|
</th>
|
||||||
|
<th class="app-label">
|
||||||
|
Used on
|
||||||
|
</th>
|
||||||
|
{% block extra_columns %}
|
||||||
|
{% endblock extra_columns %}
|
||||||
|
</tr>
|
||||||
|
{% endblock post_parent_page_headers %}
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if block_report_items %}
|
||||||
|
{% for block_report_item in block_report_items %}
|
||||||
|
<tr class="{% block page_row_classname %}{% endblock page_row_classname %}">
|
||||||
|
<td class="app-label" valign="top">
|
||||||
|
{% block_name block_report_item %}
|
||||||
|
</td>
|
||||||
|
<td class="app-label" valign="top">
|
||||||
|
{{ block_report_item.count }}
|
||||||
|
</td>
|
||||||
|
<td class="app-label" valign="top">
|
||||||
|
<span class="status-tag status-tag--label">{{ block_report_item.type_label }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="app-label" valign="top">
|
||||||
|
{% page_types_block block_report_item.content_types %}
|
||||||
|
</td>
|
||||||
|
{% block extra_page_data %}
|
||||||
|
{% endblock extra_page_data %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% block no_results %}
|
||||||
|
<p>No blocks found.</p>
|
||||||
|
{% endblock no_results %}
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -0,0 +1,5 @@
|
||||||
|
{% load wagtailcore_tags wagtailadmin_tags i18n %}
|
||||||
|
|
||||||
|
<button type="button" class="w-block-report-title" data-tippy-content="{{ full_name }}" data-tippy-maxWidth="50rem">
|
||||||
|
{{ short_name }}
|
||||||
|
</button>
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% load report_tags %}
|
||||||
|
|
||||||
|
{% render_content_types content_types_shown %}
|
||||||
|
{% if content_types_hidden %}
|
||||||
|
and
|
||||||
|
<button type="button"
|
||||||
|
class="w-block-report-tippy"
|
||||||
|
data-tippy-content="{% render_content_types content_types_hidden %}"
|
||||||
|
data-tippy-allowHTML="true"
|
||||||
|
data-tippy-interactive="true"
|
||||||
|
data-tippy-trigger="click"
|
||||||
|
>
|
||||||
|
{{ count_hidden }} more
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
Загрузка…
Ссылка в новой задаче