From e74f39cefedd24b0b58727ee5d0ec3f867b06f2b Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Mon, 6 Jan 2025 10:05:26 +0000 Subject: [PATCH 01/12] Add about you page --- app/__init__.py | 2 + app/means_test/__init__.py | 5 +++ app/means_test/forms.py | 18 +++++++++ app/means_test/routes.py | 9 +++++ app/means_test/widgets.py | 53 +++++++++++++++++++++++++ app/templates/base.html | 3 +- app/templates/means_test/about-you.html | 25 ++++++++++++ app/templates/means_test/form-page.html | 37 +++++++++++++++++ 8 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 app/means_test/__init__.py create mode 100644 app/means_test/forms.py create mode 100644 app/means_test/routes.py create mode 100644 app/means_test/widgets.py create mode 100644 app/templates/means_test/about-you.html create mode 100644 app/templates/means_test/form-page.html diff --git a/app/__init__.py b/app/__init__.py index 01aabdd89..fcac860a0 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -119,9 +119,11 @@ def create_app(config_class=Config): from app.main import bp as main_bp from app.categories import bp as categories_bp from app.find_a_legal_adviser import bp as fala_bp + from app.means_test import bp as means_test_bp app.register_blueprint(main_bp) app.register_blueprint(categories_bp) app.register_blueprint(fala_bp) + app.register_blueprint(means_test_bp) return app diff --git a/app/means_test/__init__.py b/app/means_test/__init__.py new file mode 100644 index 000000000..eb8140918 --- /dev/null +++ b/app/means_test/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint("means_test", __name__) + +from app.means_test import routes # noqa: E402,F401 diff --git a/app/means_test/forms.py b/app/means_test/forms.py new file mode 100644 index 000000000..d94ff8fa9 --- /dev/null +++ b/app/means_test/forms.py @@ -0,0 +1,18 @@ +from flask_wtf import FlaskForm +from govuk_frontend_wtf.wtforms_widgets import GovRadioInput +from wtforms.fields import RadioField + + +class AboutYouForm(FlaskForm): + partner = RadioField( + "Do you have a partner?", + choices=[("yes", "Yes"), ("no", "No")], + widget=GovRadioInput(), + description="Your husband, wife, civil partner (unless you have permanently separated) or someone you live with as if you’re married", + ) + + are_you_in_a_dispute = RadioField( + "Are you in a dispute with your partner?", + choices=[("yes", "Yes"), ("no", "No")], + widget=GovRadioInput(), + ) diff --git a/app/means_test/routes.py b/app/means_test/routes.py new file mode 100644 index 000000000..1a1dffd31 --- /dev/null +++ b/app/means_test/routes.py @@ -0,0 +1,9 @@ +from flask import render_template +from app.means_test import bp +from app.means_test.forms import AboutYouForm + + +@bp.get("/about-you") +def about_you(): + form = AboutYouForm() + return render_template("means_test/about-you.html", form=form) diff --git a/app/means_test/widgets.py b/app/means_test/widgets.py new file mode 100644 index 000000000..b1c2d880c --- /dev/null +++ b/app/means_test/widgets.py @@ -0,0 +1,53 @@ +from govuk_frontend_wtf.wtforms_widgets import GovRadioInput, GovCheckboxesInput + + +class MeansTestInputField: + """Adds additional functionality to the GovUK Input widgets, allowing these to be set on a form by form basis, + rather being required to be passed in to the widget when it is loaded via the template. + """ + + def __init__( + self, + heading_class: str = "govuk-fieldset__legend--m", + show_divider: bool = False, + is_inline: bool = True, + hint_text: str = None, + ): + super().__init__() + self.heading_class = heading_class + self.show_divider = show_divider + self.is_inline = is_inline + self.hint_text = hint_text + + def map_gov_params(self, field, **kwargs): + if self.hint_text: + kwargs["params"] = {"hint": {"text": self.hint_text}} + + params = super().map_gov_params(field, **kwargs) + if self.is_inline: + params["classes"] = "govuk-radios--inline" + + # Get or initialize the items list + items = params.get("items", []) + + # Add divider if enabled and there are enough items + if self.show_divider and len(items) >= 2: + items[-2]["divider"] = "or" + + # Handle pre-selected answer if present + if field.data: + for item in items: + if item.get("value") == field.data: + item["checked"] = True + + label_class = self.heading_class + params["fieldset"]["legend"]["classes"] = label_class + return params + + +class MeansTestRadioInput(MeansTestInputField, GovRadioInput): + pass + + +class MeansTestCheckboxInput(MeansTestInputField, GovCheckboxesInput): + pass diff --git a/app/templates/base.html b/app/templates/base.html index f67d6eaa8..96f7459a0 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -172,5 +172,6 @@ {% endblock %} {% block bodyEnd %} - + + {% endblock %} \ No newline at end of file diff --git a/app/templates/means_test/about-you.html b/app/templates/means_test/about-you.html new file mode 100644 index 000000000..597d2de65 --- /dev/null +++ b/app/templates/means_test/about-you.html @@ -0,0 +1,25 @@ +{% extends "means_test/form-page.html" %} + +{% set areYouInADisputeHtml %} + {{ form.are_you_in_a_dispute() }} +{% endset %} + + +{% block form %} + {{ form.csrf_token }} + + {{ form.partner(params={ + 'items': [ + { + 'value': 'yes', + 'conditional': { + 'html': areYouInADisputeHtml + } + }, + { + 'value': 'no' + }, + ] + }) }} + {{ form.submit }} +{% endblock %} \ No newline at end of file diff --git a/app/templates/means_test/form-page.html b/app/templates/means_test/form-page.html new file mode 100644 index 000000000..4782e7f20 --- /dev/null +++ b/app/templates/means_test/form-page.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} +{%- from 'govuk_frontend_jinja/components/back-link/macro.html' import govukBackLink -%} +{%- from 'govuk_frontend_jinja/components/exit-this-page/macro.html' import govukExitThisPage -%} + +{% block pageTitle %}{%- if form.errors %}Error: {% endif -%}{{ form.title }} - GOV.UK{% endblock %} + +{% block beforeContent%} +{{ super() }} +{{ govukBackLink({ + 'href': back_link, + 'text': "Back" + }) }} + +{% endblock %} + +{% block content %} + +{{ super() }} + +
+
+ + {% block formHeading %} +

{{ form.category }}

+ {% endblock %} + +
+ + {% block form %} } + {{ form.question() }} + {{ form.submit }} + {% endblock %} +
+ +
+
+{% endblock %} \ No newline at end of file From 0385d22471f10c98169183f70adbdaa502392a83 Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Mon, 6 Jan 2025 11:45:33 +0000 Subject: [PATCH 02/12] Support govuk-frontend on body --- app/templates/base.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/templates/base.html b/app/templates/base.html index 96f7459a0..437715e08 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -20,6 +20,8 @@ {% endblock %} +{% set bodyClasses = "govuk-frontend-supported" %} + {% block bodyStart %} {% if "cookies_policy" not in request.cookies %} {% set html %} @@ -172,6 +174,5 @@ {% endblock %} {% block bodyEnd %} - {% endblock %} \ No newline at end of file From 2a460187d27e48d7a5f7e4e3f9db36d67f461da4 Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Tue, 7 Jan 2025 12:48:58 +0000 Subject: [PATCH 03/12] Add do you receive benefits question --- app/means_test/forms.py | 9 +++++++++ app/templates/means_test/about-you.html | 2 ++ app/templates/means_test/form-page.html | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/means_test/forms.py b/app/means_test/forms.py index d94ff8fa9..a4d2385d8 100644 --- a/app/means_test/forms.py +++ b/app/means_test/forms.py @@ -4,6 +4,8 @@ class AboutYouForm(FlaskForm): + title = "About you" + partner = RadioField( "Do you have a partner?", choices=[("yes", "Yes"), ("no", "No")], @@ -16,3 +18,10 @@ class AboutYouForm(FlaskForm): choices=[("yes", "Yes"), ("no", "No")], widget=GovRadioInput(), ) + + do_you_receive_benefits = RadioField( + "Do you receive any benefits (including Child Benefit)?", + choices=[("yes", "Yes"), ("no", "No")], + widget=GovRadioInput(), + description="Being on some benefits can help you qualify for legal aid", + ) diff --git a/app/templates/means_test/about-you.html b/app/templates/means_test/about-you.html index 597d2de65..88b7119f1 100644 --- a/app/templates/means_test/about-you.html +++ b/app/templates/means_test/about-you.html @@ -21,5 +21,7 @@ }, ] }) }} + + {{ form.do_you_receive_benefits }} {{ form.submit }} {% endblock %} \ No newline at end of file diff --git a/app/templates/means_test/form-page.html b/app/templates/means_test/form-page.html index 4782e7f20..ae87e10f8 100644 --- a/app/templates/means_test/form-page.html +++ b/app/templates/means_test/form-page.html @@ -21,7 +21,7 @@
{% block formHeading %} -

