Skip to content

Commit 952a4cb

Browse files
committed
Add support to show/hide a page based on user's ticket status
1 parent 892166b commit 952a4cb

File tree

4 files changed

+178
-5
lines changed

4 files changed

+178
-5
lines changed

backend/api/cms/page/queries/cms_page.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
from pretix import user_has_admission_ticket
12
from api.cms.utils import get_site_by_host
3+
from api.context import Context
24
from cms.components.page.models import GenericPage as GenericPageModel
35

46
import strawberry
@@ -8,6 +10,7 @@
810

911
@strawberry.field
1012
def cms_page(
13+
info: strawberry.Info[Context],
1114
hostname: str,
1215
slug: str,
1316
language: str,
@@ -22,6 +25,24 @@ def cms_page(
2225
if not page:
2326
return None
2427

28+
password_restriction = (
29+
page.get_view_restrictions().filter(restriction_type="password").first()
30+
)
31+
can_see_page = None
32+
33+
if password_restriction and password_restriction.password == "ticket":
34+
from conferences.models import Conference
35+
36+
# hack so we can go live with this feature for now :)
37+
conference = Conference.objects.get(code="pycon2025")
38+
39+
user = info.context.request.user
40+
can_see_page = user.is_authenticated and user_has_admission_ticket(
41+
email=user.email,
42+
event_organizer=conference.pretix_organizer_id,
43+
event_slug=conference.pretix_event_id,
44+
)
45+
2546
translated_page = (
2647
page.get_translations(inclusive=True)
2748
.filter(locale__language_code=language, live=True)
@@ -31,4 +52,4 @@ def cms_page(
3152
if not translated_page:
3253
return None
3354

34-
return GenericPage.from_model(translated_page)
55+
return GenericPage.from_model(translated_page, can_see_page=can_see_page)

backend/api/cms/page/types.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,28 @@ class GenericPage:
1919
body: list[get_block_union()] # type: ignore
2020

2121
@classmethod
22-
def from_model(cls, obj: GenericPageModel) -> Self:
22+
def from_model(
23+
cls, obj: GenericPageModel, *, can_see_page: bool | None = None
24+
) -> Self:
25+
match can_see_page:
26+
case None:
27+
# They can see it
28+
# and there are no restrictions
29+
# so show the whole page
30+
body = obj.body
31+
case True:
32+
# They can see the whole page
33+
# so skip the first block that is used to tell users to authenticate or similar
34+
body = obj.body[1:]
35+
case False:
36+
# Only show the first block
37+
# that is used to tell users to authenticate or similar
38+
body = [obj.body[0]]
39+
2340
return cls(
2441
id=obj.id,
2542
title=obj.seo_title or obj.title,
2643
search_description=obj.search_description,
2744
slug=obj.slug,
28-
body=[get_block(block.block_type).from_block(block) for block in obj.body],
45+
body=[get_block(block.block_type).from_block(block) for block in body],
2946
)

backend/api/cms/tests/page/queries/test_cms_page.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from decimal import Decimal
2+
from conferences.tests.factories import ConferenceFactory
23
import pytest
34
from api.cms.tests.factories import GenericPageFactory, SiteFactory
5+
from wagtail.models import PageViewRestriction
46

57
pytestmark = pytest.mark.django_db
68

@@ -68,6 +70,139 @@ def test_page(graphql_client, locale):
6870
}
6971

7072

73+
def test_page_with_ticket_restriction_and_ticket_returns_content(
74+
graphql_client, locale, user, mock_has_ticket
75+
):
76+
graphql_client.force_login(user)
77+
conference = ConferenceFactory(code="pycon2025")
78+
mock_has_ticket(conference)
79+
80+
parent = GenericPageFactory()
81+
page = GenericPageFactory(
82+
slug="bubble-tea",
83+
locale=locale("en"),
84+
parent=parent,
85+
title="Bubble",
86+
body__0__text_section__title__value="I've Got a Lovely Bunch of Coconuts",
87+
body__1__map__longitude=Decimal(3.14),
88+
body__2__homepage_hero__city="florence",
89+
body__3__homepage_hero__city=None,
90+
)
91+
page.save_revision().publish()
92+
PageViewRestriction.objects.create(
93+
page=page, restriction_type="password", password="ticket"
94+
)
95+
SiteFactory(hostname="pycon", port=80, root_page=parent)
96+
page.copy_for_translation(locale=locale("it"))
97+
query = """
98+
query Page ($hostname: String!, $language: String!, $slug: String!) {
99+
cmsPage(hostname: $hostname, language: $language, slug: $slug){
100+
...on GenericPage {
101+
title
102+
slug
103+
body {
104+
...on TextSection {
105+
title
106+
}
107+
...on CMSMap {
108+
latitude
109+
longitude
110+
}
111+
... on HomepageHero {
112+
city
113+
}
114+
}
115+
}
116+
}
117+
}
118+
"""
119+
120+
response = graphql_client.query(
121+
query, variables={"hostname": "pycon", "slug": "bubble-tea", "language": "en"}
122+
)
123+
124+
assert response["data"] == {
125+
"cmsPage": {
126+
"title": "Bubble",
127+
"slug": "bubble-tea",
128+
"body": [
129+
{
130+
"latitude": "43.766199999999997771737980656325817108154296875", # noqa: E501
131+
"longitude": "3.140000000000000124344978758017532527446746826171875", # noqa: E501
132+
},
133+
{
134+
"city": "FLORENCE",
135+
},
136+
{
137+
"city": None,
138+
},
139+
],
140+
}
141+
}
142+
143+
144+
def test_page_with_ticket_restriction_without_ticket_returns_first_block(
145+
graphql_client, locale, user, mock_has_ticket
146+
):
147+
graphql_client.force_login(user)
148+
conference = ConferenceFactory(code="pycon2025")
149+
mock_has_ticket(conference, False)
150+
151+
parent = GenericPageFactory()
152+
page = GenericPageFactory(
153+
slug="bubble-tea",
154+
locale=locale("en"),
155+
parent=parent,
156+
title="Bubble",
157+
body__0__text_section__title__value="I've Got a Lovely Bunch of Coconuts",
158+
body__1__map__longitude=Decimal(3.14),
159+
body__2__homepage_hero__city="florence",
160+
body__3__homepage_hero__city=None,
161+
)
162+
page.save_revision().publish()
163+
PageViewRestriction.objects.create(
164+
page=page, restriction_type="password", password="ticket"
165+
)
166+
SiteFactory(hostname="pycon", port=80, root_page=parent)
167+
page.copy_for_translation(locale=locale("it"))
168+
query = """
169+
query Page ($hostname: String!, $language: String!, $slug: String!) {
170+
cmsPage(hostname: $hostname, language: $language, slug: $slug){
171+
...on GenericPage {
172+
title
173+
slug
174+
body {
175+
...on TextSection {
176+
title
177+
}
178+
...on CMSMap {
179+
latitude
180+
longitude
181+
}
182+
... on HomepageHero {
183+
city
184+
}
185+
}
186+
}
187+
}
188+
}
189+
"""
190+
191+
response = graphql_client.query(
192+
query, variables={"hostname": "pycon", "slug": "bubble-tea", "language": "en"}
193+
)
194+
195+
assert response["data"] == {
196+
"cmsPage": {
197+
"title": "Bubble",
198+
"slug": "bubble-tea",
199+
"body": [
200+
{"title": "I've Got a Lovely Bunch of " "Coconuts"},
201+
],
202+
}
203+
}
204+
205+
71206
def test_page_returns_live_revision(graphql_client, locale):
72207
parent = GenericPageFactory()
73208
page = GenericPageFactory(

backend/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ def locale():
9191

9292
@pytest.fixture
9393
def mock_has_ticket(requests_mock, settings):
94-
def wrapper(conference):
94+
def wrapper(conference, has_ticket=True):
9595
requests_mock.post(
9696
f"{settings.PRETIX_API}organizers/{conference.pretix_organizer_id}/events/{conference.pretix_event_id}/tickets/attendee-has-ticket/",
97-
json={"user_has_admission_ticket": True},
97+
json={"user_has_admission_ticket": has_ticket},
9898
)
9999

100100
return wrapper

0 commit comments

Comments
 (0)