Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UAT Release #2367

Merged
merged 70 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
880a2c5
Stop PII pre-commit complaining about F680
currycoder Feb 5, 2025
762751c
Add initial general application details section for F680s
currycoder Feb 5, 2025
170c2bf
Ensure F680 general application details forms reflect designs
currycoder Feb 5, 2025
e38118f
Relocate common get_cleaned_data function
currycoder Feb 6, 2025
9fb4270
Set questions and answers in API application JSON payload
currycoder Feb 6, 2025
569f8b8
Make final general application details wizard step conditional
currycoder Feb 6, 2025
c9b6811
Make answers editable via initial data
currycoder Feb 6, 2025
87874ce
Ensure GeneralApplicationDetailsView 404s when application not found
currycoder Feb 6, 2025
893e7cc
Add unit tests for GeneralApplicationDetailsView
currycoder Feb 6, 2025
79defa5
Ensure exporter-located unit tests run in CI
currycoder Feb 7, 2025
72bc1be
Fix SCSS build
currycoder Feb 7, 2025
6656a76
Fix lint complaints
currycoder Feb 7, 2025
50731bb
Add tests for payload helpers
currycoder Feb 7, 2025
36eaeb8
Fix coverage complaint
currycoder Feb 7, 2025
bc0ec7d
Factor out common F680 wizard logic to parent class
currycoder Feb 7, 2025
a49ae14
add http status code to get and add extra checking in tests
markj0hnst0n Feb 5, 2025
7139af2
remove breakpoint
markj0hnst0n Feb 5, 2025
94cdb20
fix get application for summary view not found test
markj0hnst0n Feb 6, 2025
84b1443
add help text and approval type form first pass
markj0hnst0n Feb 7, 2025
97f3720
form update
markj0hnst0n Feb 7, 2025
669fd63
form init method added
markj0hnst0n Feb 7, 2025
fb3254b
remove breakpoints
markj0hnst0n Feb 7, 2025
2ae7fb7
working version with textareas
markj0hnst0n Feb 7, 2025
bd24725
simplify form
markj0hnst0n Feb 10, 2025
dad986f
simplify further
markj0hnst0n Feb 10, 2025
1496e51
Revert "simplify further"
markj0hnst0n Feb 10, 2025
3b825d5
textboxes working dynamically
markj0hnst0n Feb 10, 2025
084bcd7
add framework
markj0hnst0n Feb 10, 2025
ee492ac
Demonstrate adjusting BaseConditionalQuestion to accommodate optional…
currycoder Feb 10, 2025
9e5eec4
tidy
markj0hnst0n Feb 10, 2025
65b0008
add in approval details section in structure
markj0hnst0n Feb 11, 2025
ef5776c
test fix and remove unneeded
markj0hnst0n Feb 11, 2025
04fb04d
overwrite existing conditional checkbox class
markj0hnst0n Feb 11, 2025
335b981
remove unneeded
markj0hnst0n Feb 11, 2025
32c5593
add tests
markj0hnst0n Feb 12, 2025
deb1ac5
Merge branch 'dev' into LTD-5901
markj0hnst0n Feb 12, 2025
82a4723
fortmat
markj0hnst0n Feb 12, 2025
9040431
tidy
markj0hnst0n Feb 12, 2025
fc3d948
tidy
markj0hnst0n Feb 12, 2025
9a02e10
test changes
markj0hnst0n Feb 12, 2025
a751fbd
remove unused test
markj0hnst0n Feb 12, 2025
4810175
remove unused and change order
markj0hnst0n Feb 12, 2025
09191e0
add extra textbox field
markj0hnst0n Feb 12, 2025
43bd926
add help text
markj0hnst0n Feb 12, 2025
23c1272
remove unnecessary
markj0hnst0n Feb 12, 2025
a4c9bc8
add in form structure
markj0hnst0n Feb 12, 2025
d6e7e2d
hooked up to summary page
markj0hnst0n Feb 12, 2025
7abf7a8
display form field
markj0hnst0n Feb 12, 2025
e7d3047
add custom widget
markj0hnst0n Feb 12, 2025
5136b0d
simplify form to just use json field
markj0hnst0n Feb 12, 2025
19f72d5
remove unused template
markj0hnst0n Feb 12, 2025
de96450
add tests
markj0hnst0n Feb 12, 2025
822557b
rename testclass
markj0hnst0n Feb 12, 2025
c0c8cc9
content change, county or state -> county
Tllew Feb 13, 2025
335fc47
check all of form errors data in test
markj0hnst0n Feb 14, 2025
8325fb7
add label explicitly
markj0hnst0n Feb 14, 2025
4d8042e
TITLE_AS_LABEL_FOR added back in
markj0hnst0n Feb 14, 2025
4c1f779
test amendment
markj0hnst0n Feb 14, 2025
5170e69
add back in
markj0hnst0n Feb 14, 2025
401552e
Merge branch 'dev' into LTD-5901
markj0hnst0n Feb 17, 2025
806ef54
Merge branch 'dev' into LTD-5907
markj0hnst0n Feb 17, 2025
331fbd8
Merge pull request #2356 from uktrade/LTD-5907
markj0hnst0n Feb 17, 2025
3c6f9df
Merge branch 'dev' into LTD-5901
markj0hnst0n Feb 17, 2025
e11cc83
update urls
markj0hnst0n Feb 17, 2025
e603369
Merge pull request #2354 from uktrade/LTD-5901
markj0hnst0n Feb 17, 2025
fd45a06
change sort order default
depsiatwal Feb 17, 2025
7ec8464
Merge branch 'dev' into LTD-5852-county-optional
Tllew Feb 17, 2025
ac76a6d
Merge pull request #2366 from uktrade/LTD-5929-change-default-Q-sort-…
depsiatwal Feb 17, 2025
7a26fb3
Merge branch 'dev' into LTD-5852-county-optional
Tllew Feb 18, 2025
5323fbd
Merge pull request #2360 from uktrade/LTD-5852-county-optional
Tllew Feb 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions caseworker/queues/views/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,11 @@ def get_params(self):
if session_sort_by:
params["sort_by"] = session_sort_by
elif self.queue_pk == ALL_CASES_QUEUE_ID:
params["sort_by"] = "submitted_at"
else:
# newest to oldest
params["sort_by"] = "-submitted_at"
else:
# oldest to newest
params["sort_by"] = "submitted_at"

