diff --git a/backend/api/cms/page/queries/cms_page.py b/backend/api/cms/page/queries/cms_page.py index 07299d835c..5ba1d1a57d 100644 --- a/backend/api/cms/page/queries/cms_page.py +++ b/backend/api/cms/page/queries/cms_page.py @@ -1,4 +1,6 @@ +from pretix import user_has_admission_ticket from api.cms.utils import get_site_by_host +from api.context import Context from cms.components.page.models import GenericPage as GenericPageModel import strawberry @@ -8,6 +10,7 @@ @strawberry.field def cms_page( + info: strawberry.Info[Context], hostname: str, slug: str, language: str, @@ -22,6 +25,24 @@ def cms_page( if not page: return None + password_restriction = ( + page.get_view_restrictions().filter(restriction_type="password").first() + ) + can_see_page = None + + if password_restriction and password_restriction.password == "ticket": + from conferences.models import Conference + + # hack so we can go live with this feature for now :) + conference = Conference.objects.get(code="pycon2025") + + user = info.context.request.user + can_see_page = user.is_authenticated and user_has_admission_ticket( + email=user.email, + event_organizer=conference.pretix_organizer_id, + event_slug=conference.pretix_event_id, + ) + translated_page = ( page.get_translations(inclusive=True) .filter(locale__language_code=language, live=True) @@ -31,4 +52,4 @@ def cms_page( if not translated_page: return None - return GenericPage.from_model(translated_page) + return GenericPage.from_model(translated_page, can_see_page=can_see_page) diff --git a/backend/api/cms/page/types.py b/backend/api/cms/page/types.py index 7e5dc228ae..ffa591f1db 100644 --- a/backend/api/cms/page/types.py +++ b/backend/api/cms/page/types.py @@ -19,11 +19,28 @@ class GenericPage: body: list[get_block_union()] # type: ignore @classmethod - def from_model(cls, obj: GenericPageModel) -> Self: + def from_model( + cls, obj: GenericPageModel, *, can_see_page: bool | None = None + ) -> Self: + match can_see_page: + case None: + # They can see it + # and there are no restrictions + # so show the whole page + body = obj.body + case True: + # They can see the whole page + # so skip the first block that is used to tell users to authenticate or similar + body = obj.body[1:] + case False: + # Only show the first block + # that is used to tell users to authenticate or similar + body = [obj.body[0]] + return cls( id=obj.id, title=obj.seo_title or obj.title, search_description=obj.search_description, slug=obj.slug, - body=[get_block(block.block_type).from_block(block) for block in obj.body], + body=[get_block(block.block_type).from_block(block) for block in body], ) diff --git a/backend/api/cms/tests/page/queries/test_cms_page.py b/backend/api/cms/tests/page/queries/test_cms_page.py index 32dd1bf36a..1b36822752 100644 --- a/backend/api/cms/tests/page/queries/test_cms_page.py +++ b/backend/api/cms/tests/page/queries/test_cms_page.py @@ -1,6 +1,8 @@ from decimal import Decimal +from conferences.tests.factories import ConferenceFactory import pytest from api.cms.tests.factories import GenericPageFactory, SiteFactory +from wagtail.models import PageViewRestriction pytestmark = pytest.mark.django_db @@ -68,6 +70,139 @@ def test_page(graphql_client, locale): } +def test_page_with_ticket_restriction_and_ticket_returns_content( + graphql_client, locale, user, mock_has_ticket +): + graphql_client.force_login(user) + conference = ConferenceFactory(code="pycon2025") + mock_has_ticket(conference) + + parent = GenericPageFactory() + page = GenericPageFactory( + slug="bubble-tea", + locale=locale("en"), + parent=parent, + title="Bubble", + body__0__text_section__title__value="I've Got a Lovely Bunch of Coconuts", + body__1__map__longitude=Decimal(3.14), + body__2__homepage_hero__city="florence", + body__3__homepage_hero__city=None, + ) + page.save_revision().publish() + PageViewRestriction.objects.create( + page=page, restriction_type="password", password="ticket" + ) + SiteFactory(hostname="pycon", port=80, root_page=parent) + page.copy_for_translation(locale=locale("it")) + query = """ + query Page ($hostname: String!, $language: String!, $slug: String!) { + cmsPage(hostname: $hostname, language: $language, slug: $slug){ + ...on GenericPage { + title + slug + body { + ...on TextSection { + title + } + ...on CMSMap { + latitude + longitude + } + ... on HomepageHero { + city + } + } + } + } + } + """ + + response = graphql_client.query( + query, variables={"hostname": "pycon", "slug": "bubble-tea", "language": "en"} + ) + + assert response["data"] == { + "cmsPage": { + "title": "Bubble", + "slug": "bubble-tea", + "body": [ + { + "latitude": "43.766199999999997771737980656325817108154296875", # noqa: E501 + "longitude": "3.140000000000000124344978758017532527446746826171875", # noqa: E501 + }, + { + "city": "FLORENCE", + }, + { + "city": None, + }, + ], + } + } + + +def test_page_with_ticket_restriction_without_ticket_returns_first_block( + graphql_client, locale, user, mock_has_ticket +): + graphql_client.force_login(user) + conference = ConferenceFactory(code="pycon2025") + mock_has_ticket(conference, False) + + parent = GenericPageFactory() + page = GenericPageFactory( + slug="bubble-tea", + locale=locale("en"), + parent=parent, + title="Bubble", + body__0__text_section__title__value="I've Got a Lovely Bunch of Coconuts", + body__1__map__longitude=Decimal(3.14), + body__2__homepage_hero__city="florence", + body__3__homepage_hero__city=None, + ) + page.save_revision().publish() + PageViewRestriction.objects.create( + page=page, restriction_type="password", password="ticket" + ) + SiteFactory(hostname="pycon", port=80, root_page=parent) + page.copy_for_translation(locale=locale("it")) + query = """ + query Page ($hostname: String!, $language: String!, $slug: String!) { + cmsPage(hostname: $hostname, language: $language, slug: $slug){ + ...on GenericPage { + title + slug + body { + ...on TextSection { + title + } + ...on CMSMap { + latitude + longitude + } + ... on HomepageHero { + city + } + } + } + } + } + """ + + response = graphql_client.query( + query, variables={"hostname": "pycon", "slug": "bubble-tea", "language": "en"} + ) + + assert response["data"] == { + "cmsPage": { + "title": "Bubble", + "slug": "bubble-tea", + "body": [ + {"title": "I've Got a Lovely Bunch of " "Coconuts"}, + ], + } + } + + def test_page_returns_live_revision(graphql_client, locale): parent = GenericPageFactory() page = GenericPageFactory( diff --git a/backend/conftest.py b/backend/conftest.py index 6c21c945de..525652533e 100644 --- a/backend/conftest.py +++ b/backend/conftest.py @@ -91,10 +91,10 @@ def locale(): @pytest.fixture def mock_has_ticket(requests_mock, settings): - def wrapper(conference): + def wrapper(conference, has_ticket=True): requests_mock.post( f"{settings.PRETIX_API}organizers/{conference.pretix_organizer_id}/events/{conference.pretix_event_id}/tickets/attendee-has-ticket/", - json={"user_has_admission_ticket": True}, + json={"user_has_admission_ticket": has_ticket}, ) return wrapper