diff --git a/consultation_analyser/consultations/dummy_data.py b/consultation_analyser/consultations/dummy_data.py index 454f1f89..8c5bcb34 100644 --- a/consultation_analyser/consultations/dummy_data.py +++ b/consultation_analyser/consultations/dummy_data.py @@ -1,3 +1,4 @@ +import datetime import random from consultation_analyser.factories import ( @@ -13,10 +14,17 @@ class DummyConsultation: - def __init__(self, responses=10, **options): - if not HostingEnvironment.is_local(): + def __init__(self, responses=10, include_themes=True, **options): + if not HostingEnvironment.is_development_environment(): raise RuntimeError("Dummy data generation should only be run in development") + # Timestamp to avoid duplicates - set these as default options + timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S") + if "name" not in options: + options["name"] = f"Dummy consultation generated at {timestamp}" + if "slug" not in options: + options["slug"] = f"consultation-slug-{timestamp}" + consultation = ConsultationFactory(**options) section = SectionFactory(name="Base section", consultation=consultation) questions = [ @@ -31,12 +39,15 @@ def __init__(self, responses=10, **options): ] for r in range(responses): response = ConsultationResponseFactory(consultation=consultation) - _answers = [AnswerFactory(question=q, consultation_response=response) for q in questions] + if include_themes: + _answers = [AnswerFactory(question=q, consultation_response=response) for q in questions] - # Set themes per question, multiple answers with the same theme - for q in questions: - themes = [ThemeFactory() for _ in range(2, 6)] - for a in _answers: - random_theme = random.choice(themes) - a.theme = random_theme - a.save() + # Set themes per question, multiple answers with the same theme + for q in questions: + themes = [ThemeFactory() for _ in range(2, 6)] + for a in _answers: + random_theme = random.choice(themes) + a.theme = random_theme + a.save() + else: + _answers = [AnswerFactory(question=q, consultation_response=response, theme=None) for q in questions] diff --git a/consultation_analyser/consultations/jinja2/all-consultations.html b/consultation_analyser/consultations/jinja2/all-consultations.html new file mode 100644 index 00000000..f8b5405e --- /dev/null +++ b/consultation_analyser/consultations/jinja2/all-consultations.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{%- from 'govuk_frontend_jinja/components/button/macro.html' import govukButton -%} + +{% set page_title = "Consultations" %} + +{% block content %} +

{{ page_title }}

+ + +{% endblock %} diff --git a/consultation_analyser/consultations/urls.py b/consultation_analyser/consultations/urls.py index e5be7e17..25595b93 100644 --- a/consultation_analyser/consultations/urls.py +++ b/consultation_analyser/consultations/urls.py @@ -5,8 +5,9 @@ urlpatterns = [ path("", pages.home), path("privacy/", pages.privacy), - path("consultations/", consultations.show_questions), path("schema/", schema.show), + path("consultations/", consultations.show_all), + path("consultations//", consultations.show, name="consultation"), path("schema/.json", schema.raw_schema), path( "consultations//sections//questions//", diff --git a/consultation_analyser/consultations/views/consultations.py b/consultation_analyser/consultations/views/consultations.py index 0960beac..4a05a229 100644 --- a/consultation_analyser/consultations/views/consultations.py +++ b/consultation_analyser/consultations/views/consultations.py @@ -1,4 +1,4 @@ -from django.http import HttpRequest +from django.http import HttpRequest, HttpResponse from django.shortcuts import render from waffle.decorators import waffle_switch @@ -6,7 +6,15 @@ @waffle_switch("CONSULTATION_PROCESSING") -def show_questions(request: HttpRequest): - questions = models.Question.objects.all().order_by("id")[:10] +def show_all(request: HttpRequest) -> HttpResponse: + # TODO - in future, would restrict to all consultations for user + consultations = models.Consultation.objects.all() + context = {"consultations": consultations} + return render(request, "all-consultations.html", context) + + +@waffle_switch("CONSULTATION_PROCESSING") +def show(request: HttpRequest, consultation_slug: str) -> HttpResponse: + questions = models.Question.objects.filter(section__consultation__slug=consultation_slug) context = {"questions": questions} return render(request, "consultation.html", context) diff --git a/consultation_analyser/context_processors.py b/consultation_analyser/context_processors.py index fcae4400..8b12e3f1 100644 --- a/consultation_analyser/context_processors.py +++ b/consultation_analyser/context_processors.py @@ -16,10 +16,14 @@ def app_config(request: HttpRequest): name="Consultation analyser support console", path="/support/", menu_items=[ + { + "href": "/support/consultations/", + "text": "Consultations", + }, { "href": "/support/sign-out/", "text": "Sign out", - } + }, ], ) else: diff --git a/consultation_analyser/hosting_environment.py b/consultation_analyser/hosting_environment.py index e395067c..59286062 100644 --- a/consultation_analyser/hosting_environment.py +++ b/consultation_analyser/hosting_environment.py @@ -17,3 +17,9 @@ def is_deployed() -> bool: environment = env.str("ENVIRONMENT", "").upper() deployed_envs = ["DEV", "DEVELOPMENT", "PREPROD", "PROD", "PRODUCTION"] return environment in deployed_envs + + @staticmethod + def is_development_environment() -> bool: + environment = env.str("ENVIRONMENT", "").upper() + development_environments = ["LOCAL", "TEST", "DEV", "DEVELOPMENT"] + return environment in development_environments diff --git a/consultation_analyser/support_console/jinja2/support_console/all-consultations.html b/consultation_analyser/support_console/jinja2/support_console/all-consultations.html new file mode 100644 index 00000000..c3fd36a5 --- /dev/null +++ b/consultation_analyser/support_console/jinja2/support_console/all-consultations.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{%- from 'govuk_frontend_jinja/components/button/macro.html' import govukButton -%} + +{% set page_title = "All consultations" %} + +{% block content %} +

{{ page_title }}

+
{{ csrf_input }} + + + {% if development_env %} + {{ govukButton({ + 'text': "Generate dummy consultation", + 'name': "generate_dummy_consultation" + }) }} + {% endif %} + +
+ +{% endblock %} diff --git a/consultation_analyser/support_console/jinja2/support_console/consultation.html b/consultation_analyser/support_console/jinja2/support_console/consultation.html new file mode 100644 index 00000000..41e9fd59 --- /dev/null +++ b/consultation_analyser/support_console/jinja2/support_console/consultation.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{%- from 'govuk_frontend_jinja/components/button/macro.html' import govukButton -%} + +{% set page_title = "Consultation" %} + +{% block content %} +

{{ page_title }}

+
{{ csrf_input }} + + +
+ + {{ govukButton({ + 'text': "Generate themes", + 'name': "generate_themes" + }) }} + +
+ +{% endblock %} diff --git a/consultation_analyser/support_console/urls.py b/consultation_analyser/support_console/urls.py index 0e0ad853..a29a434a 100644 --- a/consultation_analyser/support_console/urls.py +++ b/consultation_analyser/support_console/urls.py @@ -5,4 +5,6 @@ urlpatterns = [ path("", views.support_home), path("sign-out/", views.sign_out), + path("consultations/", views.show_consultations), + path("consultations//", views.show_consultation, name="support_consultation"), ] diff --git a/consultation_analyser/support_console/views.py b/consultation_analyser/support_console/views.py index 714fca7e..4344505e 100644 --- a/consultation_analyser/support_console/views.py +++ b/consultation_analyser/support_console/views.py @@ -1,8 +1,13 @@ +from django.contrib import messages from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth import logout -from django.http import HttpRequest +from django.contrib.messages import get_messages +from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render +from consultation_analyser.consultations import dummy_data, ml_pipeline, models +from consultation_analyser.hosting_environment import HostingEnvironment + @staff_member_required def support_home(request: HttpRequest): @@ -13,3 +18,31 @@ def support_home(request: HttpRequest): def sign_out(request: HttpRequest): logout(request) return redirect("/") + + +@staff_member_required +def show_consultations(request: HttpRequest) -> HttpResponse: + # TODO - add messages + if request.POST: + try: + dummy_data.DummyConsultation(include_themes=False) + except RuntimeError as error: + pass + # TODO - pass through message from the error + consultations = models.Consultation.objects.all() + context = {"consultations": consultations, "development_env": HostingEnvironment.is_development_environment()} + return render(request, "support_console/all-consultations.html", context=context) + + +@staff_member_required +def show_consultation(request: HttpRequest, consultation_slug: str) -> HttpResponse: + consultation = models.Consultation.objects.get(slug=consultation_slug) + if request.POST: + themes_already_exist = models.Theme.objects.filter( + answer__question__section__consultation=consultation + ).exists() + if not themes_already_exist: + ml_pipeline.save_themes_for_consultation(consultation_id=consultation.id) + # TODO - pass through messages - "themes created" or "consultation already has themes" + context = {"consultation": consultation} + return render(request, "support_console/consultation.html", context=context) diff --git a/tests/integration/test_consultation_page.py b/tests/integration/test_consultation_page.py index e2d84374..ad2e37e5 100644 --- a/tests/integration/test_consultation_page.py +++ b/tests/integration/test_consultation_page.py @@ -8,8 +8,9 @@ @override_switch("CONSULTATION_PROCESSING", True) def test_consultation_page(django_app): consultation = ConsultationFactory(with_question=True) + consultation_slug = consultation.slug question = consultation.section_set.first().question_set.first() - homepage = django_app.get("/consultations/") + homepage = django_app.get(f"/consultations/{consultation_slug}/") question_page = homepage.click("Question summary") assert "Question summary" in question_page diff --git a/tests/integration/test_logging_in_to_support.py b/tests/integration/test_logging_in_to_support.py deleted file mode 100644 index 78569281..00000000 --- a/tests/integration/test_logging_in_to_support.py +++ /dev/null @@ -1,24 +0,0 @@ -import pytest - -from consultation_analyser.factories import UserFactory - - -@pytest.mark.django_db -def test_logging_in_to_support(django_app): - # given I am an admin user - UserFactory(email="email@example.com", password="admin", is_staff=True) # pragma: allowlist secret - - # when I visit support - login_page = django_app.get("/support").follow().follow() # 2 redirects - - # and I sign in to support - login_page.form["username"] = "email@example.com" # Django field is called "username" - login_page.form["password"] = "admin" # pragma: allowlist secret - support_home = login_page.form.submit().follow() - - # then I should see the support console page - assert "Consultation analyser support console" in support_home - - logged_out_page = support_home.click("Sign out") - - assert "Consultation analyser support console" not in logged_out_page diff --git a/tests/integration/test_support_console_pages.py b/tests/integration/test_support_console_pages.py new file mode 100644 index 00000000..2edde0a0 --- /dev/null +++ b/tests/integration/test_support_console_pages.py @@ -0,0 +1,62 @@ +import pytest + +from consultation_analyser.consultations.dummy_data import DummyConsultation +from consultation_analyser.consultations.models import Consultation, Theme +from consultation_analyser.factories import UserFactory + + +@pytest.mark.django_db +def test_logging_in_to_support(django_app): + # given I am an admin user + UserFactory(email="email@example.com", password="admin", is_staff=True) # pragma: allowlist secret + + # when I visit support + login_page = django_app.get("/support").follow().follow() # 2 redirects + + # and I sign in to support + login_page.form["username"] = "email@example.com" # Django field is called "username" + login_page.form["password"] = "admin" # pragma: allowlist secret + support_home = login_page.form.submit().follow() + + # then I should see the support console page + assert "Consultation analyser support console" in support_home + + logged_out_page = support_home.click("Sign out") + + assert "Consultation analyser support console" not in logged_out_page + + +@pytest.mark.django_db +def test_generating_dummy_data(django_app): + page_url = "/support/consultations/" + UserFactory(email="email@example.com", password="admin", is_staff=True) # pragma: allowlist secret + login_page = django_app.get(page_url).follow() + login_page.form["username"] = "email@example.com" + login_page.form["password"] = "admin" # pragma: allowlist secret + consultations_page = login_page.form.submit().follow() + assert "All consultations" in consultations_page + + # Check dummy data button does generate a new consultation + initial_count = Consultation.objects.all().count() + consultations_page = consultations_page.form.submit("generate_dummy_consultation") + count_after_dummy_data = Consultation.objects.all().count() + assert count_after_dummy_data > initial_count + + # Check redirected to individual consultation page in support + latest_consultation = Consultation.objects.all().order_by("created_at").last() + next_page = consultations_page.click(latest_consultation.name) + assert "Generate themes" in next_page + + +@pytest.mark.django_db +def test_generate_themes(django_app): + DummyConsultation(name="Test consultation", slug="test-consultation", include_themes=False) + UserFactory(email="email@example.com", password="admin", is_staff=True) # pragma: allowlist secret + login_page = django_app.get("/support/consultations/test-consultation/").follow() + login_page.form["username"] = "email@example.com" + login_page.form["password"] = "admin" # pragma: allowlist secret + consultation_page = login_page.form.submit().follow() + assert "Test consultation" in consultation_page + consultation_page = consultation_page.form.submit("generate_themes") + generated_themes = Theme.objects.filter(answer__question__section__consultation__slug="test-consultation") + assert generated_themes.exists() diff --git a/tests/request/test_access_consultation_pages.py b/tests/request/test_access_consultation_pages.py index 7e9f63f9..e9eebf80 100644 --- a/tests/request/test_access_consultation_pages.py +++ b/tests/request/test_access_consultation_pages.py @@ -1,16 +1,20 @@ import pytest from waffle.testutils import override_switch +from consultation_analyser.factories import ConsultationFactory + @pytest.mark.django_db @override_switch("CONSULTATION_PROCESSING", True) def test_accessing_when_flag_is_on(client): + consultation = ConsultationFactory() assert client.get("/").status_code == 200 - assert client.get("/consultations/").status_code == 200 + assert client.get(f"/consultations/{consultation.slug}/").status_code == 200 @pytest.mark.django_db @override_switch("CONSULTATION_PROCESSING", False) def test_accessing_when_flag_is_off(client): + consultation = ConsultationFactory() assert client.get("/").status_code == 200 - assert client.get("/consultations/").status_code == 404 + assert client.get(f"/consultations/{consultation.slug}/").status_code == 404 diff --git a/tests/request/test_support_pages.py b/tests/request/test_support_pages.py new file mode 100644 index 00000000..39e24ac9 --- /dev/null +++ b/tests/request/test_support_pages.py @@ -0,0 +1,19 @@ +import pytest + +from consultation_analyser.factories import ConsultationFactory + + +@pytest.mark.django_db +def test_no_login_support_pages(client): + ConsultationFactory(slug="consultation-slug") + support_urls = [ + "", + "sign-out/", + "consultations/", + "consultations/consultation-slug/", + ] + for url in support_urls: + full_url = f"/support/{url}" + response = client.get(full_url) + print(full_url) + assert response.status_code == 302 # No access, redirect to admin login diff --git a/tests/unit/test_generate_dummy_data.py b/tests/unit/test_generate_dummy_data.py index 6afad052..a143a991 100644 --- a/tests/unit/test_generate_dummy_data.py +++ b/tests/unit/test_generate_dummy_data.py @@ -1,3 +1,4 @@ +import os from unittest.mock import patch import pytest @@ -19,7 +20,8 @@ def test_a_consultation_is_generated(settings): @pytest.mark.django_db -@patch("consultation_analyser.hosting_environment.HostingEnvironment.is_local", return_value=False) -def test_the_tool_will_only_run_in_dev(settings): - with pytest.raises(Exception, match=r"should only be run in development"): - DummyConsultation() +@pytest.mark.parametrize("environment", ["preprod", "prod", "production"]) +def test_the_tool_will_only_run_in_dev(environment): + with patch.dict(os.environ, {"ENVIRONMENT": environment}): + with pytest.raises(Exception, match=r"should only be run in development"): + DummyConsultation()