self.request.session["case_search_sort_by"] = params["sort_by"]

Expand Down
28 changes: 28 additions & 0 deletions core/forms/layouts.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,39 @@ class ConditionalCheckboxesQuestion(BaseConditionalQuestion):
template = "%s/layout/conditional_checkboxes_question.html"


class F680ConditionalCheckboxesQuestion(ConditionalCheckboxesQuestion):
def render(self, bound_field, form, form_style, context, template_pack=TEMPLATE_PACK, **kwargs):
template = self.get_template_name(template_pack)

mapped_choices = {choice[1]: choice for choice in bound_field.field.choices}
value = self.value
choice = mapped_choices[value]
position = list(mapped_choices.keys()).index(self.value)

conditional_content = ""
for field in self.fields:
if field in form.declared_fields:
conditional_content += render_field(
field, form, form_style, context, template_pack=template_pack, **kwargs
)

context.update(
{"choice": choice, "field": bound_field, "position": position, "conditional_content": conditional_content}
)

return render_to_string(template, context.flatten())


class ConditionalCheckboxes(BaseConditional):
question_class = ConditionalCheckboxesQuestion
template = "%s/layout/conditional_checkboxes.html"


class F680ConditionalCheckboxes(BaseConditional):
question_class = F680ConditionalCheckboxesQuestion
template = "%s/layout/conditional_checkboxes.html"


class ConditionalCheckbox(TemplateNameMixin):
template = "%s/layout/conditional_checkbox.html"

Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class FormSteps:
NOTES_FOR_CASEWORKER = "NOTES_FOR_CASEWORKER"
17 changes: 17 additions & 0 deletions exporter/f680/application_sections/additional_information/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django import forms

from core.common.forms import BaseForm


class NotesForCaseOfficerForm(BaseForm):
class Layout:
TITLE = "Notes"
SUBMIT_BUTTON_TEXT = "Save and continue"

note = forms.CharField(
label="Add note",
widget=forms.Textarea(attrs={"cols": "80"}),
)

def get_layout_fields(self):
return ("note",)
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import pytest

