diff options
-rw-r--r-- | core/templates/core/base.html | 55 | ||||
-rw-r--r-- | engagements/forms.py | 11 | ||||
-rw-r--r-- | engagements/templates/engagements/engagement_form.html | 5 | ||||
-rw-r--r-- | engagements/tests/conftest.py | 39 | ||||
-rw-r--r-- | engagements/tests/test_forms.py | 148 | ||||
-rw-r--r-- | engagements/views.py | 6 |
6 files changed, 198 insertions, 66 deletions
diff --git a/core/templates/core/base.html b/core/templates/core/base.html index 1bf6886..2a3f446 100644 --- a/core/templates/core/base.html +++ b/core/templates/core/base.html @@ -8,37 +8,42 @@ <link rel="stylesheet" href="{% static 'css/output.css' %}"> <link rel="preconnect" href="https://fonts.gstatic.com"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> - <script src="https://unpkg.com/htmx.org@1.9.0" integrity="sha384-aOxz9UdWG0yBiyrTwPeMibmaoq07/d3a96GCbb9x60f3mOt5zwkjdbcHFnKH8qls" crossorigin="anonymous"></script> + <script src="https://unpkg.com/htmx.org@1.9.0" + integrity="sha384-aOxz9UdWG0yBiyrTwPeMibmaoq07/d3a96GCbb9x60f3mOt5zwkjdbcHFnKH8qls" + crossorigin="anonymous"></script> <title>{% block title %}{% endblock title %}</title> {% block extra_head_tags %}{% endblock extra_head_tags %} </head> <body> {% block navbar %} -<nav class="bg-blue-600 p-4"> - <div class="container mx-auto flex items-center justify-between"> - <div class="flex items-center space-x-4"> - <a href="#" class="text-white text-xl font-semibold">Home</a> - <a href="{% url 'engagements:home' %}" class="text-white hover:text-blue-200">Engagement Plans</a> - <a href="{% url 'engagements:regulatedentities' %}" class="text-white hover:text-blue-200">Regulated Entities</a> - <a href="#" class="text-white hover:text-blue-200">Reporting</a> - <a href="#" class="text-white hover:text-blue-200">Dashboards</a> - </div> - <div class="flex items-center space-x-4"> - {% if request.user.is_authenticated %} - <span class="text-white">{{ user }}</span> - <a href="{% url 'logout' %}" class="text-white hover:text-blue-200">Log Out</a> - {% else %} - <a href="{% url 'login' %}" class="text-white hover:text-blue-200">Log In</a> - {% endif %} - <a href="#" class="text-white hover:text-blue-200">Help</a> - </div> - </div> -</nav> + <nav class="bg-blue-600 p-4"> + <div class="container mx-auto flex items-center justify-between"> + <div class="flex items-center space-x-4"> + <a href="#" class="text-white text-xl font-semibold">Home</a> + <a href="{% url 'engagements:home' %}" class="text-white hover:text-blue-200">Engagement Plans</a> + <a href="{% url 'engagements:regulatedentities' %}" class="text-white hover:text-blue-200">Regulated + Entities</a> + <a href="#" class="text-white hover:text-blue-200">Reporting</a> + <a href="#" class="text-white hover:text-blue-200">Dashboards</a> + </div> + <div class="flex items-center space-x-4"> + {% if request.user.is_authenticated %} + <span class="text-white">{{ user }}</span> + <a href="{% url 'logout' %}" class="text-white hover:text-blue-200">Log Out</a> + {% else %} + <a href="{% url 'login' %}" class="text-white hover:text-blue-200">Log In</a> + {% endif %} + <a href="#" class="text-white hover:text-blue-200">Help</a> + </div> + </div> + </nav> {% endblock navbar %} - <div class="container mx-auto"> - {% block content %} - {% endblock content %} - </div> +<div class="container mx-auto"> + {% block content %} + {% block messages %} + {% endblock messages %} + {% endblock content %} +</div> </body> </html> diff --git a/engagements/forms.py b/engagements/forms.py index 2532892..0b097df 100644 --- a/engagements/forms.py +++ b/engagements/forms.py @@ -1,5 +1,6 @@ from django import forms from django.forms.widgets import HiddenInput +from django.core.exceptions import ValidationError from .models import Engagement, EngagementEffort @@ -187,6 +188,16 @@ class EngagementCreateForm(forms.ModelForm): except KeyError: pass + def clean_proposed_end_date(self): + proposed_start_date = self.cleaned_data["proposed_start_date"] + proposed_end_date = self.cleaned_data["proposed_end_date"] + + if proposed_start_date and proposed_end_date: + if proposed_start_date > proposed_end_date: + raise ValidationError("The proposed start date must be before the proposed end date.") + + return proposed_start_date + class Meta: model = Engagement fields = [ diff --git a/engagements/templates/engagements/engagement_form.html b/engagements/templates/engagements/engagement_form.html index a0cdea2..89f6b0e 100644 --- a/engagements/templates/engagements/engagement_form.html +++ b/engagements/templates/engagements/engagement_form.html @@ -44,7 +44,7 @@ {% endfor %} </div> </div> - + <div> {{ form.proposed_start_date.label_tag }} <div class="my-2"> @@ -57,6 +57,9 @@ <div class="my-2"> {% render_field form.proposed_end_date class+="block w-full rounded-md border-0 p-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" %} </div> + {% for error in form.proposed_end_date.errors %} + <p class="text-red-500 text-sm">{{ error }}</p> + {% endfor %} </div> <div> diff --git a/engagements/tests/conftest.py b/engagements/tests/conftest.py new file mode 100644 index 0000000..cb0b9e0 --- /dev/null +++ b/engagements/tests/conftest.py @@ -0,0 +1,39 @@ +import pytest + +from engagements.models import EngagementType, Organisation, Engagement +from myuser.models import TeamUser + + +@pytest.fixture +def engagement(): + data = { + "proposed_start_date": "2022-10-01", + "engagement_type": EngagementType.objects.create(name="ET1"), + "external_party": Organisation.objects.create(name="O1"), + } + return Engagement.objects.create(**data) + + +@pytest.fixture +def user(): + return TeamUser.objects.create_user(email="ming@ming.com") + + +@pytest.fixture +def engagement_type(): + return EngagementType.objects.create(name="ET2") + + +@pytest.fixture +def external_party(): + return Organisation.objects.create(name="O2") + + +@pytest.fixture +def user1(): + return TeamUser.objects.create_user(email="user1@example.com") + + +@pytest.fixture +def user2(): + return TeamUser.objects.create_user(email="user2@example.com") diff --git a/engagements/tests/test_forms.py b/engagements/tests/test_forms.py index b6aab9a..3c213c5 100644 --- a/engagements/tests/test_forms.py +++ b/engagements/tests/test_forms.py @@ -1,45 +1,113 @@ -from django.test import TestCase +import pytest +from django.forms import DateInput, Select, SelectMultiple +from engagements.forms import EngagementCreateForm from engagements.forms import EngagementEffortCreateForm -from engagements.models import Engagement, EngagementType, Organisation -from myuser.models import TeamUser +from engagements.models import EngagementType +pytestmark = pytest.mark.django_db -class EngagementEffortCreate(TestCase): - def setUp(self): - data = { - "proposed_start_date": "2022-10-01", - "engagement_type": EngagementType.objects.create(name="ET1"), - "external_party": Organisation.objects.create(name="O1"), +def test_basic_validation(engagement, user): + form = EngagementEffortCreateForm( + data={ + "is_planned": True, + "proposed_start_date": "2022-10-10 10:00", + "proposed_end_date": "2022-10-10 12:00", + "engagement": engagement, + "effort_type": "PLANNING", + "officers": [user], } - self.e = Engagement.objects.create(**data) - self.user = TeamUser.objects.create_user(email="ming@ming.com") - - def test_basic_validation(self): - form = EngagementEffortCreateForm( - data={ - "is_planned": True, - "proposed_start_date": "2022-10-10 10:00", - "proposed_end_date": "2022-10-10 12:00", - "engagement": self.e, - "effort_type": "PLANNING", - "officers": [self.user], - } - ) - self.assertFalse(form.errors) - - def test_basic_validation_on_bad_entry(self): - form = EngagementEffortCreateForm( - data={ - "is_planned": True, - "proposed_start_date": "20240-10-10 10:00", - "proposed_end_date": "2022-10-10 12:00", - "engagement": self.e, - "effort_type": "bobbins", - "officers": [self.user], - "sub_instruments": [""], - } - ) - self.assertTrue(form.errors["effort_type"]) - self.assertTrue(form.errors["proposed_start_date"]) - self.assertTrue(form.errors["sub_instruments"]) + ) + assert not form.errors + + +def test_basic_validation_on_bad_entry(engagement, user): + form = EngagementEffortCreateForm( + data={ + "is_planned": True, + "proposed_start_date": "20240-10-10 10:00", + "proposed_end_date": "2022-10-10 12:00", + "engagement": engagement, + "effort_type": "bobbins", + "officers": [user], + "sub_instruments": [""], + } + ) + assert "effort_type" in form.errors + assert "proposed_start_date" in form.errors + + +def test_form_fields(): + form = EngagementCreateForm() + expected_fields = [ + "proposed_start_date", + "proposed_end_date", + "engagement_type", + "external_party", + "officers", + ] + assert list(form.fields.keys()) == expected_fields + + +def test_form_labels(): + form = EngagementCreateForm() + assert form.fields["officers"].label == "Inspectors" + + +def test_form_help_texts(): + form = EngagementCreateForm() + assert form.fields["proposed_start_date"].help_text == "<small><em>YYYY-MM-DD</em></small>" + assert form.fields["proposed_end_date"].help_text == "<small><em>YYYY-MM-DD</em></small>" + + +def test_form_widgets(): + form = EngagementCreateForm() + assert isinstance(form.fields["proposed_start_date"].widget, DateInput) + assert isinstance(form.fields["proposed_end_date"].widget, DateInput) + assert isinstance(form.fields["engagement_type"].widget, Select) + assert isinstance(form.fields["officers"].widget, SelectMultiple) + + +def test_form_valid_data(engagement_type, external_party, user1, user2): + form_data = { + "proposed_start_date": "2023-01-01", + "proposed_end_date": "2023-01-31", + "engagement_type": engagement_type.id, + "external_party": external_party.id, + "officers": [user1.id, user2.id], + } + form = EngagementCreateForm(data=form_data) + assert form.is_valid() + + +def test_form_invalid_dates(engagement_type, external_party, user1): + form_data = { + "proposed_start_date": "2023-01-31", + "proposed_end_date": "2023-01-01", + "engagement_type": engagement_type.id, + "external_party": external_party.id, + "officers": [user1.id], + } + form = EngagementCreateForm(data=form_data) + assert not form.is_valid() + + +def test_form_missing_required_fields(): + form_data = { + "proposed_start_date": "2023-01-01", + } + form = EngagementCreateForm(data=form_data) + assert not form.is_valid() + assert "engagement_type" in form.errors + assert "external_party" in form.errors + + +def test_form_engagement_type_queryset(): + initial_data = {"engagement_type": EngagementType.objects.all()} + form = EngagementCreateForm(initial=initial_data) + assert form.fields["engagement_type"].queryset == initial_data["engagement_type"] + + +def test_form_engagement_type_queryset_without_initial(): + form = EngagementCreateForm() + assert list(form.fields["engagement_type"].queryset) == list(EngagementType.objects.all()) diff --git a/engagements/views.py b/engagements/views.py index a76a37d..88f2014 100644 --- a/engagements/views.py +++ b/engagements/views.py @@ -155,6 +155,12 @@ def engagement_create(request, slug, reg=None): ef.external_party = Organisation.objects.get(slug=slug) ef.save() return redirect("engagements:plan_for_org", orgslug=slug) + else: + return render( + request, + "engagements/engagement_form.html", + {"form": form, "title": f"Create Engagement for {slug}", "errors": form.errors}, + ) else: if reg: form = EngagementCreateForm( |