{{ form.category }}

+

{{ form.title }}

{% endblock %}
From 5fbb9d9ebd3cee28911842b2a45a7939747a8588 Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Tue, 7 Jan 2025 16:00:13 +0000 Subject: [PATCH 04/12] Add the additional questions --- app/means_test/forms.py | 64 +++++++++++++++++++++++-- app/static/src/scss/radios.scss | 6 +++ app/static/src/scss/styles.scss | 1 + app/templates/means_test/about-you.html | 36 ++++++++++---- 4 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 app/static/src/scss/radios.scss diff --git a/app/means_test/forms.py b/app/means_test/forms.py index a4d2385d8..c0a6d5c98 100644 --- a/app/means_test/forms.py +++ b/app/means_test/forms.py @@ -1,7 +1,8 @@ from flask_wtf import FlaskForm -from govuk_frontend_wtf.wtforms_widgets import GovRadioInput from wtforms.fields import RadioField +from app.means_test.widgets import MeansTestRadioInput + class AboutYouForm(FlaskForm): title = "About you" @@ -9,19 +10,72 @@ class AboutYouForm(FlaskForm): partner = RadioField( "Do you have a partner?", choices=[("yes", "Yes"), ("no", "No")], - widget=GovRadioInput(), - description="Your husband, wife, civil partner (unless you have permanently separated) or someone you live with as if you’re married", + widget=MeansTestRadioInput(), + description="Your husband, wife, civil partner (unless you have permanently separated) or someone you live with as if you're married", ) are_you_in_a_dispute = RadioField( "Are you in a dispute with your partner?", choices=[("yes", "Yes"), ("no", "No")], - widget=GovRadioInput(), + widget=MeansTestRadioInput(), ) do_you_receive_benefits = RadioField( "Do you receive any benefits (including Child Benefit)?", choices=[("yes", "Yes"), ("no", "No")], - widget=GovRadioInput(), + widget=MeansTestRadioInput(), description="Being on some benefits can help you qualify for legal aid", ) + + children_under_16 = RadioField( + "Do you have any children aged 15 or under?", + choices=[("yes", "Yes"), ("no", "No")], + widget=MeansTestRadioInput(), + description="Don't include any children who don't live with you", + ) + + dependants_over_16 = RadioField( + "Do you have any dependants aged 16 or over?", + choices=[("yes", "Yes"), ("no", "No")], + widget=MeansTestRadioInput(), + description="People who you live with and support financially. This could be a young person for whom you get Child Benefit", + ) + + own_property = RadioField( + "Do you own any property?", + choices=[("yes", "Yes"), ("no", "No")], + widget=MeansTestRadioInput(), + description="For example, a house, static caravan or flat", + ) + + employed = RadioField( + "Are you employed?", + choices=[("yes", "Yes"), ("no", "No")], + widget=MeansTestRadioInput(), + description="This means working as an employee - you may be both employed and self-employed", + ) + + self_employed = RadioField( + "Are you self-employed?", + choices=[("yes", "Yes"), ("no", "No")], + widget=MeansTestRadioInput(), + description="This means working for yourself - you may be both employed and self-employed", + ) + + over_60 = RadioField( + "Are you or your partner (if you have one) aged 60 or over?", + choices=[("yes", "Yes"), ("no", "No")], + widget=MeansTestRadioInput(), + ) + + savings_or_investments = RadioField( + "Do you have any savings or investments?", + choices=[("yes", "Yes"), ("no", "No")], + widget=MeansTestRadioInput(), + ) + + valuable_items = RadioField( + "Do you have any valuable items worth over £500 each?", + choices=[("yes", "Yes"), ("no", "No")], + widget=MeansTestRadioInput(), + ) diff --git a/app/static/src/scss/radios.scss b/app/static/src/scss/radios.scss new file mode 100644 index 000000000..61297c396 --- /dev/null +++ b/app/static/src/scss/radios.scss @@ -0,0 +1,6 @@ +/* This is a fix to ensure conditional questions are shown under, rather than next to, the answer that causes them to display. */ +/* This is required to display the legacy means test pages correctly */ +.govuk-radios--inline .govuk-radios__conditional { + order: 9999; + width: 100%; +} \ No newline at end of file diff --git a/app/static/src/scss/styles.scss b/app/static/src/scss/styles.scss index 8cb8de681..737a9682b 100644 --- a/app/static/src/scss/styles.scss +++ b/app/static/src/scss/styles.scss @@ -5,3 +5,4 @@ /* Import your custom SCSS below to be compiled into one by Flask Assets */ @import "./custom"; @import "./exit_this_page"; +@import "./radios"; diff --git a/app/templates/means_test/about-you.html b/app/templates/means_test/about-you.html index 88b7119f1..9ebcdf519 100644 --- a/app/templates/means_test/about-you.html +++ b/app/templates/means_test/about-you.html @@ -4,24 +4,40 @@ {{ form.are_you_in_a_dispute() }} {% endset %} - {% block form %} {{ form.csrf_token }} {{ form.partner(params={ 'items': [ { - 'value': 'yes', - 'conditional': { - 'html': areYouInADisputeHtml - } - }, - { - 'value': 'no' - }, + 'value': 'yes', + 'conditional': { + 'html': areYouInADisputeHtml + } + }, + { + 'value': 'no' + }, ] }) }} - {{ form.do_you_receive_benefits }} + {{ form.do_you_receive_benefits() }} + + {{ form.children_under_16() }} + + {{ form.dependants_over_16() }} + + {{ form.own_property() }} + + {{ form.employed() }} + + {{ form.self_employed() }} + + {{ form.over_60() }} + + {{ form.savings_or_investments() }} + + {{ form.valuable_items() }} + {{ form.submit }} {% endblock %} \ No newline at end of file From 0e724b9e8b5dd5e5e20d82eba05ad41975594a14 Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Fri, 10 Jan 2025 13:09:14 +0000 Subject: [PATCH 05/12] Add means test link --- app/templates/categories/in-scope.html | 1 + 1 file changed, 1 insertion(+) diff --git a/app/templates/categories/in-scope.html b/app/templates/categories/in-scope.html index bb780b9ef..b97980232 100644 --- a/app/templates/categories/in-scope.html +++ b/app/templates/categories/in-scope.html @@ -56,6 +56,7 @@