from django.urls import reverse

from core import client

from ..forms import NotesForCaseOfficerForm
from ..constants import FormSteps


@pytest.fixture()
def unset_f680_feature_flag(settings):
settings.FEATURE_FLAG_ALLOW_F680 = False


@pytest.fixture(autouse=True)
def setup(mock_exporter_user_me, settings):
settings.FEATURE_FLAG_ALLOW_F680 = True


@pytest.fixture
def missing_application_id():
return "6bb0828c-1520-4624-b729-7f3e6e5b9f5d"


@pytest.fixture
def missing_f680_application_wizard_url(missing_application_id):
return reverse(
"f680:additional_information:notes_wizard",
kwargs={"pk": missing_application_id},
)


@pytest.fixture
def f680_application_wizard_url(data_f680_case):
return reverse(
"f680:additional_information:notes_wizard",
kwargs={"pk": data_f680_case["id"]},
)


@pytest.fixture
def mock_f680_application_get_404(requests_mock, missing_application_id):
url = client._build_absolute_uri(f"/exporter/f680/application/{missing_application_id}/")
return requests_mock.get(url=url, json={}, status_code=404)


@pytest.fixture
def mock_f680_application_get(requests_mock, data_f680_case):
application_id = data_f680_case["id"]
url = client._build_absolute_uri(f"/exporter/f680/application/{application_id}/")
return requests_mock.get(url=url, json=data_f680_case)


@pytest.fixture
def mock_f680_application_get_existing_data(requests_mock, data_f680_case):
data_f680_case["application"] = {
"additional_information": {"answers": {"note": "Some note text"}, "questions": {"note": "Add note"}}
}
application_id = data_f680_case["id"]
url = client._build_absolute_uri(f"/exporter/f680/application/{application_id}/")
return requests_mock.get(url=url, json=data_f680_case)


@pytest.fixture
def mock_patch_f680_application(requests_mock, data_f680_case):
application_id = data_f680_case["id"]
url = client._build_absolute_uri(f"/exporter/f680/application/{application_id}/")
return requests_mock.patch(url=url, json=data_f680_case)


@pytest.fixture
def post_to_step(post_to_step_factory, f680_application_wizard_url):
return post_to_step_factory(f680_application_wizard_url)


@pytest.fixture
def goto_step(goto_step_factory, f680_application_wizard_url):
return goto_step_factory(f680_application_wizard_url)


class TestAdditionalInformationView:

def test_GET_no_application_404(
self,
authorized_client,
missing_f680_application_wizard_url,
mock_f680_application_get_404,
):
response = authorized_client.get(missing_f680_application_wizard_url)
assert response.status_code == 404

def test_GET_success(
self,
authorized_client,
mock_f680_application_get,
f680_application_wizard_url,
):
response = authorized_client.get(f680_application_wizard_url)
assert response.status_code == 200
assert isinstance(response.context["form"], NotesForCaseOfficerForm)

def test_GET_no_feature_flag_forbidden(
self,
authorized_client,
mock_f680_application_get,
f680_application_wizard_url,
unset_f680_feature_flag,
):
response = authorized_client.get(f680_application_wizard_url)
assert response.status_code == 200
assert response.context["title"] == "Forbidden"

def test_POST_approval_type_and_submit_wizard_success(
self, post_to_step, goto_step, mock_f680_application_get, mock_patch_f680_application
):
response = post_to_step(
FormSteps.NOTES_FOR_CASEWORKER,
{"note": "Some information"},
)
assert response.status_code == 302
assert mock_patch_f680_application.called_once
assert mock_patch_f680_application.last_request.json() == {
"application": {
"name": "F680 Test 1",
"additional_information": {"answers": {"note": "Some information"}, "questions": {"note": "Add note"}},
}
}

def test_POST_to_step_validation_error(
self,
post_to_step,
goto_step,
mock_f680_application_get,
):
goto_step(FormSteps.NOTES_FOR_CASEWORKER)
response = post_to_step(
FormSteps.NOTES_FOR_CASEWORKER,
{},
)
assert response.status_code == 200
assert response.context["form"]["note"].errors == ["This field is required."]

