From ad41aa7258b9dd38fe79bb2fe50534125603aef5 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:05:40 +0000 Subject: [PATCH] Remove the old pages system Everything is driven by the CMS now, so the legacy `pages` app is gone: the Django app, its `PagesQuery`/`Page` GraphQL types, the demo fixture, and the `MenuLink.page` foreign key that was its only remaining consumer. The header query on the frontend still asked for `page { slug }`, but the value was never read, so it is dropped from the document too. `cms.0006_menu_menulink` no longer declares the `page` column (its `pages` dependency could not survive the app removal), and a new migration cleans up the leftover column and table on already migrated databases. Co-authored-by: Marco Acierno <3382153+marcoacierno@users.noreply.github.com> --- backend/api/cms/types.py | 2 - backend/api/pages/__init__.py | 0 backend/api/pages/schema.py | 24 --- backend/api/pages/types.py | 25 --- backend/api/schema.py | 2 - .../api/tests/schema/conference/test_menu.py | 18 +- backend/api/tests/schema/test_pages.py | 158 ------------------ backend/cms/migrations/0006_menu_menulink.py | 2 - .../migrations/0009_remove_menulink_page.py | 25 +++ backend/cms/models.py | 8 - backend/demodata/cms.json | 12 +- backend/demodata/pages.json | 16 -- backend/pages/__init__.py | 0 backend/pages/admin.py | 25 --- backend/pages/apps.py | 5 - backend/pages/migrations/0001_initial.py | 36 ---- .../migrations/0002_auto_20190914_1504.py | 33 ---- backend/pages/migrations/__init__.py | 0 backend/pages/models.py | 52 ------ backend/pages/tests/__init__.py | 0 backend/pages/tests/factories.py | 19 --- backend/pycon/settings/base.py | 1 - backend/schema.graphql | 12 -- frontend/src/components/header/header.graphql | 16 -- 24 files changed, 30 insertions(+), 461 deletions(-) delete mode 100644 backend/api/pages/__init__.py delete mode 100644 backend/api/pages/schema.py delete mode 100644 backend/api/pages/types.py delete mode 100644 backend/api/tests/schema/test_pages.py create mode 100644 backend/cms/migrations/0009_remove_menulink_page.py delete mode 100644 backend/demodata/pages.json delete mode 100644 backend/pages/__init__.py delete mode 100644 backend/pages/admin.py delete mode 100644 backend/pages/apps.py delete mode 100644 backend/pages/migrations/0001_initial.py delete mode 100644 backend/pages/migrations/0002_auto_20190914_1504.py delete mode 100644 backend/pages/migrations/__init__.py delete mode 100644 backend/pages/models.py delete mode 100644 backend/pages/tests/__init__.py delete mode 100644 backend/pages/tests/factories.py diff --git a/backend/api/cms/types.py b/backend/api/cms/types.py index aa3cd18d6d..e8f596f778 100644 --- a/backend/api/cms/types.py +++ b/backend/api/cms/types.py @@ -1,7 +1,6 @@ import strawberry import strawberry_django -from api.pages.types import Page from cms import models from ..helpers.i18n import make_localized_resolver @@ -23,7 +22,6 @@ class MenuLink: resolver=make_localized_resolver("title"), only=["title"] ) is_primary: strawberry.auto - page: Page | None @strawberry_django.type(models.Menu) diff --git a/backend/api/pages/__init__.py b/backend/api/pages/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/backend/api/pages/schema.py b/backend/api/pages/schema.py deleted file mode 100644 index 4a5810fb0c..0000000000 --- a/backend/api/pages/schema.py +++ /dev/null @@ -1,24 +0,0 @@ -from api.context import Info -import strawberry -import strawberry_django -from pages import models - -from .types import Page - - -@strawberry.type -class PagesQuery: - # TODO: use custom scalar for code and update custom gatsby source to use - # that instead of a generic argument called code - - @strawberry_django.field - def pages(self, info: Info, code: str) -> list[Page]: - return models.Page.published_pages.filter(conference__code=code) - - @strawberry_django.field - def page(self, info: Info, code: str, slug: str) -> Page | None: - return ( - models.Page.published_pages.by_slug(slug) - .filter(conference__code=code) - .first() - ) diff --git a/backend/api/pages/types.py b/backend/api/pages/types.py deleted file mode 100644 index f062869998..0000000000 --- a/backend/api/pages/types.py +++ /dev/null @@ -1,25 +0,0 @@ -import strawberry -import strawberry_django -from pages import models - -from ..helpers.i18n import make_localized_resolver -from ..helpers.images import resolve_image - - -@strawberry_django.type(models.Page) -class Page: - id: strawberry.auto - title: str = strawberry_django.field( - resolver=make_localized_resolver("title"), only=["title"] - ) - slug: str = strawberry_django.field( - resolver=make_localized_resolver("slug"), only=["slug"] - ) - content: str = strawberry_django.field( - resolver=make_localized_resolver("content"), only=["content"] - ) - excerpt: str | None = strawberry_django.field( - resolver=lambda root: getattr(root, "excerpt", None), - disable_optimization=True, - ) - image: str | None = strawberry_django.field(resolver=resolve_image, only=["image"]) diff --git a/backend/api/schema.py b/backend/api/schema.py index bd396cc711..3f4c39132a 100644 --- a/backend/api/schema.py +++ b/backend/api/schema.py @@ -12,7 +12,6 @@ from .newsletters.schema import NewsletterMutations from .orders.mutations import OrdersMutations from .orders.query import OrdersQuery -from .pages.schema import PagesQuery from .pretix.mutations import AttendeeTicketMutation from .schedule.schema import ScheduleQuery, ScheduleMutations from .submissions.mutations import SubmissionsMutations @@ -34,7 +33,6 @@ class Query( ConferenceQuery, SubmissionsQuery, - PagesQuery, CountryQuery, OrdersQuery, JobBoardQuery, diff --git a/backend/api/tests/schema/conference/test_menu.py b/backend/api/tests/schema/conference/test_menu.py index 57b3a94912..c3abb90536 100644 --- a/backend/api/tests/schema/conference/test_menu.py +++ b/backend/api/tests/schema/conference/test_menu.py @@ -2,7 +2,6 @@ from cms.tests.factories import MenuFactory, MenuLinkFactory from conferences.tests.factories import ConferenceFactory -from pages.tests.factories import PageFactory @mark.django_db @@ -68,10 +67,7 @@ def test_frontend_header_menus_query_is_constant( for identifier in ["conference-nav", "program-nav"]: menu = MenuFactory(identifier=identifier, conference=conference) for _ in range(link_count): - MenuLinkFactory( - menu=menu, - page=PageFactory(conference=conference), - ) + MenuLinkFactory(menu=menu) with django_assert_num_queries(9): resp = graphql_client.query( @@ -83,36 +79,24 @@ def test_frontend_header_menus_query_is_constant( links { text: title(language: "en") link: href(language: "en") - page { - slug(language: "en") - } } } programMenuEn: menu(identifier: "program-nav") { links { text: title(language: "en") link: href(language: "en") - page { - slug(language: "en") - } } } conferenceMenuIt: menu(identifier: "conference-nav") { links { text: title(language: "it") link: href(language: "it") - page { - slug(language: "it") - } } } programMenuIt: menu(identifier: "program-nav") { links { text: title(language: "it") link: href(language: "it") - page { - slug(language: "it") - } } } } diff --git a/backend/api/tests/schema/test_pages.py b/backend/api/tests/schema/test_pages.py deleted file mode 100644 index 3efdf9dd9c..0000000000 --- a/backend/api/tests/schema/test_pages.py +++ /dev/null @@ -1,158 +0,0 @@ -from conferences.tests.factories import ConferenceFactory -from pytest import mark - -from i18n.strings import LazyI18nString -from pages.tests.factories import PageFactory - - -def _query_pages(client, conference_code): - return client.query( - """query Pages($code: String!) { - pages(code: $code) { - id - title - slug - content - image - } - }""", - variables={"code": conference_code}, - ) - - -def _get_image_url(request, image): - if not image: - return None - - return request.build_absolute_uri(image.url) - - -@mark.django_db -def test_query_pages( - rf, - graphql_client, - django_assert_num_queries, -): - conference_1 = ConferenceFactory(code="pycon11") - conference_2 = ConferenceFactory(code="pycon10") - PageFactory(published=False, conference=conference_1) - PageFactory(published=True, conference=conference_2) - page = PageFactory(published=True, conference=conference_1) - - request = rf.get("/") - - with django_assert_num_queries(1): - resp = _query_pages(graphql_client, conference_code=conference_1.code) - - assert not resp.get("errors") - - assert len(resp["data"]["pages"]) == 1 - - assert { - "id": str(page.id), - "title": str(page.title), - "slug": str(page.slug), - "content": str(page.content), - "image": _get_image_url(request, page.image), - } == resp["data"]["pages"][0] - - -@mark.django_db -def test_query_single_page( - rf, - graphql_client, -): - request = rf.get("/") - page = PageFactory( - slug=LazyI18nString({"en": "demo"}), - published=True, - image=None, - conference__code="pycon11", - ) - - resp = graphql_client.query( - """query { - page(code: "pycon11", slug: "demo") { - id - title - slug - content - image - } - } """ - ) - - assert not resp.get("errors") - assert { - "id": str(page.id), - "title": str(page.title), - "slug": str(page.slug), - "content": str(page.content), - "image": _get_image_url(request, page.image), - } == resp["data"]["page"] - - resp = graphql_client.query( - """query { - page(slug: "demo", code: "pyconb") { - id - } - } """ - ) - - assert resp["data"]["page"] is None - - -@mark.django_db -def test_passing_language( - graphql_client, -): - PageFactory( - title=LazyI18nString({"en": "this is a test", "it": "questa è una prova"}), - slug=LazyI18nString({"en": "slug", "it": "lumaca"}), - content=LazyI18nString({"en": "content", "it": "contenuto"}), - published=True, - image=None, - conference__code="pycon11", - ) - - resp = graphql_client.query( - """query { - page(code: "pycon11", slug: "slug") { - title(language: "it") - slug(language: "it") - } - } """ - ) - - assert not resp.get("errors") - assert resp["data"]["page"] == {"title": "questa è una prova", "slug": "lumaca"} - - -@mark.django_db -@mark.skip(reason="disabled for now") -def test_defaults_on_browser_language( - graphql_client, -): - PageFactory( - title=LazyI18nString({"en": "this is a test", "it": "questa è una prova"}), - slug=LazyI18nString({"en": "slug", "it": "lumaca"}), - content=LazyI18nString({"en": "content", "it": "contenuto"}), - published=True, - image=None, - conference__code="pycon11", - ) - - headers = {"HTTP_ACCEPT_LANGUAGE": "it;q=0.8,de;q=0.7,la;q=0.6"} - - resp = graphql_client.query( - """query { - page(code: "pycon11", slug: "slug") { - title - slug - } - } """, - headers=headers, - ) - - assert not resp.get("errors") - assert resp["data"]["page"] == {"title": "questa è una prova", "slug": "lumaca"} diff --git a/backend/cms/migrations/0006_menu_menulink.py b/backend/cms/migrations/0006_menu_menulink.py index 0ec1e5d8e0..557217d82a 100644 --- a/backend/cms/migrations/0006_menu_menulink.py +++ b/backend/cms/migrations/0006_menu_menulink.py @@ -11,7 +11,6 @@ class Migration(migrations.Migration): dependencies = [ ('conferences', '0011_auto_20190921_2340'), - ('pages', '0002_auto_20190914_1504'), ('cms', '0005_faq'), ] @@ -39,7 +38,6 @@ class Migration(migrations.Migration): ('title', i18n.fields.I18nTextField(verbose_name='title')), ('href', i18n.fields.I18nTextField(blank=True, verbose_name='Link url')), ('menu', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='links', to='cms.Menu', verbose_name='menu')), - ('page', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='links', to='pages.Page', verbose_name='page')), ], options={ 'ordering': ('order',), diff --git a/backend/cms/migrations/0009_remove_menulink_page.py b/backend/cms/migrations/0009_remove_menulink_page.py new file mode 100644 index 0000000000..cdfd3890f2 --- /dev/null +++ b/backend/cms/migrations/0009_remove_menulink_page.py @@ -0,0 +1,25 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('cms', '0008_menu_title'), + ] + + # The old pages app has been removed: 0006_menu_menulink no longer creates + # the menulink.page column, so there is no state to change here, only the + # leftover database objects of already migrated databases to clean up. + operations = [ + migrations.RunSQL( + 'ALTER TABLE cms_menulink DROP COLUMN IF EXISTS page_id;', + migrations.RunSQL.noop, + ), + migrations.RunSQL( + 'DROP TABLE IF EXISTS pages_page;', + migrations.RunSQL.noop, + ), + migrations.RunSQL( + "DELETE FROM django_migrations WHERE app = 'pages';", + migrations.RunSQL.noop, + ), + ] diff --git a/backend/cms/models.py b/backend/cms/models.py index 860650f118..b2a1fb1396 100644 --- a/backend/cms/models.py +++ b/backend/cms/models.py @@ -67,14 +67,6 @@ class MenuLink(OrderedModel, TimeStampedModel): title = I18nTextField(_("title"), blank=False) href = I18nTextField(_("Link url"), blank=True) is_primary = models.BooleanField(_("Is primary"), default=False) - page = models.ForeignKey( - "pages.Page", - on_delete=models.CASCADE, - verbose_name=_("page"), - related_name="links", - null=True, - blank=True, - ) def __str__(self): return f"{self.title} ({self.menu})" diff --git a/backend/demodata/cms.json b/backend/demodata/cms.json index 1f47241243..73ab73c10e 100644 --- a/backend/demodata/cms.json +++ b/backend/demodata/cms.json @@ -191,8 +191,7 @@ "en": "/en/schedule", "it": "/en/programma" }, - "is_primary": false, - "page": null + "is_primary": false } }, { @@ -211,8 +210,7 @@ "en": "/en/register", "it": "/it/registrati" }, - "is_primary": true, - "page": null + "is_primary": true } }, { @@ -231,8 +229,7 @@ "en": "/en/schedule", "it": "/it/programma" }, - "is_primary": false, - "page": null + "is_primary": false } }, { @@ -251,8 +248,7 @@ "en": "/en/where", "it": "/it/dove" }, - "is_primary": false, - "page": null + "is_primary": false } } ] diff --git a/backend/demodata/pages.json b/backend/demodata/pages.json deleted file mode 100644 index 5e459e16fb..0000000000 --- a/backend/demodata/pages.json +++ /dev/null @@ -1,16 +0,0 @@ -[ - { - "model": "pages.page", - "pk": 1, - "fields": { - "created": "2019-08-28T21:18:06.212Z", - "modified": "2019-08-28T21:18:06.212Z", - "title": "{ \"en\": \"Help Us\", \"it\": \"Aiutaci\" }", - "slug": "{ \"en\": \"help-us\", \"it\": \"aiutaci\" }", - "content": "{ \"en\": \"# Demo\", \"it\": \"# Demo\" }", - "published": true, - "image": "", - "conference": 1 - } - } -] diff --git a/backend/pages/__init__.py b/backend/pages/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/backend/pages/admin.py b/backend/pages/admin.py deleted file mode 100644 index 9190a60d13..0000000000 --- a/backend/pages/admin.py +++ /dev/null @@ -1,25 +0,0 @@ -from django import forms -from django.contrib import admin - -from i18n.forms import I18nTextarea, I18nTextInput - -from .models import Page - - -class PageForm(forms.ModelForm): - class Meta: - model = Page - fields = "__all__" - widgets = { - "title": I18nTextInput, - "slug": I18nTextInput, - "content": I18nTextarea, - } - - -@admin.register(Page) -class PageAdmin(admin.ModelAdmin): - list_display = ("title", "published", "slug", "conference") - form = PageForm - - list_filter = ("conference",) diff --git a/backend/pages/apps.py b/backend/pages/apps.py deleted file mode 100644 index 344e0f0cf6..0000000000 --- a/backend/pages/apps.py +++ /dev/null @@ -1,5 +0,0 @@ -from django.apps import AppConfig - - -class PagesConfig(AppConfig): - name = "pages" diff --git a/backend/pages/migrations/0001_initial.py b/backend/pages/migrations/0001_initial.py deleted file mode 100644 index f00fa86ace..0000000000 --- a/backend/pages/migrations/0001_initial.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 2.2.4 on 2019-08-27 22:02 - -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import model_utils.fields - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('conferences', '0007_auto_20190811_1953'), - ] - - operations = [ - migrations.CreateModel( - name='Page', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')), - ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')), - ('title', models.CharField(max_length=200, verbose_name='title')), - ('slug', models.SlugField(blank=True, max_length=200, verbose_name='slug')), - ('content', models.TextField(verbose_name='content')), - ('published', models.BooleanField(default=False, verbose_name='published')), - ('image', models.ImageField(blank=True, null=True, upload_to='pages', verbose_name='image')), - ('conference', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pages', to='conferences.Conference', verbose_name='conference')), - ], - options={ - 'ordering': ['-published'], - 'unique_together': {('slug', 'conference')}, - }, - ), - ] diff --git a/backend/pages/migrations/0002_auto_20190914_1504.py b/backend/pages/migrations/0002_auto_20190914_1504.py deleted file mode 100644 index 13e233b099..0000000000 --- a/backend/pages/migrations/0002_auto_20190914_1504.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 2.2.5 on 2019-09-14 15:04 - -from django.db import migrations -import i18n.fields - - -class Migration(migrations.Migration): - - dependencies = [ - ('pages', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='page', - options={}, - ), - migrations.AlterField( - model_name='page', - name='content', - field=i18n.fields.I18nTextField(verbose_name='content'), - ), - migrations.AlterField( - model_name='page', - name='slug', - field=i18n.fields.I18nCharField(blank=True, max_length=200, verbose_name='slug'), - ), - migrations.AlterField( - model_name='page', - name='title', - field=i18n.fields.I18nCharField(max_length=200, verbose_name='title'), - ), - ] diff --git a/backend/pages/migrations/__init__.py b/backend/pages/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/backend/pages/models.py b/backend/pages/models.py deleted file mode 100644 index d479e8767e..0000000000 --- a/backend/pages/models.py +++ /dev/null @@ -1,52 +0,0 @@ -from copy import copy - -from django.conf import settings -from django.db import models -from django.db.models import Q -from django.utils.text import slugify -from django.utils.translation import gettext_lazy as _ -from i18n.fields import I18nCharField, I18nTextField -from model_utils.models import TimeStampedModel - - -class PageManager(models.Manager): - def get_queryset(self): - return super().get_queryset().filter(published=True) - - def by_slug(self, slug): - filters = Q() - - for lang, __ in settings.LANGUAGES: - filters |= Q(**{f"slug__{lang}": slug}) - - return self.get_queryset().filter(filters) - - -class Page(TimeStampedModel): - title = I18nCharField(_("title"), max_length=200) - slug = I18nCharField(_("slug"), max_length=200, blank=True) - content = I18nTextField(_("content"), blank=False) - published = models.BooleanField(_("published"), default=False) - image = models.ImageField(_("image"), null=True, blank=True, upload_to="pages") - conference = models.ForeignKey( - "conferences.Conference", - on_delete=models.CASCADE, - verbose_name=_("conference"), - related_name="pages", - ) - - objects = models.Manager() - published_pages = PageManager() - - def __str__(self): - return str(self.title) - - def save(self, *args, **kwargs): - if not self.slug: - self.slug = copy(self.title) - self.slug.map(slugify) - - super().save(*args, **kwargs) - - class Meta: - unique_together = ["slug", "conference"] diff --git a/backend/pages/tests/__init__.py b/backend/pages/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/backend/pages/tests/factories.py b/backend/pages/tests/factories.py deleted file mode 100644 index 35deed1e76..0000000000 --- a/backend/pages/tests/factories.py +++ /dev/null @@ -1,19 +0,0 @@ -import factory -import factory.fuzzy -from conferences.tests.factories import ConferenceFactory -from factory.django import DjangoModelFactory -from i18n.tests.factories import LanguageFactory -from pages.models import Page - - -class PageFactory(DjangoModelFactory): - conference = factory.SubFactory(ConferenceFactory) - factory.Faker - title = LanguageFactory("sentence") - slug = LanguageFactory("slug") - content = LanguageFactory("sentence") - published = True - image = factory.django.ImageField() - - class Meta: - model = Page diff --git a/backend/pycon/settings/base.py b/backend/pycon/settings/base.py index 35df050de1..a8ac815376 100644 --- a/backend/pycon/settings/base.py +++ b/backend/pycon/settings/base.py @@ -95,7 +95,6 @@ "languages.apps.LanguagesConfig", "submissions.apps.SubmissionsConfig", "voting.apps.VotingConfig", - "pages.apps.PagesConfig", "sponsors.apps.SponsorsConfig", "cms.apps.CMSConfig", "events.apps.EventsConfig", diff --git a/backend/schema.graphql b/backend/schema.graphql index 3c9ed32744..cf5e2b603a 100644 --- a/backend/schema.graphql +++ b/backend/schema.graphql @@ -722,7 +722,6 @@ type MenuLink { href(language: String = null): String! title(language: String = null): String! isPrimary: Boolean! - page: Page } input MultiLingualInput { @@ -846,15 +845,6 @@ type Option { name: String! } -type Page { - id: ID! - title(language: String = null): String! - slug(language: String = null): String! - content(language: String = null): String! - excerpt: String - image: String -} - type PageInfo { totalPages: Int! totalItems: Int! @@ -950,8 +940,6 @@ type Query { submissions(code: String!, languages: [String!] = null, voted: Boolean = null, tags: [String!] = null, types: [String!] = null, audienceLevels: [String!] = null, page: Int = 1, pageSize: Int = 50, onlyAccepted: Boolean! = false): SubmissionPaginated submissionTags: [SubmissionTag!]! votingTags(conference: String!): [SubmissionTag!]! - pages(code: String!): [Page!]! - page(code: String!, slug: String!): Page countries: [Country!]! country(code: String! = ""): Country! order(conferenceCode: String!, code: String!): PretixOrder diff --git a/frontend/src/components/header/header.graphql b/frontend/src/components/header/header.graphql index 6e4acc7097..17399d11ca 100644 --- a/frontend/src/components/header/header.graphql +++ b/frontend/src/components/header/header.graphql @@ -13,20 +13,12 @@ query Header($code: String!) { links { text: title(language: "en") link: href(language: "en") - - page { - slug(language: "en") - } } } programMenuEn: menu(identifier: "program-nav") { links { text: title(language: "en") link: href(language: "en") - - page { - slug(language: "en") - } } } @@ -34,20 +26,12 @@ query Header($code: String!) { links { text: title(language: "it") link: href(language: "it") - - page { - slug(language: "it") - } } } programMenuIt: menu(identifier: "program-nav") { links { text: title(language: "it") link: href(language: "it") - - page { - slug(language: "it") - } } } }