{% trans %}If you use British Sign Language (BSL){% {{ govukButton(params={ "text": _("Check if you qualify financially"), + "href": url_for("means_test.about_you"), "isStartButton": "true", "classes": "govuk-!-margin-top-2 govuk-!-margin-bottom-8" }) }} From 5b14dcb510b2af6a91cbf762e69b53137912508e Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Fri, 10 Jan 2025 13:18:46 +0000 Subject: [PATCH 06/12] Add conditional integer questions --- app/means_test/forms.py | 8 ++- .../src/scss/{radios.scss => means-test.scss} | 0 app/static/src/scss/styles.scss | 2 +- app/templates/means_test/about-you.html | 50 ++++++++++++++++++- 4 files changed, 55 insertions(+), 5 deletions(-) rename app/static/src/scss/{radios.scss => means-test.scss} (100%) diff --git a/app/means_test/forms.py b/app/means_test/forms.py index c0a6d5c98..81777cf98 100644 --- a/app/means_test/forms.py +++ b/app/means_test/forms.py @@ -1,6 +1,6 @@ from flask_wtf import FlaskForm -from wtforms.fields import RadioField - +from wtforms.fields import RadioField, IntegerField +from govuk_frontend_wtf.wtforms_widgets import GovTextInput from app.means_test.widgets import MeansTestRadioInput @@ -34,6 +34,8 @@ class AboutYouForm(FlaskForm): description="Don't include any children who don't live with you", ) + how_many_children_under_16 = IntegerField("How many?", widget=GovTextInput()) + dependants_over_16 = RadioField( "Do you have any dependants aged 16 or over?", choices=[("yes", "Yes"), ("no", "No")], @@ -41,6 +43,8 @@ class AboutYouForm(FlaskForm): description="People who you live with and support financially. This could be a young person for whom you get Child Benefit", ) + how_many_dependents_over_16 = IntegerField("How many?", widget=GovTextInput()) + own_property = RadioField( "Do you own any property?", choices=[("yes", "Yes"), ("no", "No")], diff --git a/app/static/src/scss/radios.scss b/app/static/src/scss/means-test.scss similarity index 100% rename from app/static/src/scss/radios.scss rename to app/static/src/scss/means-test.scss diff --git a/app/static/src/scss/styles.scss b/app/static/src/scss/styles.scss index 014303282..4fc95d752 100644 --- a/app/static/src/scss/styles.scss +++ b/app/static/src/scss/styles.scss @@ -7,4 +7,4 @@ @import "./sidebar"; @import "./exit_this_page"; @import "./help-organisations"; -@import "./radios"; +@import "means-test"; diff --git a/app/templates/means_test/about-you.html b/app/templates/means_test/about-you.html index 9ebcdf519..0c69fbc8c 100644 --- a/app/templates/means_test/about-you.html +++ b/app/templates/means_test/about-you.html @@ -4,6 +4,28 @@ {{ form.are_you_in_a_dispute() }} {% endset %} +{% set howManyChildrenUnder16Html %} + {{ form.how_many_children_under_16(params={ + "label": { + "classes": "govuk-label--s" + }, + "classes": "govuk-input--width-4", + "inputmode": "numeric", + "spellcheck": false + }) }} +{% endset %} + +{% set howManyDependentsOver16Html %} + {{ form.how_many_dependents_over_16(params={ + "label": { + "classes": "govuk-label--s" + }, + "classes": "govuk-input--width-4", + "inputmode": "numeric", + "spellcheck": false + }) }} +{% endset %} + {% block form %} {{ form.csrf_token }} @@ -23,9 +45,33 @@ {{ form.do_you_receive_benefits() }} - {{ form.children_under_16() }} + {{ form.children_under_16(params={ + 'items': [ + { + 'value': 'yes', + 'conditional': { + 'html': howManyChildrenUnder16Html + } + }, + { + 'value': 'no' + }, + ] + }) }} - {{ form.dependants_over_16() }} + {{ form.dependants_over_16(params={ + 'items': [ + { + 'value': 'yes', + 'conditional': { + 'html': howManyDependentsOver16Html + } + }, + { + 'value': 'no' + }, + ] + }) }} {{ form.own_property() }} From 6694a61592774538f3710971bcf9ef59bd2c23e4 Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Fri, 10 Jan 2025 13:23:25 +0000 Subject: [PATCH 07/12] Safely load category from session --- app/session.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/session.py b/app/session.py index a4fb5ff3b..6374046c9 100644 --- a/app/session.py +++ b/app/session.py @@ -10,7 +10,10 @@ def category(self) -> Category | None: Returns: The category name if found, None otherwise """ - return Category(**self.get("category")) + category_dict = self.get("category") + if category_dict is None: + return None + return Category(**category_dict) @property def has_children(self): From 1822ace15e5080798318078cd9d1e59b97db3164 Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Fri, 10 Jan 2025 16:14:02 +0000 Subject: [PATCH 08/12] Add form validation --- app/means_test/forms.py | 81 +++++++++++++++++++++---- app/means_test/routes.py | 8 ++- app/means_test/validators.py | 21 +++++++ app/templates/means_test/about-you.html | 20 +++--- 4 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 app/means_test/validators.py diff --git a/app/means_test/forms.py b/app/means_test/forms.py index 81777cf98..443bd2058 100644 --- a/app/means_test/forms.py +++ b/app/means_test/forms.py @@ -1,7 +1,12 @@ from flask_wtf import FlaskForm from wtforms.fields import RadioField, IntegerField -from govuk_frontend_wtf.wtforms_widgets import GovTextInput +from govuk_frontend_wtf.wtforms_widgets import GovTextInput, GovSubmitInput +from wtforms.fields.simple import SubmitField +from wtforms.validators import InputRequired, NumberRange + +from app.means_test.validators import ValidateIf from app.means_test.widgets import MeansTestRadioInput +from flask_babel import gettext as _ class AboutYouForm(FlaskForm): @@ -12,74 +17,128 @@ class AboutYouForm(FlaskForm): choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), description="Your husband, wife, civil partner (unless you have permanently separated) or someone you live with as if you're married", + validators=[InputRequired(message=_("Tell us whether you have a partner"))], ) are_you_in_a_dispute = RadioField( "Are you in a dispute with your partner?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), + validators=[ + ValidateIf("partner", "yes"), + InputRequired( + message=_("Tell us whether you’re in dispute with your partner") + ), + ], ) - do_you_receive_benefits = RadioField( + on_benefits = RadioField( "Do you receive any benefits (including Child Benefit)?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), description="Being on some benefits can help you qualify for legal aid", + validators=[InputRequired(message=_("Tell us whether you receive benefits"))], ) - children_under_16 = RadioField( + have_children = RadioField( "Do you have any children aged 15 or under?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), description="Don't include any children who don't live with you", + validators=[ + InputRequired( + message=_("Tell us whether you have any children aged 15 or under") + ) + ], ) - how_many_children_under_16 = IntegerField("How many?", widget=GovTextInput()) + num_children = IntegerField( + "How many?", + widget=GovTextInput(), + validators=[ + ValidateIf("have_children", "yes"), + InputRequired( + message=_("Tell us how many children you have aged 15 or under") + ), + NumberRange(min=1, max=50, message=_("Enter a number between 1 and 50")), + ], + ) - dependants_over_16 = RadioField( + have_dependents = RadioField( "Do you have any dependants aged 16 or over?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), description="People who you live with and support financially. This could be a young person for whom you get Child Benefit", + validators=[ + InputRequired( + message=_("Tell us whether you have any dependants aged 16 or over") + ) + ], ) - how_many_dependents_over_16 = IntegerField("How many?", widget=GovTextInput()) + num_dependents = IntegerField( + "How many?", + widget=GovTextInput(), + validators=[ + ValidateIf("have_dependents", "yes"), + InputRequired(_("Tell us how many dependants you have aged 16 or over")), + NumberRange(min=1, max=50, message=_("Enter a number between 1 and 50")), + ], + ) own_property = RadioField( "Do you own any property?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), description="For example, a house, static caravan or flat", + validators=[InputRequired(message=_("Tell us if you own any properties"))], ) - employed = RadioField( + is_employed = RadioField( "Are you employed?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), description="This means working as an employee - you may be both employed and self-employed", + validators=[InputRequired(message=_("Tell us if you are employed"))], ) - self_employed = RadioField( + is_self_employed = RadioField( "Are you self-employed?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), description="This means working for yourself - you may be both employed and self-employed", + validators=[InputRequired(message=_("Tell us if you are self-employed"))], ) - over_60 = RadioField( + aged_60_or_over = RadioField( "Are you or your partner (if you have one) aged 60 or over?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), + validators=[ + InputRequired( + message=_("Tell us if you or your partner are aged 60 or over") + ) + ], ) - savings_or_investments = RadioField( + have_savings = RadioField( "Do you have any savings or investments?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), + validators=[ + InputRequired(message=_("Tell us whether you have savings or investments")) + ], ) - valuable_items = RadioField( + have_valuables = RadioField( "Do you have any valuable items worth over £500 each?", choices=[("yes", "Yes"), ("no", "No")], widget=MeansTestRadioInput(), + validators=[ + InputRequired( + message=_("Tell us if you have any valuable items worth over £500 each") + ) + ], ) + + submit = SubmitField(_("Continue"), widget=GovSubmitInput()) diff --git a/app/means_test/routes.py b/app/means_test/routes.py index 1a1dffd31..f110b9990 100644 --- a/app/means_test/routes.py +++ b/app/means_test/routes.py @@ -1,9 +1,13 @@ -from flask import render_template +from flask import render_template, redirect, url_for from app.means_test import bp from app.means_test.forms import AboutYouForm -@bp.get("/about-you") +@bp.route("/about-you", methods=["GET", "POST"]) def about_you(): form = AboutYouForm() + + if form.validate_on_submit(): + return redirect(url_for("means_test.about-your-income")) + return render_template("means_test/about-you.html", form=form) diff --git a/app/means_test/validators.py b/app/means_test/validators.py new file mode 100644 index 000000000..f3685c5f4 --- /dev/null +++ b/app/means_test/validators.py @@ -0,0 +1,21 @@ +from wtforms.validators import StopValidation + + +class ValidateIf: + def __init__( + self, + dependent_field_name: str, + dependent_field_value, + ): + self.dependent_field_name: str = dependent_field_name + self.dependent_field_value = dependent_field_value + + def __call__(self, form, field): + other_field = form._fields.get(self.dependent_field_name) + if other_field is None: + raise Exception('no field named "%s" in form' % self.dependent_field_name) + + # If the dependent field doesn't match the value, skip validation + if other_field.data != self.dependent_field_value: + field.errors = [] + raise StopValidation() diff --git a/app/templates/means_test/about-you.html b/app/templates/means_test/about-you.html index 0c69fbc8c..c046b02ed 100644 --- a/app/templates/means_test/about-you.html +++ b/app/templates/means_test/about-you.html @@ -5,7 +5,7 @@ {% endset %} {% set howManyChildrenUnder16Html %} - {{ form.how_many_children_under_16(params={ + {{ form.num_children(params={ "label": { "classes": "govuk-label--s" }, @@ -16,7 +16,7 @@ {% endset %} {% set howManyDependentsOver16Html %} - {{ form.how_many_dependents_over_16(params={ + {{ form.num_dependents(params={ "label": { "classes": "govuk-label--s" }, @@ -43,9 +43,9 @@ ] }) }} - {{ form.do_you_receive_benefits() }} + {{ form.on_benefits() }} - {{ form.children_under_16(params={ + {{ form.have_children(params={ 'items': [ { 'value': 'yes', @@ -59,7 +59,7 @@ ] }) }} - {{ form.dependants_over_16(params={ + {{ form.have_dependents(params={ 'items': [ { 'value': 'yes', @@ -75,15 +75,15 @@ {{ form.own_property() }} - {{ form.employed() }} + {{ form.is_employed() }} - {{ form.self_employed() }} + {{ form.is_self_employed() }} - {{ form.over_60() }} + {{ form.aged_60_or_over() }} - {{ form.savings_or_investments() }} + {{ form.have_savings() }} - {{ form.valuable_items() }} + {{ form.have_valuables() }} {{ form.submit }} {% endblock %} \ No newline at end of file From fb0c9a7272e57df8736f88565a22ee1a374aff5f Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Mon, 13 Jan 2025 15:34:36 +0000 Subject: [PATCH 09/12] Send About You payload to cla_backend --- app/api.py | 10 + app/means_test/api.py | 17 ++ app/means_test/forms.py | 320 ++++++++++++++++++++++-- app/means_test/routes.py | 6 +- app/static/src/js/custom.js | 38 ++- app/static/src/scss/means-test.scss | 2 +- app/templates/means_test/about-you.html | 21 +- run_local.sh | 8 +- 8 files changed, 388 insertions(+), 34 deletions(-) create mode 100644 app/means_test/api.py diff --git a/app/api.py b/app/api.py index afd5e891f..1f5850f1e 100644 --- a/app/api.py +++ b/app/api.py @@ -102,6 +102,16 @@ def post(self, endpoint: str, json: dict): """ return self._make_request(method="POST", endpoint=endpoint, json=json) + def patch(self, endpoint: str, json: dict): + """Make a PATCH request to CLA Backend. + Args: + endpoint (str): The endpoint to request + json (dict): The data to send to the backend + Returns: + dict: The JSON response from the backend + """ + return self._make_request(method="PATCH", endpoint=endpoint, json=json) + @cache.memoize(timeout=86400) # 1 day def get_help_organisations(self, category: str): """Get help organisations for a given category, each unique set of arguments return value is cached for 24 hours. diff --git a/app/means_test/api.py b/app/means_test/api.py new file mode 100644 index 000000000..acdc0a7a6 --- /dev/null +++ b/app/means_test/api.py @@ -0,0 +1,17 @@ +from app.api import cla_backend +from flask import session + + +def update_means_test(form_data): + means_test_endpoint = "checker/api/v1/eligibility_check/" + + ec_reference = session.get("reference") + + if ec_reference: + print(form_data) + form_data["reference"] = ec_reference + cla_backend.patch(f"{means_test_endpoint}/{ec_reference}", json=form_data) + else: + print(form_data) + response = cla_backend.post(means_test_endpoint, json=form_data) + session["reference"] = response["reference"] diff --git a/app/means_test/forms.py b/app/means_test/forms.py index 443bd2058..3f47d600c 100644 --- a/app/means_test/forms.py +++ b/app/means_test/forms.py @@ -8,13 +8,16 @@ from app.means_test.widgets import MeansTestRadioInput from flask_babel import gettext as _ +YES = "1" +NO = "0" + class AboutYouForm(FlaskForm): title = "About you" - partner = RadioField( + has_partner = RadioField( "Do you have a partner?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), description="Your husband, wife, civil partner (unless you have permanently separated) or someone you live with as if you're married", validators=[InputRequired(message=_("Tell us whether you have a partner"))], @@ -22,10 +25,10 @@ class AboutYouForm(FlaskForm): are_you_in_a_dispute = RadioField( "Are you in a dispute with your partner?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), validators=[ - ValidateIf("partner", "yes"), + ValidateIf("has_partner", YES), InputRequired( message=_("Tell us whether you’re in dispute with your partner") ), @@ -34,7 +37,7 @@ class AboutYouForm(FlaskForm): on_benefits = RadioField( "Do you receive any benefits (including Child Benefit)?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), description="Being on some benefits can help you qualify for legal aid", validators=[InputRequired(message=_("Tell us whether you receive benefits"))], @@ -42,7 +45,7 @@ class AboutYouForm(FlaskForm): have_children = RadioField( "Do you have any children aged 15 or under?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), description="Don't include any children who don't live with you", validators=[ @@ -56,7 +59,7 @@ class AboutYouForm(FlaskForm): "How many?", widget=GovTextInput(), validators=[ - ValidateIf("have_children", "yes"), + ValidateIf("have_children", YES), InputRequired( message=_("Tell us how many children you have aged 15 or under") ), @@ -66,7 +69,7 @@ class AboutYouForm(FlaskForm): have_dependents = RadioField( "Do you have any dependants aged 16 or over?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), description="People who you live with and support financially. This could be a young person for whom you get Child Benefit", validators=[ @@ -80,7 +83,7 @@ class AboutYouForm(FlaskForm): "How many?", widget=GovTextInput(), validators=[ - ValidateIf("have_dependents", "yes"), + ValidateIf("have_dependents", YES), InputRequired(_("Tell us how many dependants you have aged 16 or over")), NumberRange(min=1, max=50, message=_("Enter a number between 1 and 50")), ], @@ -88,7 +91,7 @@ class AboutYouForm(FlaskForm): own_property = RadioField( "Do you own any property?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), description="For example, a house, static caravan or flat", validators=[InputRequired(message=_("Tell us if you own any properties"))], @@ -96,23 +99,49 @@ class AboutYouForm(FlaskForm): is_employed = RadioField( "Are you employed?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), description="This means working as an employee - you may be both employed and self-employed", validators=[InputRequired(message=_("Tell us if you are employed"))], ) + partner_is_employed = RadioField( + _("Is your partner employed?"), + description=_( + "This means working as an employee - your partner may be both employed and self-employed" + ), + choices=[(YES, "Yes"), (NO, "No")], + validators=[ + ValidateIf("are_you_in_a_dispute", NO), + InputRequired(message=_("Tell us whether your partner is employed")), + ], + widget=MeansTestRadioInput(), + ) + is_self_employed = RadioField( "Are you self-employed?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), description="This means working for yourself - you may be both employed and self-employed", validators=[InputRequired(message=_("Tell us if you are self-employed"))], ) + partner_is_self_employed = RadioField( + _("Is your partner self-employed?"), + description=_( + "This means working for yourself - your partner may be both employed and self-employed" + ), + choices=[(YES, "Yes"), (NO, "No")], + validators=[ + ValidateIf("are_you_in_a_dispute", NO), + InputRequired(message=_("Tell us whether your partner is self-employed")), + ], + widget=MeansTestRadioInput(), + ) + aged_60_or_over = RadioField( "Are you or your partner (if you have one) aged 60 or over?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), validators=[ InputRequired( @@ -123,7 +152,7 @@ class AboutYouForm(FlaskForm): have_savings = RadioField( "Do you have any savings or investments?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), validators=[ InputRequired(message=_("Tell us whether you have savings or investments")) @@ -132,7 +161,7 @@ class AboutYouForm(FlaskForm): have_valuables = RadioField( "Do you have any valuable items worth over £500 each?", - choices=[("yes", "Yes"), ("no", "No")], + choices=[(YES, "Yes"), (NO, "No")], widget=MeansTestRadioInput(), validators=[ InputRequired( @@ -142,3 +171,264 @@ class AboutYouForm(FlaskForm): ) submit = SubmitField(_("Continue"), widget=GovSubmitInput()) + + def payload_2(self): + return { + "on_passported_benefits": "0", + "category": "u" "debt", + "is_you_or_your_partner_over_60": "u" "0", + "has_partner": "0", + "property_set": [], + "specific_benefits": {}, + "dependants_old": 0, + "you": { + "savings": { + "credit_balance": 0, + "investment_balance": 0, + "asset_balance": 0, + "bank_balance": 0, + }, + "deductions": { + "income_tax": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "mortgage": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "childcare": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "rent": { + "per_interval_value": 0, + "interval_period": "u" "per_week", + }, + "maintenance": { + "per_interval_value": 0, + "interval_period": "u" "per_week", + }, + "criminal_legalaid_contributions": 0, + "national_insurance": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + }, + "income": { + "self_employment_drawings": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "benefits": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "maintenance_received": { + "per_interval_value": 0, + "interval_period": "u" "per_week", + }, + "self_employed": "0", + "tax_credits": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "earnings": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "child_benefits": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "other_income": { + "per_interval_value": 0, + "interval_period": "u" "per_week", + }, + "pension": { + "per_interval_value": 0, + "interval_period": "u" "per_week", + }, + }, + }, + "dependants_young": 0, + "on_nass_benefits": "0", + } + + def payload(self): + payload = { + "has_partner": YES + if self.has_partner.data == YES and not self.are_you_in_a_dispute.data == NO + else NO, + "is_you_or_your_partner_over_60": "1", + "dependants_young": self.num_children.data + if self.have_children.data == YES + else 0, + "dependants_old": self.num_dependents.data + if self.have_dependents.data == YES + else 0, + "you": {"income": {"self_employed": self.is_self_employed.data}}, + # TEST DATA + "property_set": [], + "category": "debt", + "on_passported_benefits": "0", + "on_nass_benefits": "0", + "specific_benefits": {}, + } + + payload["you"] = { + "savings": { + "credit_balance": 0, + "investment_balance": 0, + "asset_balance": 0, + "bank_balance": 0, + }, + "deductions": { + "income_tax": {"per_interval_value": 0, "interval_period": "per_month"}, + "mortgage": {"per_interval_value": 0, "interval_period": "per_month"}, + "childcare": {"per_interval_value": 0, "interval_period": "per_month"}, + "rent": {"per_interval_value": 0, "interval_period": "per_week"}, + "maintenance": {"per_interval_value": 0, "interval_period": "per_week"}, + "criminal_legalaid_contributions": 0, + "national_insurance": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + }, + "income": { + "self_employment_drawings": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "benefits": {"per_interval_value": 0, "interval_period": "per_month"}, + "maintenance_received": { + "per_interval_value": 0, + "interval_period": "per_week", + }, + "self_employed": "0", + "tax_credits": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "earnings": {"per_interval_value": 0, "interval_period": "per_month"}, + "child_benefits": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "other_income": { + "per_interval_value": 0, + "interval_period": "per_month", + }, + "pension": {"per_interval_value": 0, "interval_period": "per_month"}, + "total": 0, + }, + } + + if payload["has_partner"] and self.partner_is_self_employed.data == YES: + payload["partner"] = { + "income": {"self_employed": self.partner_is_self_employed.data} + } + + if self.own_property.data: + # TODO: Get property data + pass + + if self.have_savings or self.have_valuables: + # TODO: Get savings data + pass + + if self.on_benefits.data: + # TODO: Get benefits data + pass + + # TODO: Get income and outgoing data + + return payload + + """ +{ + "on_passported_benefits":"0", + "category":"u""debt", + "is_you_or_your_partner_over_60":"u""0", + "has_partner":"0", + "property_set":[ + + ], + "specific_benefits":{ + + }, + "dependants_old":0, + "you":{ + "savings":{ + "credit_balance":0, + "investment_balance":0, + "asset_balance":0, + "bank_balance":0 + }, + "deductions":{ + "income_tax":{ + "per_interval_value":0, + "interval_period":"per_month" + }, + "mortgage":{ + "per_interval_value":0, + "interval_period":"per_month" + }, + "childcare":{ + "per_interval_value":0, + "interval_period":"per_month" + }, + "rent":{ + "per_interval_value":0, + "interval_period":"u""per_week" + }, + "maintenance":{ + "per_interval_value":0, + "interval_period":"u""per_week" + }, + "criminal_legalaid_contributions":0, + "national_insurance":{ + "per_interval_value":0, + "interval_period":"per_month" + } + }, + "income":{ + "self_employment_drawings":{ + "per_interval_value":0, + "interval_period":"per_month" + }, + "benefits":{ + "per_interval_value":0, + "interval_period":"per_month" + }, + "maintenance_received":{ + "per_interval_value":0, + "interval_period":"u""per_week" + }, + "self_employed":"0", + "tax_credits":{ + "per_interval_value":0, + "interval_period":"per_month" + }, + "earnings":{ + "per_interval_value":0, + "interval_period":"per_month" + }, + "child_benefits":{ + "per_interval_value":0, + "interval_period":"per_month" + }, + "other_income":{ + "per_interval_value":0, + "interval_period":"u""per_week" + }, + "pension":{ + "per_interval_value":0, + "interval_period":"u""per_week" + } + } + }, + "dependants_young":0, + "on_nass_benefits":"0" +} + """ diff --git a/app/means_test/routes.py b/app/means_test/routes.py index f110b9990..490811e44 100644 --- a/app/means_test/routes.py +++ b/app/means_test/routes.py @@ -1,6 +1,7 @@ -from flask import render_template, redirect, url_for +from flask import render_template from app.means_test import bp from app.means_test.forms import AboutYouForm +from app.means_test.api import update_means_test @bp.route("/about-you", methods=["GET", "POST"]) @@ -8,6 +9,7 @@ def about_you(): form = AboutYouForm() if form.validate_on_submit(): - return redirect(url_for("means_test.about-your-income")) + update_means_test(form.payload_2()) + return "Done" return render_template("means_test/about-you.html", form=form) diff --git a/app/static/src/js/custom.js b/app/static/src/js/custom.js index e6a24879b..11e2ef83f 100644 --- a/app/static/src/js/custom.js +++ b/app/static/src/js/custom.js @@ -1,2 +1,38 @@ import { initAll } from 'govuk-frontend/dist/govuk/all.bundle.js'; -initAll(); \ No newline at end of file +initAll(); + +document.addEventListener('DOMContentLoaded', function() { + const dependentElements = document.querySelectorAll('[data-depends-on]'); + + function updateVisibility() { + dependentElements.forEach(element => { + // Check parent condition first + const parentField = element.dataset.parentField; + const parentValue = element.dataset.parentValue; + const parentSelected = document.querySelector( + `input[name="${parentField}"]:checked` + )?.value === parentValue; + + // Only check the dependent field if parent condition is met + const controllingField = element.dataset.dependsOn; + const requiredValue = element.dataset.dependsOnValue; + const dependentSelected = document.querySelector( + `input[name="${controllingField}"]:checked` + )?.value === requiredValue; + + // Show only if both conditions are met + const shouldShow = parentSelected && dependentSelected; + element.classList.toggle('govuk-radios__conditional--hidden', !shouldShow); + }); + } + + // Listen for changes on any radio button + document.addEventListener('change', function(event) { + if (event.target.type === 'radio') { + requestAnimationFrame(updateVisibility); + } + }); + + // Initial state + updateVisibility(); +}); diff --git a/app/static/src/scss/means-test.scss b/app/static/src/scss/means-test.scss index 61297c396..048de71cc 100644 --- a/app/static/src/scss/means-test.scss +++ b/app/static/src/scss/means-test.scss @@ -3,4 +3,4 @@ .govuk-radios--inline .govuk-radios__conditional { order: 9999; width: 100%; -} \ No newline at end of file +} diff --git a/app/templates/means_test/about-you.html b/app/templates/means_test/about-you.html index c046b02ed..d16209a33 100644 --- a/app/templates/means_test/about-you.html +++ b/app/templates/means_test/about-you.html @@ -29,16 +29,17 @@ {% block form %} {{ form.csrf_token }} - {{ form.partner(params={ + {{ form.has_partner(params={ + "idPrefix": "has-partner", 'items': [ { - 'value': 'yes', + 'value': '1', 'conditional': { 'html': areYouInADisputeHtml - } + }, }, { - 'value': 'no' + 'value': '0' }, ] }) }} @@ -48,13 +49,13 @@ {{ form.have_children(params={ 'items': [ { - 'value': 'yes', + 'value': '1', 'conditional': { 'html': howManyChildrenUnder16Html } }, { - 'value': 'no' + 'value': '0' }, ] }) }} @@ -62,13 +63,13 @@ {{ form.have_dependents(params={ 'items': [ { - 'value': 'yes', + 'value': '1', 'conditional': { 'html': howManyDependentsOver16Html } }, { - 'value': 'no' + 'value': '0' }, ] }) }} @@ -77,8 +78,12 @@ {{ form.is_employed() }} + {{ form.partner_is_employed() }} + {{ form.is_self_employed() }} + {{ form.partner_is_self_employed() }} + {{ form.aged_60_or_over() }} {{ form.have_savings() }} diff --git a/run_local.sh b/run_local.sh index 7d02c5040..72639b2d8 100755 --- a/run_local.sh +++ b/run_local.sh @@ -13,13 +13,7 @@ echo "Running environment: $ENVIRONMENT" docker compose down --remove-orphans -if ! docker images -q govuk-frontend-standalone; then - echo "The 'govuk-frontend-standalone' image is not found locally. Building it now..." - docker compose -f compose.yml -f compose-standalone.yml up --build -d -else - echo "'govuk-frontend-standalone' image already exists. Starting containers without rebuilding." - docker compose -f compose.yml -f compose-standalone.yml up -d -fi +docker compose -f compose.yml -f compose-standalone.yml up -d echo "Waiting for backend container to be ready..." until docker exec cla_backend bash -c "echo 'Backend is ready'"; do From 68a422f185472e1e817e227780253f25cb92bb75 Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Tue, 14 Jan 2025 17:25:29 +0000 Subject: [PATCH 10/12] Implement benefits page and backend payload --- app/api.py | 3 + app/categories/constants.py | 46 +++- app/categories/x_cat/forms.py | 2 +- app/config/__init__.py | 2 +- app/means_test/__init__.py | 5 +- app/means_test/api.py | 19 +- app/means_test/forms.py | 284 +++++------------------- app/means_test/routes.py | 15 -- app/means_test/urls.py | 11 + app/means_test/views.py | 216 ++++++++++++++++++ app/session.py | 25 +++ app/templates/categories/in-scope.html | 2 +- app/templates/means_test/about-you.html | 6 - app/templates/means_test/benefits.html | 10 + app/templates/means_test/review.html | 29 +++ 15 files changed, 400 insertions(+), 275 deletions(-) delete mode 100644 app/means_test/routes.py create mode 100644 app/means_test/urls.py create mode 100644 app/means_test/views.py create mode 100644 app/templates/means_test/benefits.html create mode 100644 app/templates/means_test/review.html diff --git a/app/api.py b/app/api.py index 1f5850f1e..e68ace39f 100644 --- a/app/api.py +++ b/app/api.py @@ -58,6 +58,9 @@ def _make_request( Returns: The parsed JSON response from the server """ + if not endpoint.endswith("/"): # CLA Backend requires trailing slashes + endpoint = f"{endpoint}/" + if params: params = self.clean_params( params diff --git a/app/categories/constants.py b/app/categories/constants.py index e54e26c46..5add65bab 100644 --- a/app/categories/constants.py +++ b/app/categories/constants.py @@ -7,34 +7,58 @@ class Category: display_text: LazyString code: str article_category_name: str | None + chs_code: str | None # One of legalaid_categories.code def __str__(self): # Returns the translated display text return str(self.display_text) -FAMILY = Category(_("Children, families and relationships"), "FAMILY", "Family") +FAMILY = Category( + _("Children, families and relationships"), "FAMILY", "Family", "family" +) HOUSING = Category( - _("Housing, homelessness, losing your home"), "HOUSING", "Community care" + _("Housing, homelessness, losing your home"), "HOUSING", "Housing", "housing" +) +COMMUNITY_CARE = Category( + _("Community care"), "COMMUNITY_CARE", "Community care", "commcare" +) +DOMESTIC_ABUSE = Category( + _("Domestic abuse"), "DOMESTIC_ABUSE", "Domestic abuse", "family" ) -COMMUNITY_CARE = Category(_("Community care"), "COMMUNITY_CARE", "Community care") -DOMESTIC_ABUSE = Category(_("Domestic abuse"), "DOMESTIC_ABUSE", "Domestic abuse") BENEFITS = Category( - _("Appeal a decision about your benefits"), "BENEFITS", "Welfare benefits" + _("Appeal a decision about your benefits"), + "BENEFITS", + "Welfare benefits", + "benefits", +) +DISCRIMINATION = Category( + _("Discrimination"), "DISCRIMINATION", "Discrimination", "discrimination" ) -DISCRIMINATION = Category(_("Discrimination"), "DISCRIMINATION", "Discrimination") MENTAL_CAPACITY = Category( - _("Mental capacity, mental health"), "MENTAL_CAPACITY", "Mental health" + _("Mental capacity, mental health"), + "MENTAL_CAPACITY", + "Mental health", + "mentalhealth", ) ASYLUM_AND_IMMIGRATION = Category( - _("Asylum and immigration"), "ASYLUM_AND_IMMIGRATION", None + _("Asylum and immigration"), "ASYLUM_AND_IMMIGRATION", None, "immigration" ) SOCIAL_CARE = Category( - _("Care needs for disability and old age (social care)"), "SOCIAL_CARE", None + _("Care needs for disability and old age (social care)"), + "SOCIAL_CARE", + None, + "commcare", ) PUBLIC_LAW = Category( - _("Legal action against police and public organisations"), "PUBLIC_LAW", "Public" + _("Legal action against police and public organisations"), + "PUBLIC_LAW", + "Public", + "publiclaw", ) EDUCATION = Category( - _("Special educational needs and disability (SEND)"), "EDUCATION", "Education" + _("Special educational needs and disability (SEND)"), + "EDUCATION", + "Education", + "education", ) diff --git a/app/categories/x_cat/forms.py b/app/categories/x_cat/forms.py index 71773181a..bf22d2840 100644 --- a/app/categories/x_cat/forms.py +++ b/app/categories/x_cat/forms.py @@ -28,7 +28,7 @@ class AreYouUnder18Form(QuestionForm): class AntiSocialBehaviourForm(QuestionForm): title = _("Were you accused by a landlord or the council?") category = Category( - _("Anti-social behaviour and gangs"), "ANTI_SOCIAL_BEHAVIOUR", None + _("Anti-social behaviour and gangs"), "ANTI_SOCIAL_BEHAVIOUR", None, None ) next_step_mapping = { "yes": { diff --git a/app/config/__init__.py b/app/config/__init__.py index 85b2f2c49..3b14f14d5 100644 --- a/app/config/__init__.py +++ b/app/config/__init__.py @@ -26,4 +26,4 @@ class Config(object): "https://laa-legal-adviser-api-production.cloud-platform.service.justice.gov.uk", ) POSTCODES_IO_URL = os.environ.get("POSTCODES_IO_URL", "https://api.postcodes.io") - CLA_BACKEND_URL = os.environ.get("CLA_BACKEND_URL", "http://localhost:8010") + CLA_BACKEND_URL = os.environ.get("CLA_BACKEND_URL", "http://localhost:8000") diff --git a/app/means_test/__init__.py b/app/means_test/__init__.py index eb8140918..52228f985 100644 --- a/app/means_test/__init__.py +++ b/app/means_test/__init__.py @@ -2,4 +2,7 @@ bp = Blueprint("means_test", __name__) -from app.means_test import routes # noqa: E402,F401 +YES = "1" +NO = "0" + +from app.means_test import urls # noqa: E402,F401 diff --git a/app/means_test/api.py b/app/means_test/api.py index acdc0a7a6..ddd5b78e1 100644 --- a/app/means_test/api.py +++ b/app/means_test/api.py @@ -1,17 +1,22 @@ from app.api import cla_backend -from flask import session +from flask import session, url_for, redirect -def update_means_test(form_data): +def update_means_test(payload): means_test_endpoint = "checker/api/v1/eligibility_check/" ec_reference = session.get("reference") if ec_reference: - print(form_data) - form_data["reference"] = ec_reference - cla_backend.patch(f"{means_test_endpoint}/{ec_reference}", json=form_data) + print(payload) + response = cla_backend.patch( + f"{means_test_endpoint}{ec_reference}", json=payload + ) + return response else: - print(form_data) - response = cla_backend.post(means_test_endpoint, json=form_data) + print(payload) + response = cla_backend.post(means_test_endpoint, json=payload) session["reference"] = response["reference"] + session["name"] = "Ben" + print(session) + return redirect(url_for(("categories.results.in_scope"))) diff --git a/app/means_test/forms.py b/app/means_test/forms.py index 3f47d600c..4fe6a63ce 100644 --- a/app/means_test/forms.py +++ b/app/means_test/forms.py @@ -1,20 +1,34 @@ +from flask import session from flask_wtf import FlaskForm -from wtforms.fields import RadioField, IntegerField +from wtforms.fields import RadioField, IntegerField, SelectMultipleField from govuk_frontend_wtf.wtforms_widgets import GovTextInput, GovSubmitInput from wtforms.fields.simple import SubmitField from wtforms.validators import InputRequired, NumberRange from app.means_test.validators import ValidateIf -from app.means_test.widgets import MeansTestRadioInput +from app.means_test.widgets import MeansTestRadioInput, MeansTestCheckboxInput from flask_babel import gettext as _ +from app.means_test import YES, NO -YES = "1" -NO = "0" +class BaseMeansTestForm(FlaskForm): + title = "" -class AboutYouForm(FlaskForm): + submit = SubmitField(_("Continue"), widget=GovSubmitInput()) + + def payload(self) -> dict: + return {} + + @classmethod + def should_show(cls) -> bool: + return True + + +class AboutYouForm(BaseMeansTestForm): title = "About you" + template = "means_test/about-you.html" + has_partner = RadioField( "Do you have a partner?", choices=[(YES, "Yes"), (NO, "No")], @@ -170,97 +184,12 @@ class AboutYouForm(FlaskForm): ], ) - submit = SubmitField(_("Continue"), widget=GovSubmitInput()) - - def payload_2(self): - return { - "on_passported_benefits": "0", - "category": "u" "debt", - "is_you_or_your_partner_over_60": "u" "0", - "has_partner": "0", - "property_set": [], - "specific_benefits": {}, - "dependants_old": 0, - "you": { - "savings": { - "credit_balance": 0, - "investment_balance": 0, - "asset_balance": 0, - "bank_balance": 0, - }, - "deductions": { - "income_tax": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "mortgage": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "childcare": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "rent": { - "per_interval_value": 0, - "interval_period": "u" "per_week", - }, - "maintenance": { - "per_interval_value": 0, - "interval_period": "u" "per_week", - }, - "criminal_legalaid_contributions": 0, - "national_insurance": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - }, - "income": { - "self_employment_drawings": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "benefits": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "maintenance_received": { - "per_interval_value": 0, - "interval_period": "u" "per_week", - }, - "self_employed": "0", - "tax_credits": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "earnings": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "child_benefits": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "other_income": { - "per_interval_value": 0, - "interval_period": "u" "per_week", - }, - "pension": { - "per_interval_value": 0, - "interval_period": "u" "per_week", - }, - }, - }, - "dependants_young": 0, - "on_nass_benefits": "0", - } - def payload(self): payload = { "has_partner": YES if self.has_partner.data == YES and not self.are_you_in_a_dispute.data == NO else NO, - "is_you_or_your_partner_over_60": "1", + "is_you_or_your_partner_over_60": self.aged_60_or_over.data, "dependants_young": self.num_children.data if self.have_children.data == YES else 0, @@ -268,60 +197,6 @@ def payload(self): if self.have_dependents.data == YES else 0, "you": {"income": {"self_employed": self.is_self_employed.data}}, - # TEST DATA - "property_set": [], - "category": "debt", - "on_passported_benefits": "0", - "on_nass_benefits": "0", - "specific_benefits": {}, - } - - payload["you"] = { - "savings": { - "credit_balance": 0, - "investment_balance": 0, - "asset_balance": 0, - "bank_balance": 0, - }, - "deductions": { - "income_tax": {"per_interval_value": 0, "interval_period": "per_month"}, - "mortgage": {"per_interval_value": 0, "interval_period": "per_month"}, - "childcare": {"per_interval_value": 0, "interval_period": "per_month"}, - "rent": {"per_interval_value": 0, "interval_period": "per_week"}, - "maintenance": {"per_interval_value": 0, "interval_period": "per_week"}, - "criminal_legalaid_contributions": 0, - "national_insurance": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - }, - "income": { - "self_employment_drawings": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "benefits": {"per_interval_value": 0, "interval_period": "per_month"}, - "maintenance_received": { - "per_interval_value": 0, - "interval_period": "per_week", - }, - "self_employed": "0", - "tax_credits": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "earnings": {"per_interval_value": 0, "interval_period": "per_month"}, - "child_benefits": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "other_income": { - "per_interval_value": 0, - "interval_period": "per_month", - }, - "pension": {"per_interval_value": 0, "interval_period": "per_month"}, - "total": 0, - }, } if payload["has_partner"] and self.partner_is_self_employed.data == YES: @@ -345,90 +220,35 @@ def payload(self): return payload - """ -{ - "on_passported_benefits":"0", - "category":"u""debt", - "is_you_or_your_partner_over_60":"u""0", - "has_partner":"0", - "property_set":[ - - ], - "specific_benefits":{ - - }, - "dependants_old":0, - "you":{ - "savings":{ - "credit_balance":0, - "investment_balance":0, - "asset_balance":0, - "bank_balance":0 - }, - "deductions":{ - "income_tax":{ - "per_interval_value":0, - "interval_period":"per_month" - }, - "mortgage":{ - "per_interval_value":0, - "interval_period":"per_month" - }, - "childcare":{ - "per_interval_value":0, - "interval_period":"per_month" - }, - "rent":{ - "per_interval_value":0, - "interval_period":"u""per_week" - }, - "maintenance":{ - "per_interval_value":0, - "interval_period":"u""per_week" - }, - "criminal_legalaid_contributions":0, - "national_insurance":{ - "per_interval_value":0, - "interval_period":"per_month" - } - }, - "income":{ - "self_employment_drawings":{ - "per_interval_value":0, - "interval_period":"per_month" - }, - "benefits":{ - "per_interval_value":0, - "interval_period":"per_month" - }, - "maintenance_received":{ - "per_interval_value":0, - "interval_period":"u""per_week" - }, - "self_employed":"0", - "tax_credits":{ - "per_interval_value":0, - "interval_period":"per_month" - }, - "earnings":{ - "per_interval_value":0, - "interval_period":"per_month" - }, - "child_benefits":{ - "per_interval_value":0, - "interval_period":"per_month" - }, - "other_income":{ - "per_interval_value":0, - "interval_period":"u""per_week" - }, - "pension":{ - "per_interval_value":0, - "interval_period":"u""per_week" - } - } - }, - "dependants_young":0, - "on_nass_benefits":"0" -} - """ + +class BenefitsForm(BaseMeansTestForm): + title = _(" Which benefits do you receive?") + + template = "means_test/benefits.html" + + @classmethod + def should_show(cls) -> bool: + return ( + session.get_eligibility().forms.get("about-you", {}).get("on_benefits") + == YES + ) + + benefits = SelectMultipleField( + label="", + widget=MeansTestCheckboxInput( + is_inline=False, show_divider=True, hint_text=_("Select all that apply") + ), + choices=[ + ("child_benefit", _("Child Benefit")), + ("pension_credit", _("Guarantee Credit")), + ("income_support", _("Income Support")), + ("job_seekers_allowance", _("Income-based Jobseeker's Allowance")), + ( + "employment_support", + _("Income-related Employment and Support Allowance"), + ), + ("universal_credit", _("Universal Credit")), + ("", ""), + ("other-benefit", _("Any other benefits")), + ], + ) diff --git a/app/means_test/routes.py b/app/means_test/routes.py deleted file mode 100644 index 490811e44..000000000 --- a/app/means_test/routes.py +++ /dev/null @@ -1,15 +0,0 @@ -from flask import render_template -from app.means_test import bp -from app.means_test.forms import AboutYouForm -from app.means_test.api import update_means_test - - -@bp.route("/about-you", methods=["GET", "POST"]) -def about_you(): - form = AboutYouForm() - - if form.validate_on_submit(): - update_means_test(form.payload_2()) - return "Done" - - return render_template("means_test/about-you.html", form=form) diff --git a/app/means_test/urls.py b/app/means_test/urls.py new file mode 100644 index 000000000..46ea62cc8 --- /dev/null +++ b/app/means_test/urls.py @@ -0,0 +1,11 @@ +from app.means_test.views import MeansTest, CheckYourAnswers +from app.means_test import bp + +for name, form_class in MeansTest.forms.items(): + bp.add_url_rule( + f"/{name}", + view_func=MeansTest.as_view(name, form_class, name), + methods=["GET", "POST"], + ) + +bp.add_url_rule("/review", view_func=CheckYourAnswers.as_view("review")) diff --git a/app/means_test/views.py b/app/means_test/views.py new file mode 100644 index 000000000..e32d812e6 --- /dev/null +++ b/app/means_test/views.py @@ -0,0 +1,216 @@ +from flask.views import View, MethodView +from flask import render_template, url_for, redirect, session + +from app.means_test.api import update_means_test +from app.means_test.forms import BenefitsForm, AboutYouForm + + +class MeansTest(View): + forms = {"about-you": AboutYouForm, "benefits": BenefitsForm} + + def __init__(self, current_form_class, current_name): + self.form_class = current_form_class + self.current_name = current_name + + def dispatch_request(self): + form = self.form_class() + if form.validate_on_submit(): + session.get_eligibility().add(self.current_name, form.data) + next_page = url_for(f"means_test.{self.get_next_page(self.current_name)}") + payload = self.get_payload(session.get_eligibility()) + update_means_test(payload) + + return redirect(next_page) + return render_template(self.form_class.template, form=form) + + def get_next_page(self, current_key): + keys = iter(self.forms.keys()) # Create an iterator over the keys + for key in keys: + if key == current_key: + next_page = next( + keys, None + ) # Return the next key or None if no more keys + if not next_page: + return "review" + next_page_form = self.forms[next_page] + if next_page_form.should_show(): + return next_page + continue + return "review" + + @classmethod + def get_payload(cls, eligibility_data: dict) -> dict: + about = eligibility_data.forms.get("about-you", {}) + benefits_form = eligibility_data.forms.get("benefits", {}) + + benefits = benefits_form.get("benefits", []) + + payload = { + "category": eligibility_data.category, + "your_problem_notes": "", + "notes": "", + "property_set": [], + "you": { + "income": { + "earnings": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "self_employment_drawings": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "benefits": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "tax_credits": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "child_benefits": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "maintenance_received": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "pension": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "other_income": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "self_employed": about.get("is_self_employed", False), + }, + "savings": { + "bank_balance": None, + "investment_balance": None, + "asset_balance": None, + "credit_balance": None, + "total": None, + }, + "deductions": { + "income_tax": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "national_insurance": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "maintenance": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "childcare": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "mortgage": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "rent": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "criminal_legalaid_contributions": None, + }, + }, + "partner": { + "income": { + "earnings": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "self_employment_drawings": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "benefits": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "tax_credits": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "maintenance_received": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "pension": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "other_income": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "self_employed": about.get("is_partner_self_employed", False), + }, + "savings": { + "bank_balance": None, + "investment_balance": None, + "asset_balance": None, + "credit_balance": None, + }, + "deductions": { + "income_tax": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "national_insurance": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "maintenance": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "childcare": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "mortgage": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "rent": { + "per_interval_value": None, + "interval_period": "per_month", + }, + "criminal_legalaid_contributions": None, + }, + }, + "dependants_young": about.get("dependants_young", 0) + if about.get("has_children", False) + else 0, + "dependants_old": about.get("dependants_old", 0) + if about.get("has_dependants", False) + else 0, + "is_you_or_your_partner_over_60": about.get("aged_60_or_over", False), + "has_partner": about.get("has_partner", False) + and about.get("in_dispute", False), + "on_passported_benefits": False, + "on_nass_benefits": False, + "specific_benefits": { + "pension_credit": "pension_credit" in benefits, + "job_seekers_allowance": "job_seekers_allowance" in benefits, + "employment_support": "employment_support" in benefits, + "universal_credit": "universal_credit" in benefits, + "income_support": "income_support" in benefits, + }, + "disregards": [], + } + + return payload + + +class CheckYourAnswers(MethodView): + def get(self): + return render_template("means_test/review.html", data=session.get_eligibility()) diff --git a/app/session.py b/app/session.py index 6374046c9..b44ae1d2a 100644 --- a/app/session.py +++ b/app/session.py @@ -1,8 +1,33 @@ from flask.sessions import SecureCookieSession, SecureCookieSessionInterface from app.categories.constants import Category +from flask import session +from dataclasses import dataclass + + +@dataclass +class Eligibility: + forms = {} + + def add(self, form_name, data): + self.forms[form_name] = data + + @property + def category(self): + return session.get("category")["chs_code"] class Session(SecureCookieSession): + def __init__(self, *args, **kwargs): + print(args) + super().__init__(*args, **kwargs) + self["eligibility"] = Eligibility() + + def update_eligibility(self, form_name, form_data): + self["eligibility"].add(form_name, form_data) + + def get_eligibility(self): + return self["eligibility"] + @property def category(self) -> Category | None: """Get the category from the session. diff --git a/app/templates/categories/in-scope.html b/app/templates/categories/in-scope.html index b97980232..8b00f76a5 100644 --- a/app/templates/categories/in-scope.html +++ b/app/templates/categories/in-scope.html @@ -56,7 +56,7 @@

{% trans %}If you use British Sign Language (BSL){% {{ govukButton(params={ "text": _("Check if you qualify financially"), - "href": url_for("means_test.about_you"), + "href": url_for("means_test.about-you"), "isStartButton": "true", "classes": "govuk-!-margin-top-2 govuk-!-margin-bottom-8" }) }} diff --git a/app/templates/means_test/about-you.html b/app/templates/means_test/about-you.html index d16209a33..bd1aad2c3 100644 --- a/app/templates/means_test/about-you.html +++ b/app/templates/means_test/about-you.html @@ -33,13 +33,11 @@ "idPrefix": "has-partner", 'items': [ { - 'value': '1', 'conditional': { 'html': areYouInADisputeHtml }, }, { - 'value': '0' }, ] }) }} @@ -49,13 +47,11 @@ {{ form.have_children(params={ 'items': [ { - 'value': '1', 'conditional': { 'html': howManyChildrenUnder16Html } }, { - 'value': '0' }, ] }) }} @@ -63,13 +59,11 @@ {{ form.have_dependents(params={ 'items': [ { - 'value': '1', 'conditional': { 'html': howManyDependentsOver16Html } }, { - 'value': '0' }, ] }) }} diff --git a/app/templates/means_test/benefits.html b/app/templates/means_test/benefits.html new file mode 100644 index 000000000..d1cdaa56c --- /dev/null +++ b/app/templates/means_test/benefits.html @@ -0,0 +1,10 @@ +{% extends "means_test/form-page.html" %} + +{% block form %} + + {{ form.csrf_token }} + + {{ form.benefits }} + + {{ form.submit }} +{% endblock %} \ No newline at end of file diff --git a/app/templates/means_test/review.html b/app/templates/means_test/review.html new file mode 100644 index 000000000..967ef558b --- /dev/null +++ b/app/templates/means_test/review.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} +{%- from 'govuk_frontend_jinja/components/back-link/macro.html' import govukBackLink -%} +{%- from 'govuk_frontend_jinja/components/exit-this-page/macro.html' import govukExitThisPage -%} + +{% block pageTitle %}Review your answers - GOV.UK{% endblock %} + +{% block beforeContent%} +{{ super() }} +{{ govukBackLink({ + 'href': back_link, + 'text': "Back" + }) }} + +{% endblock %} + +{% block content %} + +{{ super() }} + +
+
+ + {% block formHeading %} +