def test_GET_with_existing_data_success(
self,
authorized_client,
mock_f680_application_get_existing_data,
f680_application_wizard_url,
):
response = authorized_client.get(f680_application_wizard_url)
assert response.status_code == 200
assert isinstance(response.context["form"], NotesForCaseOfficerForm)
assert response.context["form"]["note"].initial == "Some note text"
10 changes: 10 additions & 0 deletions exporter/f680/application_sections/additional_information/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.urls import path

from . import views


app_name = "additional_information"

urlpatterns = [
path("notes/", views.NotesForCaseOfficersView.as_view(), name="notes_wizard"),
]
11 changes: 11 additions & 0 deletions exporter/f680/application_sections/additional_information/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from exporter.f680.application_sections.views import F680ApplicationSectionWizard

from .constants import FormSteps
from .forms import NotesForCaseOfficerForm


class NotesForCaseOfficersView(F680ApplicationSectionWizard):
form_list = [
(FormSteps.NOTES_FOR_CASEWORKER, NotesForCaseOfficerForm),
]
section = "additional_information"
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class FormSteps:
APPROVAL_TYPE = "APPROVAL_TYPE"
84 changes: 84 additions & 0 deletions exporter/f680/application_sections/approval_details/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from django import forms
from django.db.models import TextChoices
from django.template.loader import render_to_string

from crispy_forms_gds.layout.content import HTML

from core.common.forms import BaseForm, TextChoice
from core.forms.layouts import F680ConditionalCheckboxes, F680ConditionalCheckboxesQuestion


class ApprovalTypeForm(BaseForm):
class Layout:
TITLE = "Select the types of approvals you need"
TITLE_AS_LABEL_FOR = "approval_choices"
SUBMIT_BUTTON_TEXT = "Save and continue"

class ApprovalTypeChoices(TextChoices):
INITIAL_DISCUSSIONS_OR_PROMOTING = (
"initial_discussion_or_promoting",
"Initial discussions or promoting products",
)
DEMONSTRATION_IN_THE_UK = (
"demonstration_in_uk",
"Demonstration in the United Kingdom to overseas customers",
)
DEMONSTRATION_OVERSEAS = "demonstration_overseas", "Demonstration overseas"
TRAINING = "training", "Training"
THROUGH_LIFE_SUPPORT = "through_life_support", "Through life support"
SUPPLY = "supply", "Supply"

ApprovalTypeChoices = (
TextChoice(ApprovalTypeChoices.INITIAL_DISCUSSIONS_OR_PROMOTING),
TextChoice(ApprovalTypeChoices.DEMONSTRATION_IN_THE_UK),
TextChoice(ApprovalTypeChoices.DEMONSTRATION_OVERSEAS),
TextChoice(ApprovalTypeChoices.TRAINING),
TextChoice(ApprovalTypeChoices.THROUGH_LIFE_SUPPORT),
TextChoice(ApprovalTypeChoices.SUPPLY),
)

approval_choices = forms.MultipleChoiceField(
label=Layout.TITLE,
choices=(),
error_messages={
"required": "Select an approval choice",
},
widget=forms.CheckboxSelectMultiple(),
)

demonstration_in_uk = forms.CharField(
label="Explain what you are demonstrating and why",
help_text="Explain what materials will be involved and if you'll use a substitute product",
widget=forms.Textarea(attrs={"rows": 5}),
required=False,
)

demonstration_overseas = forms.CharField(
label="Explain what you are demonstrating and why",
help_text="Explain what materials will be involved and if you'll use a substitute product",
widget=forms.Textarea(attrs={"rows": 5}),
required=False,
)

approval_details_text = forms.CharField(
label="Provide details about what you're seeking approval to do",
widget=forms.Textarea(attrs={"rows": 5}),
required=False,
)

def __init__(self, *args, **kwargs):
self.conditional_checkbox_choices = (
F680ConditionalCheckboxesQuestion(choices.label, choices.value) for choices in self.ApprovalTypeChoices
)
super().__init__(*args, **kwargs)
self.fields["approval_choices"].choices = self.ApprovalTypeChoices

def get_layout_fields(self):
return (
F680ConditionalCheckboxes("approval_choices", *self.conditional_checkbox_choices),
"approval_details_text",
HTML.details(
"Help with exceptional circumstances",
render_to_string("f680/forms/help_with_approval_type.html"),
),
)
Empty file.
Loading