diff --git a/backend/api/cms/news/queries/news_article.py b/backend/api/cms/news/queries/news_article.py index d244bb7c8e..33a00d5b2f 100644 --- a/backend/api/cms/news/queries/news_article.py +++ b/backend/api/cms/news/queries/news_article.py @@ -11,7 +11,7 @@ def news_article(hostname: str, slug: str, language: str) -> NewsArticle | None: if not site: raise ValueError(f"Site {hostname} not found") - article = NewsArticleModel.objects.in_site(site).filter(slug=slug).first() + article = NewsArticleModel.objects.in_site(site).filter(slug=slug, live=True).first() if not article: return None diff --git a/backend/api/cms/page/queries/cms_page.py b/backend/api/cms/page/queries/cms_page.py index aab4636990..373faf8b04 100644 --- a/backend/api/cms/page/queries/cms_page.py +++ b/backend/api/cms/page/queries/cms_page.py @@ -20,7 +20,7 @@ def cms_page( if not site: return SiteNotFoundError(message=f"Site `{hostname}` not found") - page = GenericPageModel.objects.in_site(site).filter(slug=slug).first() + page = GenericPageModel.objects.in_site(site).filter(slug=slug, live=True).first() if not page: return None diff --git a/backend/api/cms/tests/news/test_queries.py b/backend/api/cms/tests/news/test_queries.py index bf949d515b..120b743cbe 100644 --- a/backend/api/cms/tests/news/test_queries.py +++ b/backend/api/cms/tests/news/test_queries.py @@ -212,6 +212,53 @@ def test_cannot_get_draft_news_article( assert response["data"]["newsArticle"] is None +def test_cannot_get_draft_base_article_even_if_translation_is_live( + graphql_client, + locale, +): + """ + Test that a draft base article cannot be fetched even if a translation exists that is live. + This ensures the live=True filter is applied to the initial queryset. + """ + user = UserFactory(full_name="marco world") + parent = GenericPageFactory() + # Create a draft base article (live=False) + article = NewsArticleFactory( + title="Article 1", + parent=parent, + owner=user, + slug="slug", + first_published_at=None, + live=False, + ) + SiteFactory(hostname="pycon", port=80, root_page=parent) + + # Create a live Italian translation + it_article = article.copy_for_translation(locale=locale("it")) + it_article.title = "Article Italian" + it_article.save_revision().publish() + + query = """query NewsArticle( + $hostname: String!, + $slug: String!, + $language: String! + ) { + newsArticle(hostname: $hostname, slug: $slug, language: $language) { + id + title + authorFullname + } + }""" + + # Even though the Italian translation is live, the base article is draft + # so it should not be fetchable + response = graphql_client.query( + query, variables={"hostname": "pycon", "slug": article.slug, "language": "it"} + ) + + assert response["data"]["newsArticle"] is None + + def test_get_news_article_another_locale( graphql_client, locale, 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 986c616bc2..437e9db83e 100644 --- a/backend/api/cms/tests/page/queries/test_cms_page.py +++ b/backend/api/cms/tests/page/queries/test_cms_page.py @@ -304,6 +304,50 @@ def test_cannot_fetch_draft_pages(graphql_client, locale): assert response["data"]["cmsPage"] is None +def test_cannot_fetch_draft_base_page_even_if_translation_is_live(graphql_client, locale): + """ + Test that a draft base page cannot be fetched even if a translation exists that is live. + This ensures the live=True filter is applied to the initial queryset. + """ + parent = GenericPageFactory() + # Create a draft base page (live=False) + 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", + live=False, + ) + + SiteFactory(hostname="pycon", port=80, root_page=parent) + + # Create a live Italian translation + it_page = page.copy_for_translation(locale=locale("it")) + it_page.title = "Bubble Italian" + it_page.save_revision().publish() + + query = """ + query Page ($hostname: String!, $language: String!, $slug: String!) { + cmsPage(hostname: $hostname, language: $language, slug: $slug){ + ...on GenericPage { + title + slug + } + } + } + """ + + # Even though the Italian translation is live, the base page is draft + # so it should not be fetchable + response = graphql_client.query( + query, variables={"hostname": "pycon", "slug": "bubble-tea", "language": "it"} + ) + + assert not response.get("errors") + assert response["data"]["cmsPage"] is None + + def test_page_for_unknown_locale(graphql_client, locale): parent = GenericPageFactory() page = GenericPageFactory(