This command will find all objects of a specific content type (limited by configuration) and output a report of links to the admin edit pages.
Usage
The command can be dropped into any app folder as per the django management command folder structure and run with:
python manage.py report_types
How it might be useful:
The command is designed to be used locally, during development and will require a good dataset to be most useful (e.g. a copy of the staging/production database)
It is recommended that you run the command with a superuser account.
Scenario: A client has reported a particular page type is not working as expected in the admin interface. Let's assume they mention it's a custom page type called FeaturedPage that potentially could only ever be added as a child of a specific page type. It could be time consuming to browse through pages and find a page to test.
Usage: Just run the command to see all content types and then enter the contenttype ID from the list to show admin edit urls for all objects of that type.
# app-name/management/commands/report_types.py
from django.apps import apps
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from wagtail.admin.admin_url_finder import AdminURLFinder
from wagtail.admin.utils import get_admin_base_url
from wagtail.contrib.settings.registry import registry as settings_registry
from wagtail.models import Page
from wagtail.snippets.models import get_snippet_models
class Command(BaseCommand):
"""
Report on content types in the project.
This command will generate a list of all the content types in the project
along with their contenttype ID. The contenttype ID can be entered to generate a report of all
the admin edit pages of that type.
The command is only available in DEBUG mode. Set DEBUG=True in your settings to enable it.
Usage:
python manage.py report_types
"""
excluded_apps = []
apps_prefix = "" # optional [your-project-directory]
registered_modeladmin = [
# add model admin models as they cannot be auto detected. For example ...
"events.EventType",
]
def add_arguments(self, parser):
parser.add_argument(
"--host",
default=get_admin_base_url(),
help="The URL to check",
)
def handle(self, *args, **options):
"""Report on content types in the project."""
if not settings.DEBUG:
self.out_message_warning(
"Command is only available in DEBUG mode. Set DEBUG=True in your settings to enable it."
)
return
self.out_message_warning("\nUsing this command:")
self.out_message("Enter a C-Type ID from the list below")
self.out_message("to view a report of all the admin edit pages of that type.")
content_types = ContentType.objects.filter(
app_label__in=self.get_apps_for_report(self.apps_prefix, self.excluded_apps)
).order_by("model", "app_label")
if not content_types:
self.out_message_warning("No content types found.")
return
# Generate the index
content_type_pages = self.get_contenttype_for_pages(content_types)
content_type_snippets = self.get_contenttype_for_snippets(content_types)
content_type_modeladmin = self.get_contenttypes_for_modeladmin(content_types)
content_type_settings = self.get_contenttypes_for_settings(content_types)
all_content_types = {
**content_type_pages,
**content_type_snippets,
**content_type_modeladmin,
**content_type_settings,
}
if not all(all_content_types):
self.out_message_error("No content types found.")
return
self.out_table(content_type_pages, "Page")
self.out_table(content_type_snippets, "Snippet")
self.out_table(content_type_modeladmin, "ModelAdmin")
self.out_table(content_type_settings, "Settings")
# Get the index and generate the report
try:
index = int(input("\nC-Type ID: "))
except ValueError:
self.out_message_error("Value must be an integer.")
return
if index not in all_content_types:
self.out_message_error(f"Invalid C-Type ID: {index}")
return
self.out_edit_links(options, all_content_types[index])
def get_contenttypes_for_settings(self, content_types):
content_type_settings = dict()
settings_models = []
for model in settings_registry:
settings_models.append(
f"{model._meta.app_label}.{model._meta.model_name}".lower()
)
for content_type in content_types:
model_str = f"{content_type.app_label}.{content_type.model}"
if model_str in settings_models:
content_type_settings[content_type.id] = [
content_type.model_class().__name__,
content_type.app_label,
]
return content_type_settings
def get_contenttypes_for_modeladmin(self, content_types):
content_type_modeladmin = dict()
modeladmin_models = [model.lower() for model in self.registered_modeladmin]
for content_type in content_types:
model_str = f"{content_type.app_label}.{content_type.model}"
if model_str in modeladmin_models:
content_type_modeladmin[content_type.id] = [
content_type.model_class().__name__,
content_type.app_label,
]
return content_type_modeladmin
def get_contenttype_for_snippets(self, content_types):
content_type_snippets = dict()
snippet_models = [model.__name__.lower() for model in get_snippet_models()]
for content_type in content_types:
if content_type.model in snippet_models:
content_type_snippets[content_type.id] = [
content_type.model_class().__name__,
content_type.app_label,
]
return content_type_snippets
def get_contenttype_for_pages(self, content_types):
content_type_pages = dict()
for content_type in content_types:
if issubclass(content_type.model_class(), Page):
content_type_pages[content_type.id] = [
content_type.model_class().__name__,
content_type.app_label,
]
return content_type_pages
def out_table(self, data, model_type=None):
self.out_message_info(f"\nIndex of {model_type} Types")
headers = ["Model", "App", "C-Type ID"]
max_col_width = self.calc_col_width(data)
self.out_message("-" * max_col_width * len(headers))
self.out_message(
" ".join([f"{header: <{max_col_width}}" for header in headers])
)
self.out_message("-" * max_col_width * len(headers))
for key, row in data.items():
self.out_message(
" ".join([f"{col: <{max_col_width}}" for col in row]) + f" {key}"
)
self.out_message("-" * max_col_width * len(headers))
def calc_col_width(self, data):
max_col_width = 0
for row in data.values():
for col in row:
if len(col) > max_col_width:
max_col_width = len(col)
max_col_width += 2 # add some right padding
return max_col_width
def out_edit_links(self, options, data):
model = apps.get_model(data[1], data[0])
objects = model.objects.all()
self.out_message_success(f"\nEdit Links for {data[0]}")
self.out_message("-" * 70)
title_field = None
if hasattr(model, "title"):
title_field = "title"
elif hasattr(model, "name"):
title_field = "name"
for obj in objects:
if title_field:
t = getattr(obj, title_field)
title = t[:30] + "..." if len(t) > 30 else t
self.out_message(f"{title}")
self.out_message(f"{self.get_admin_edit_url(options, obj)}\n\n")
def out_message_warning(self, message):
self.stdout.write(self.style.WARNING(message))
def out_message(self, message):
self.stdout.write(message)
def out_message_error(self, message):
self.stdout.write(self.style.ERROR(message))
def out_message_info(self, message):
self.stdout.write(self.style.HTTP_INFO(message))
def out_message_success(self, message):
self.stdout.write(self.style.SUCCESS(message))
@staticmethod
def get_admin_edit_url(options, obj):
admin_url_finder = AdminURLFinder()
return f"{options['host']}{admin_url_finder.get_edit_url(obj)}"
@staticmethod
def get_apps_for_report(apps_prefix=None, excluded_apps=[]):
"""Return a list of apps we care about for the page types report."""
if not apps_prefix:
apps = [
app
for app in settings.INSTALLED_APPS
if not app.split(".")[0] in excluded_apps
]
return apps
return [
app.split(".")[1]
for app in settings.INSTALLED_APPS
if app.startswith(apps_prefix)
]