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 }}
+
+
+{% 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 }}
+
+
+{% 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()