Skip to content

Commit

Permalink
Add Taxonomy Topic Tagging (#104)
Browse files Browse the repository at this point in the history
* Add taxonomy app and topic tree node model

* Update sync command

* edit_handler added in and taxonomy tab tested in information page

* Topic model and links

* Fix the exclusive topic relationships

* Fix topic choosers

* Add generic taxonomy to all required pages, fix some bits

* Add topic deduplication

* Code tidy up and doc strings added

* Add sync command tests

* Testing taxonomy models

* Add TopicFactory and update other factories which now depend on it

* Remove taxonomy from release calendar page, improve factory

* Move sync topics command to taxonomy app

* Add start functional tests

* Refactor locations, use dummy root level topic

* Fix migration, use multi chooser

* Update functional tests

* get_queryset added to TopicChooserViewSet, taxonomy models and viewsets tests added.

* Add make target for loading topics fixture, small refactors and moves

* Combine migrations

* Pushing latest tests for taxonomy

* Debugging tests

* Move root topic creation to a migration, fix some bugs

* Address review comments

* Fix title with depth mistake and add more type hinting

* Fix title with depth mistake

* Make mypy happy, fix some issues

* Fix linting/mypy conflict

* Fix mistake, remove unecessary taxonomy panels fields

* Address review comments

* Address review comments, remove unecessary function

* Tests for forms, mixins, models and viewsets: Cleaned up, code formatted and complete.

* Fix move function issue

* Remove complete FIXME

* Update migrations

* Remove accidental unrelated migration

* Re-add article model migration

* Ignoring migration: 0002_articleseriespage_listing_image_and_more

* Add parents topics column to chooser

* Revert experimental chooser enhancements

* Bring typing hinting in line with our standards

* save_new_topic usage updated to make work as an instance method of Topic instead of getting it to work as a classmethod (save_new_topic is not a classmethod)

* sync_topics, _update_topic function refactored

* calling management commands using call_command and self.assertEqual being used replacing assert.

* All sync_topics.Command().handle() replaced with call_command("sync_topics")

---------

Co-authored-by: sanjeevz3009 <[email protected]>
  • Loading branch information
AdamHawtin and sanjeevz3009 authored Feb 26, 2025
1 parent b7740fc commit d0e1017
Show file tree
Hide file tree
Showing 45 changed files with 3,930 additions and 128 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ megalint: ## Run the mega-linter.
load-design-system-templates: ## Load the design system templates
./scripts/load-design-system-templates.sh $(DESIGN_SYSTEM_VERSION)

.PHONY: load-topics
load-topics: ## Load our fixture of taxonomy topics
poetry run python ./manage.py loaddata cms/taxonomy/fixtures/topics.json

# Docker and docker compose make commands

.PHONY: compose-build
Expand Down Expand Up @@ -162,7 +166,7 @@ runserver: ## Run the Django application locally
poetry run python ./manage.py runserver 0:8000

.PHONY: dev-init
dev-init: load-design-system-templates collectstatic makemigrations migrate createsuperuser ## Run the pre-run setup scripts
dev-init: load-design-system-templates collectstatic makemigrations migrate load-topics createsuperuser ## Run the pre-run setup scripts

.PHONY: functional-tests-up
functional-tests-up: ## Start the functional tests docker compose dependencies
Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ Follow these steps to set up and run the project using Docker.

2. **Migrations and Superuser Creation**

If this is your first time setting up the project, you’ll need to run migrations to set up the database schema and create an administrative user.
If this is your first time setting up the project, you’ll need to run migrations to set up the database schema, load the topics dev fixture, and create an
administrative user.

```bash
# ssh into the web container
Expand All @@ -116,6 +117,9 @@ Follow these steps to set up and run the project using Docker.
# Run database migrations
make migrate
# Load the topics dev fixture
make load-topics
# Create a superuser for accessing the admin interface
make createsuperuser
```
Expand Down Expand Up @@ -167,6 +171,7 @@ In order to run it:
- loading design system templates,
- collecting the static files,
- generating and applying database migrations,
- loading the topics dev fixture
- creating a superuser with:
- username: `admin`
- password: `changeme` # pragma: allowlist secret
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Generated by Django 5.1.6 on 2025-02-20 09:34

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("articles", "0001_initial"),
("images", "0004_alter_customimage__privacy"),
]

