Skip to content

Commit

Permalink
Merge pull request #106 from i-dot-ai/feature/dummy-data-take-3
Browse files Browse the repository at this point in the history
Feature/dummy data take 3
  • Loading branch information
nmenezes0 authored Apr 19, 2024
2 parents 45bef7b + 548b4b9 commit 5a614cf
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 47 deletions.
31 changes: 21 additions & 10 deletions consultation_analyser/consultations/dummy_data.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import random

from consultation_analyser.factories import (
Expand All @@ -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 = [
Expand All @@ -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]
18 changes: 18 additions & 0 deletions consultation_analyser/consultations/jinja2/all-consultations.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% extends "base.html" %}
{%- from 'govuk_frontend_jinja/components/button/macro.html' import govukButton -%}

{% set page_title = "Consultations" %}

{% block content %}
<h1 class="govuk-heading-l">{{ page_title }}</h1>
<ul class="govuk-list">
{% for consultation in consultations %}
<li>
<a href="{{ url('consultation', kwargs={'consultation_slug': consultation.slug}) }}" class="govuk-body-l govuk-link govuk-link--no-visited-state">
{{ consultation.name }}
</a>
</li>
{% endfor %}
</ul>

{% endblock %}
3 changes: 2 additions & 1 deletion consultation_analyser/consultations/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<str:consultation_slug>/", consultations.show, name="consultation"),
path("schema/<str:schema_name>.json", schema.raw_schema),
path(
"consultations/<str:consultation_slug>/sections/<str:section_slug>/questions/<str:question_slug>/",
Expand Down
14 changes: 11 additions & 3 deletions consultation_analyser/consultations/views/consultations.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
from django.http import HttpRequest
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from waffle.decorators import waffle_switch

from .. import models


@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)
6 changes: 5 additions & 1 deletion consultation_analyser/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions consultation_analyser/hosting_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends "base.html" %}
{%- from 'govuk_frontend_jinja/components/button/macro.html' import govukButton -%}

{% set page_title = "All consultations" %}

{% block content %}
<h1 class="govuk-heading-l">{{ page_title }}</h1>
<form method="post" novalidate>{{ csrf_input }}
<ul class="govuk-list">
{% for consultation in consultations %}
<li>
<a href="{{ url('support_consultation', kwargs={'consultation_slug': consultation.slug}) }}" class="govuk-body govuk-link govuk-link--no-visited-state">
{{ consultation.name }}
</a>
</li>
{% endfor %}
</ul>

{% if development_env %}
{{ govukButton({
'text': "Generate dummy consultation",
'name': "generate_dummy_consultation"
}) }}
{% endif %}

</form>

{% endblock %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{% extends "base.html" %}
{%- from 'govuk_frontend_jinja/components/button/macro.html' import govukButton -%}

{% set page_title = "Consultation" %}

{% block content %}
<h1 class="govuk-heading-l">{{ page_title }}</h1>
<form method="post" novalidate>{{ csrf_input }}
<div>
<a href="{{ url('consultation', kwargs={'consultation_slug': consultation.slug}) }}" class="govuk-body govuk-link govuk-link--no-visited-state">
{{ consultation.name }}
</a>
</div>

<br>

{{ govukButton({
'text': "Generate themes",
'name': "generate_themes"
}) }}

</form>

{% endblock %}
2 changes: 2 additions & 0 deletions consultation_analyser/support_console/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@
urlpatterns = [
path("", views.support_home),
path("sign-out/", views.sign_out),
path("consultations/", views.show_consultations),
path("consultations/<str:consultation_slug>/", views.show_consultation, name="support_consultation"),
]
35 changes: 34 additions & 1 deletion consultation_analyser/support_console/views.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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)
3 changes: 2 additions & 1 deletion tests/integration/test_consultation_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 0 additions & 24 deletions tests/integration/test_logging_in_to_support.py

This file was deleted.

62 changes: 62 additions & 0 deletions tests/integration/test_support_console_pages.py
Original file line number Diff line number Diff line change
@@ -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 protected]", 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 protected]" # 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 protected]", password="admin", is_staff=True) # pragma: allowlist secret
login_page = django_app.get(page_url).follow()
login_page.form["username"] = "[email protected]"
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 protected]", password="admin", is_staff=True) # pragma: allowlist secret
login_page = django_app.get("/support/consultations/test-consultation/").follow()
login_page.form["username"] = "[email protected]"
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()
8 changes: 6 additions & 2 deletions tests/request/test_access_consultation_pages.py
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions tests/request/test_support_pages.py
Original file line number Diff line number Diff line change
@@ -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
10 changes: 6 additions & 4 deletions tests/unit/test_generate_dummy_data.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from unittest.mock import patch

import pytest
Expand All @@ -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()

0 comments on commit 5a614cf

Please sign in to comment.