Review your answers

+ {% endblock %} + +
+
+{% endblock %} \ No newline at end of file From 0a28dec36d0d8ff751da74fc4f4ebf0212bd7746 Mon Sep 17 00:00:00 2001 From: Ben Millar Date: Tue, 14 Jan 2025 17:34:18 +0000 Subject: [PATCH 11/12] Add is_eligible endpoint --- app/means_test/api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/means_test/api.py b/app/means_test/api.py index ddd5b78e1..6e9c020df 100644 --- a/app/means_test/api.py +++ b/app/means_test/api.py @@ -20,3 +20,9 @@ def update_means_test(payload): session["name"] = "Ben" print(session) return redirect(url_for(("categories.results.in_scope"))) + + +def is_eligible(reference): + means_test_endpoint = "checker/api/v1/eligibility_check/" + response = cla_backend.post(f"{means_test_endpoint}{reference}/is_eligible/") + return response["is_eligible"] From 8ca7796352a9dd6ccae439ae605834e9b6d7969c Mon Sep 17 00:00:00 2001 From: said-moj <45761276+said-moj@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:28:47 +0000 Subject: [PATCH 12/12] Render all the fields of a form on without individually rendering each field --- app/means_test/forms.py | 17 ++++++++++++++--- app/templates/means_test/benefits.html | 10 ---------- app/templates/means_test/form-page.html | 7 ++++--- 3 files changed, 18 insertions(+), 16 deletions(-) delete mode 100644 app/templates/means_test/benefits.html diff --git a/app/means_test/forms.py b/app/means_test/forms.py index 4fe6a63ce..cfefd2a66 100644 --- a/app/means_test/forms.py +++ b/app/means_test/forms.py @@ -13,9 +13,22 @@ class BaseMeansTestForm(FlaskForm): title = "" - + template = "means_test/form-page.html" submit = SubmitField(_("Continue"), widget=GovSubmitInput()) + @property + def fields_to_render(self): + fields = [] + submit_fields = [] + for name, field in self._fields.items(): + if isinstance(field, SubmitField): + submit_fields.append(field) + else: + fields.append(field) + if submit_fields: + fields.extend(submit_fields) + return fields + def payload(self) -> dict: return {} @@ -224,8 +237,6 @@ def payload(self): class BenefitsForm(BaseMeansTestForm): title = _(" Which benefits do you receive?") - template = "means_test/benefits.html" - @classmethod def should_show(cls) -> bool: return ( diff --git a/app/templates/means_test/benefits.html b/app/templates/means_test/benefits.html deleted file mode 100644 index d1cdaa56c..000000000 --- a/app/templates/means_test/benefits.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "means_test/form-page.html" %} - -{% block form %} - - {{ form.csrf_token }} - - {{ form.benefits }} - - {{ form.submit }} -{% endblock %} \ No newline at end of file diff --git a/app/templates/means_test/form-page.html b/app/templates/means_test/form-page.html index ae87e10f8..3f915fa36 100644 --- a/app/templates/means_test/form-page.html +++ b/app/templates/means_test/form-page.html @@ -26,9 +26,10 @@

{{ form.title }}

- {% block form %} } - {{ form.question() }} - {{ form.submit }} + {% block form %} + {% for field in form.fields_to_render %} + {{ field }} + {% endfor %} {% endblock %}