operations = [
migrations.AddField(
model_name="articleseriespage",
name="listing_image",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="images.customimage",
),
),
migrations.AddField(
model_name="articleseriespage",
name="listing_summary",
field=models.CharField(blank=True, max_length=255),
),
migrations.AddField(
model_name="articleseriespage",
name="listing_title",
field=models.CharField(blank=True, max_length=255),
),
migrations.AddField(
model_name="articleseriespage",
name="social_image",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to="images.customimage",
),
),
migrations.AddField(
model_name="articleseriespage",
name="social_text",
field=models.CharField(blank=True, max_length=255),
),
]
3 changes: 2 additions & 1 deletion cms/articles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from cms.core.blocks.stream_blocks import SectionStoryBlock
from cms.core.fields import StreamField
from cms.core.models import BasePage
from cms.taxonomy.mixins import GenericTaxonomyMixin

if TYPE_CHECKING:
from django.http import HttpRequest
Expand All @@ -26,7 +27,7 @@
from wagtail.admin.panels import Panel


class ArticleSeriesPage(RoutablePageMixin, Page):
class ArticleSeriesPage(RoutablePageMixin, GenericTaxonomyMixin, BasePage): # type: ignore[django-manager-missing]
"""The article series model."""

parent_page_types: ClassVar[list[str]] = ["topics.TopicPage"]
Expand Down
Empty file added cms/core/management/__init__.py
Empty file.
29 changes: 28 additions & 1 deletion cms/core/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from wagtail.admin.panels import ObjectList, TabbedInterface
from wagtail.models import Page
from wagtail.query import PageQuerySet
from wagtail.utils.decorators import cached_classmethod

from cms.core.cache import get_default_cache_control_decorator
from cms.core.query import order_by_pk_position
from cms.taxonomy.forms import DeduplicateTopicsAdminForm

from .mixins import ListingFieldsMixin, SocialFieldsMixin

Expand Down Expand Up @@ -36,7 +39,6 @@ class WagtailBaseGenericSetting(_WagtailBaseGenericSetting, models.Model):
BaseSiteSetting as WagtailBaseSiteSetting,
)


__all__ = ["BasePage", "BaseSiteSetting"]


Expand All @@ -45,6 +47,8 @@ class WagtailBaseGenericSetting(_WagtailBaseGenericSetting, models.Model):
class BasePage(ListingFieldsMixin, SocialFieldsMixin, Page): # type: ignore[django-manager-missing]
"""Base page class with listing and social fields additions as well as cache decorators."""

base_form_class = DeduplicateTopicsAdminForm

show_in_menus_default = True
# Used to check for the existence of equation and ONS embed blocks.
# Update in your specific Page class if the StreamField using them is different.
Expand All @@ -62,6 +66,29 @@ class Meta:
*SocialFieldsMixin.promote_panels,
]

@cached_classmethod
def get_edit_handler(cls) -> TabbedInterface: # pylint: disable=no-self-argument
"""Override the default edit handler property, enabling us to add editor tabs."""
if hasattr(cls, "edit_handler"):
edit_handler = cls.edit_handler
else:
# construct a TabbedInterface made up of content_panels, promote_panels
# and settings_panels, skipping any which are empty
tabs = []

if cls.content_panels:
tabs.append(ObjectList(cls.content_panels, heading="Content"))
if taxonomy_panels := getattr(cls, "taxonomy_panels", None):
tabs.append(ObjectList(taxonomy_panels, heading="Taxonomy"))
if cls.promote_panels:
tabs.append(ObjectList(cls.promote_panels, heading="Promote"))
if cls.settings_panels:
tabs.append(ObjectList(cls.settings_panels, heading="Settings"))

edit_handler = TabbedInterface(tabs, base_form_class=cls.base_form_class)

return edit_handler.bind_to_model(cls)

