-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2354 from uktrade/LTD-5901
LTD-5901 Add approval type flow
- Loading branch information
Showing
12 changed files
with
376 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
2 changes: 2 additions & 0 deletions
2
exporter/f680/application_sections/approval_details/constants.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
84
exporter/f680/application_sections/approval_details/forms.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
193 changes: 193 additions & 0 deletions
193
exporter/f680/application_sections/approval_details/tests/test_views.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import pytest | ||
|
||
from django.urls import reverse | ||
|
||
from core import client | ||
|
||
from ..forms import ApprovalTypeForm | ||
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:approval_details:type_wizard", | ||
kwargs={"pk": missing_application_id}, | ||
) | ||
|
||
|
||
@pytest.fixture | ||
def f680_application_wizard_url(data_f680_case): | ||
return reverse( | ||
"f680:approval_details:type_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"] = { | ||
"approval_details": { | ||
"answers": { | ||
"approval_choices": [ | ||
"initial_discussion_or_promoting", | ||
"demonstration_in_uk", | ||
"demonstration_overseas", | ||
"training", | ||
"through_life_support", | ||
"supply", | ||
], | ||
"demonstration_in_uk": "Test text 1", | ||
"demonstration_overseas": "Test text 2", | ||
}, | ||
"questions": { | ||
"approval_choices": None, | ||
"demonstration_in_uk": "Explain what you are demonstrating and why", | ||
"demonstration_overseas": "Explain what you are demonstrating and why", | ||
}, | ||
}, | ||
} | ||
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 TestApprovalDetailsView: | ||
|
||
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"], ApprovalTypeForm) | ||
|
||
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.APPROVAL_TYPE, | ||
{"approval_choices": ["training", "supply"]}, | ||
) | ||
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", | ||
"approval_details": { | ||
"answers": { | ||
"approval_choices": ["training", "supply"], | ||
"demonstration_in_uk": "", | ||
"demonstration_overseas": "", | ||
"approval_details_text": "", | ||
}, | ||
"questions": { | ||
"approval_choices": "Select the types of approvals you need", | ||
"demonstration_in_uk": "Explain what you are demonstrating and why", | ||
"demonstration_overseas": "Explain what you are demonstrating and why", | ||
"approval_details_text": "Provide details about what you're seeking approval to do", | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
def test_POST_to_step_validation_error( | ||
self, | ||
post_to_step, | ||
goto_step, | ||
mock_f680_application_get, | ||
): | ||
goto_step(FormSteps.APPROVAL_TYPE) | ||
response = post_to_step( | ||
FormSteps.APPROVAL_TYPE, | ||
{}, | ||
) | ||
assert response.status_code == 200 | ||
assert "Select an approval choice" in response.context["form"]["approval_choices"].errors | ||
|
||
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"], ApprovalTypeForm) | ||
assert response.context["form"]["approval_choices"].initial == [ | ||
"initial_discussion_or_promoting", | ||
"demonstration_in_uk", | ||
"demonstration_overseas", | ||
"training", | ||
"through_life_support", | ||
"supply", | ||
] | ||
assert response.context["form"]["demonstration_in_uk"].initial == "Test text 1" | ||
assert response.context["form"]["demonstration_overseas"].initial == "Test text 2" |
10 changes: 10 additions & 0 deletions
10
exporter/f680/application_sections/approval_details/urls.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from django.urls import path | ||
|
||
from . import views | ||
|
||
|
||
app_name = "approval_details" | ||
|
||
urlpatterns = [ | ||
path("type/", views.ApprovalTypeView.as_view(), name="type_wizard"), | ||
] |
10 changes: 10 additions & 0 deletions
10
exporter/f680/application_sections/approval_details/views.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from exporter.f680.application_sections.views import F680ApplicationSectionWizard | ||
from .constants import FormSteps | ||
from .forms import ApprovalTypeForm | ||
|
||
|
||
class ApprovalTypeView(F680ApplicationSectionWizard): | ||
form_list = [ | ||
(FormSteps.APPROVAL_TYPE, ApprovalTypeForm), | ||
] | ||
section = "approval_details" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
class ApplicationFormSteps: | ||
APPLICATION_NAME = "APPLICATION_NAME" | ||
|
||
|
||
class ApprovalTypeSteps: | ||
APPROVAL_TYPE = "APPROVAL_TYPE" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
exporter/templates/f680/forms/help_with_approval_type.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<p class="govuk-body"> | ||
<b>Initial discussions or promoting the products</b><br> | ||
Talking about or promoting your products to potential customers, including offering enhanced through life support to an existing customer. This can be in the UK or overseas. It includes meetings hosted by government organisations. | ||
|
||
</p> | ||
<p class="govuk-body"> | ||
<b>Product demonstrations in the UK or overseas</b><br> | ||
Carrying out a 'live' activity to showcase your products. Trials and evaluations count as demonstrations. You can demonstrate the actual products or substitutes. You must include any substitutes as products in your application. The demonstration can take place anywhere, but you must include all entities and locations in your application. | ||
</p> | ||
<p class="govuk-body"> | ||
<b>Training</b><br> | ||
You do not need training approval to provide basic instructions for how to a use products. You do need training approval to teach operational employment of products, including tactics, techniques and procedures. Training can happen in the UK or overseas. | ||
</p> | ||
<p class="govuk-body"> | ||
<b>Through life support</b><br> | ||
This covers all aspect of an export programme. It includes exporting and delivering products to a customer. It also includes all support offered throughout the life of products, such as maintenance, enhancements, obsolescence management and disposal. You must have a valid F680 throughout the life of the export programme. | ||
</p> | ||
<p class="govuk-body"> | ||
<b>Supply</b><br> | ||
Content TBC needs to come from MOD as this is a new option. | ||
</p> |
Oops, something went wrong.