@cached_property
def related_pages(self) -> PageQuerySet:
"""Return a `PageQuerySet` of items related to this page via the
Expand Down
3 changes: 2 additions & 1 deletion cms/methodology/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from cms.core.fields import StreamField
from cms.core.models import BasePage
from cms.core.query import order_by_pk_position
from cms.taxonomy.mixins import GenericTaxonomyMixin

if TYPE_CHECKING:
import datetime
Expand All @@ -37,7 +38,7 @@ class MethodologyRelatedPage(Orderable):
panels: ClassVar[list[FieldPanel]] = [PageChooserPanel("page", page_type=["articles.StatisticalArticlePage"])]


class MethodologyPage(BasePage): # type: ignore[django-manager-missing]
class MethodologyPage(GenericTaxonomyMixin, BasePage): # type: ignore[django-manager-missing]
parent_page_types: ClassVar[list[str]] = ["topics.TopicPage"]
template = "templates/pages/methodology_page.html"
label = _("Methodology")
Expand Down
3 changes: 3 additions & 0 deletions cms/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"cms.standard_pages",
"cms.methodology",
"cms.navigation",
"cms.taxonomy",
"wagtail.embeds",
"wagtail.sites",
"wagtail.users",
Expand Down Expand Up @@ -846,3 +847,5 @@


SLACK_NOTIFICATIONS_WEBHOOK_URL = env.get("SLACK_NOTIFICATIONS_WEBHOOK_URL")

ONS_API_BASE_URL = env.get("ONS_API_BASE_URL")
9 changes: 8 additions & 1 deletion cms/settings/dev.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import copy
import os

from .base import * # noqa: F403 # pylint: disable=wildcard-import,unused-wildcard-import

env = os.environ.copy()

# Debugging to be enabled locally only
DEBUG = True

Expand All @@ -23,6 +26,9 @@
# Disable password validators when developing locally.
AUTH_PASSWORD_VALIDATORS = []

ONS_API_BASE_URL = env.get("ONS_API_BASE_URL", "https://api.beta.ons.gov.uk/v1")


# Enable Wagtail's style guide in Wagtail's settings menu.
# http://docs.wagtail.io/en/stable/contributing/styleguide.html
INSTALLED_APPS += ["wagtail.contrib.styleguide"] # noqa: F405
Expand Down Expand Up @@ -52,7 +58,7 @@
DATABASES["read_replica"] = copy.deepcopy(DATABASES["default"])

# Redis
REDIS_URL = env.get("REDIS_URL", "redis://localhost:6379") # noqa: F405
REDIS_URL = env.get("REDIS_URL", "redis://localhost:6379")
CACHES["default"] = { # noqa: F405
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": REDIS_URL,
Expand Down Expand Up @@ -90,5 +96,6 @@
"0002_customimage_description",
"0003_customimage__privacy_and_more",
"0003_customdocument__privacy_and_more",
"0002_articleseriespage_listing_image_and_more", # Ignoring NOT NULL constraint on columns
],
}
2 changes: 2 additions & 0 deletions cms/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@

# Silence Slack notifications by default
SLACK_NOTIFICATIONS_WEBHOOK_URL = None

ONS_API_BASE_URL = "dummy_url"
3 changes: 2 additions & 1 deletion cms/standard_pages/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
from cms.core.blocks.stream_blocks import CoreStoryBlock
from cms.core.fields import StreamField
from cms.core.models import BasePage
from cms.taxonomy.mixins import GenericTaxonomyMixin

if TYPE_CHECKING:
from django.http import HttpRequest
from wagtail.admin.panels import Panel


class InformationPage(BasePage): # type: ignore[django-manager-missing]
class InformationPage(GenericTaxonomyMixin, BasePage): # type: ignore[django-manager-missing]
"""A generic information page model."""

template = "templates/pages/information_page.html"
Expand Down
Empty file added cms/taxonomy/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions cms/taxonomy/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class TaxonomyConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "cms.taxonomy"
Loading

0 comments on commit d0e1017

Please sign in to comment.