diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 426bd06..7d8f02c 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -36,11 +36,14 @@ jobs: uses: actions/setup-python@v5 with: python-version: "3.x" - - name: Install build dependencies - run: | - python -m pip install --upgrade pip - pip install build + - uses: snok/install-poetry@v1 + with: + virtualenvs-create: true + virtualenvs-in-project: true + virtualenvs-path: .venv + - name: Install dependencies + run: poetry install --no-interaction --no-root - name: Build package - run: python -m build + run: poetry build - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + run: poetry publish diff --git a/Dockerfile b/Dockerfile index 4d2fd4b..be04a3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,9 @@ -FROM ghcr.io/nationalarchives/tna-python:latest +FROM ghcr.io/nationalarchives/tna-python-dev:latest COPY --chown=app . . RUN tna-build -CMD ["tna-run", "test:app"] \ No newline at end of file +ENV FLASK_APP="test:app" + +CMD ["tna-run", "$FLASK_APP"] \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index a78dcdc..1db7429 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,24 @@ [tool.poetry] name = "tna-frontend-jinja" version = "0.2.8" -description = "" +description = "TNA Frontend Jinja templates" authors = ["Andrew Hosgood "] license = "MIT" readme = "README.md" +repository = "https://github.com/nationalarchives/tna-frontend-jinja" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Topic :: Software Development :: Code Generators", + "Topic :: Software Development :: User Interfaces", + "Topic :: Text Processing :: Markup :: HTML", +] +packages = [{include = "tna_frontend_jinja"}] +include = ["**/*.html"] +exclude = ["test"] [tool.poetry.dependencies] python = "^3.12" @@ -19,6 +33,9 @@ optional = true [tool.poetry.group.dev.dependencies] email-validator = "^2.2.0" +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/nationalarchives/tna-frontend-jinja/issues" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 4026620..0000000 --- a/setup.cfg +++ /dev/null @@ -1,15 +0,0 @@ -[metadata] -# This includes the license file(s) in the wheel. -# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file -license_files = LICENSE - -[flake8] -exclude=venv -ignore=E203,W503 -max-complexity=10 -max-line-length=120 - -[isort] -profile=black -multi_line_output=3 -line_length=120 \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index b471149..0000000 --- a/setup.py +++ /dev/null @@ -1,34 +0,0 @@ -import setuptools - -with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() - -setuptools.setup( - name="tna-frontend-jinja", - version="0.2.8", - author="Andrew Hosgood", - author_email="andrew.hosgood@nationalarchives.gov.uk", - description="TNA Frontend Jinja templates", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/nationalarchives/tna-frontend-jinja", - project_urls={ - "Bug Tracker": "https://github.com/nationalarchives/tna-frontend-jinja/issues", - }, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Environment :: Web Environment", - "Intended Audience :: Developers", - "Topic :: Software Development :: Code Generators", - "Topic :: Software Development :: User Interfaces", - "Topic :: Text Processing :: Markup :: HTML", - ], - packages=["tna_frontend_jinja"], - package_data={ - "": ["**/*.html"], - }, - python_requires=">=3.8", - install_requires=["flask>=2"], -) diff --git a/test/__init__.py b/test/__init__.py index ddcd5ac..b63b31b 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,7 +1,7 @@ import os from flask import Flask -from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader +from jinja2 import ChoiceLoader, PackageLoader from tna_frontend_jinja.wtforms.widgets import WTFormsHelpers from .components import bp as components_bp diff --git a/test/components/__init__.py b/test/components/__init__.py index 23ba395..0cee1d8 100644 --- a/test/components/__init__.py +++ b/test/components/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -bp = Blueprint("components", __name__, template_folder="test-templates") +bp = Blueprint("components", __name__) from test.components import routes # noqa: E402,F401 diff --git a/test/components/routes.py b/test/components/routes.py index f1c0091..98ab24d 100644 --- a/test/components/routes.py +++ b/test/components/routes.py @@ -5,166 +5,166 @@ def render_component(template): - params = request.args.get("params") - context = json.loads(params) if params else {} - return render_template(template, context=context) + params_str = request.args.get("params") + params = json.loads(params_str) if params_str else {} + return render_template(template, params=params) @bp.route("/accordion") def accordion(): - return render_component("accordion.html") + return render_component("components/accordion.html") @bp.route("/breadcrumbs") def breadcrumbs(): - return render_component("breadcrumbs.html") + return render_component("components/breadcrumbs.html") @bp.route("/button") def button(): - return render_component("button.html") + return render_component("components/button.html") @bp.route("/card") def card(): - return render_component("card.html") + return render_component("components/card.html") @bp.route("/checkboxes") def checkboxes(): - return render_component("checkboxes.html") + return render_component("components/checkboxes.html") @bp.route("/compound-filters") def compound_filters(): - return render_component("compound-filters.html") + return render_component("components/compound-filters.html") @bp.route("/cookie-banner") def cookie_banner(): - return render_component("cookie-banner.html") + return render_component("components/cookie-banner.html") @bp.route("/date-input") def date_input(): - return render_component("date-input.html") + return render_component("components/date-input.html") @bp.route("/date-search") def date_search(): - return render_component("date-search.html") + return render_component("components/date-search.html") @bp.route("/details") def details(): - return render_component("details.html") + return render_component("components/details.html") @bp.route("/error-summary") def error_summary(): - return render_component("error-summary.html") + return render_component("components/error-summary.html") @bp.route("/files-list") def files_list(): - return render_component("files-list.html") + return render_component("components/files-list.html") @bp.route("/footer") def footer(): - return render_component("footer.html") + return render_component("components/footer.html") @bp.route("/gallery") def gallery(): - return render_component("gallery.html") + return render_component("components/gallery.html") @bp.route("/global-header") def globalHeader(): - return render_component("global-header.html") + return render_component("components/global-header.html") @bp.route("/header") def header(): - return render_component("header.html") + return render_component("components/header.html") @bp.route("/hero") def hero(): - return render_component("hero.html") + return render_component("components/hero.html") @bp.route("/index-grid") def index_grid(): - return render_component("index-grid.html") + return render_component("components/index-grid.html") @bp.route("/pagination") def pagination(): - return render_component("pagination.html") + return render_component("components/pagination.html") @bp.route("/phase-banner") def phase_banner(): - return render_component("phase-banner.html") + return render_component("components/phase-banner.html") @bp.route("/picture") def picture(): - return render_component("picture.html") + return render_component("components/picture.html") @bp.route("/quick-filters") def quick_filters(): - return render_component("quick-filters.html") + return render_component("components/quick-filters.html") @bp.route("/radios") def radios(): - return render_component("radios.html") + return render_component("components/radios.html") @bp.route("/records-list") def records_list(): - return render_component("records-list.html") + return render_component("components/records-list.html") @bp.route("/search-field") def search_field(): - return render_component("search-field.html") + return render_component("components/search-field.html") @bp.route("/select") def select(): - return render_component("select.html") + return render_component("components/select.html") @bp.route("/sidebar") def sidebar(): - return render_component("sidebar.html") + return render_component("components/sidebar.html") @bp.route("/skip-link") def skip_link(): - return render_component("skip-link.html") + return render_component("components/skip-link.html") @bp.route("/tabs") def tabs(): - return render_component("tabs.html") + return render_component("components/tabs.html") @bp.route("/text-input") def text_input(): - return render_component("text-input.html") + return render_component("components/text-input.html") @bp.route("/textarea") def textarea(): - return render_component("textarea.html") + return render_component("components/textarea.html") @bp.route("/warning") def warning(): - return render_component("warning.html") + return render_component("components/warning.html") diff --git a/test/components/test-templates/button.html b/test/components/test-templates/button.html deleted file mode 100644 index 2e0a83c..0000000 --- a/test/components/test-templates/button.html +++ /dev/null @@ -1,2 +0,0 @@ -{%- from "components/button/macro.html" import tnaButton -%} -{{ tnaButton(context) }} \ No newline at end of file diff --git a/test/components/test-templates/text-input.html b/test/components/test-templates/text-input.html deleted file mode 100644 index 6754233..0000000 --- a/test/components/test-templates/text-input.html +++ /dev/null @@ -1,2 +0,0 @@ -{%- from "components/text-input/macro.html" import tnaTextInput -%} -{{ tnaTextInput(context) }} \ No newline at end of file diff --git a/test/forms/routes.py b/test/forms/routes.py index 189685d..e0cb5e0 100644 --- a/test/forms/routes.py +++ b/test/forms/routes.py @@ -1,17 +1,35 @@ -import sys from test.forms import bp from flask import redirect, render_template, url_for from flask_wtf import FlaskForm -from tna_frontend_jinja.wtforms.widgets import GovSubmitInput, GovTextInput -from wtforms import StringField, SubmitField +from tna_frontend_jinja.wtforms.widgets import ( + TnaCheckboxesInput, + TnaCheckboxInput, + TnaDateInput, + TnaPasswordInput, + TnaRadioInput, + TnaSelect, + TnaSubmitInput, + TnaTextArea, + TnaTextInput, +) +from wtforms import ( + BooleanField, + DateField, + RadioField, + SelectField, + SelectMultipleField, + StringField, + SubmitField, + TextAreaField, +) from wtforms.validators import Email, InputRequired, Length class ExampleForm(FlaskForm): email_address = StringField( "Email address", - widget=GovTextInput(), + widget=TnaTextInput(), validators=[ InputRequired(message="Enter an email address"), Length( @@ -23,8 +41,43 @@ class ExampleForm(FlaskForm): ], description="We’ll only use this to send you a receipt", ) + password = StringField( + "Password", + widget=TnaPasswordInput(), + ) + remember = BooleanField( + "Remember me", + widget=TnaCheckboxInput(), + ) + shopping = SelectMultipleField( + "Shopping list", + choices=[("cpp", "C++"), ("py", "Python"), ("text", "Plain Text")], + widget=TnaCheckboxesInput(), + ) + day = RadioField( + "Day", + choices=[("mon", "Monday"), ("tue", "Tuesday"), ("wed", "Wednesday")], + widget=TnaRadioInput(), + ) + birthday = DateField( + "Birthday", + widget=TnaDateInput(), + ) + message = TextAreaField( + "Message", + widget=TnaTextArea(), + ) + order = SelectField( + "Order", + choices=[ + ("date", "Date"), + ("relevance", "Relevance"), + ("popularity", "Popularity"), + ], + widget=TnaSelect(), + ) - submit = SubmitField("Continue", widget=GovSubmitInput()) + submit = SubmitField("Continue", widget=TnaSubmitInput()) @bp.route("/", methods=["GET", "POST"]) diff --git a/test/forms/test-templates/example-form.html b/test/forms/test-templates/example-form.html index 0576177..93c25e9 100644 --- a/test/forms/test-templates/example-form.html +++ b/test/forms/test-templates/example-form.html @@ -21,12 +21,43 @@

Example form

{{ form.csrf_token }} + {{ form.email_address(params={ 'headingLevel': 2, 'type': 'email', 'autofill': 'email', 'spellcheck': False }) }} + + {{ form.password(params={ + 'headingLevel': 2, + 'spellcheck': False + }) }} + + {{ form.remember(params={ + 'headingLevel': 2 + }) }} + + {{ form.shopping(params={ + 'headingLevel': 2 + }) }} + + {{ form.day(params={ + 'headingLevel': 2 + }) }} + + {{ form.birthday(params={ + 'headingLevel': 2 + }) }} + + {{ form.message(params={ + 'headingLevel': 2 + }) }} + + {{ form.order(params={ + 'headingLevel': 2 + }) }} +
{{ form.submit }}
@@ -37,8 +68,5 @@

Example form

{% block bodyEnd %} - + {% endblock %} diff --git a/test/utilities/__init__.py b/test/utilities/__init__.py index 79db77b..fddbc13 100644 --- a/test/utilities/__init__.py +++ b/test/utilities/__init__.py @@ -1,5 +1,5 @@ from flask import Blueprint -bp = Blueprint("utilities", __name__, template_folder="test-templates") +bp = Blueprint("utilities", __name__) from test.utilities import routes # noqa: E402,F401 diff --git a/test/utilities/routes.py b/test/utilities/routes.py index d452e2d..d548541 100644 --- a/test/utilities/routes.py +++ b/test/utilities/routes.py @@ -5,11 +5,11 @@ def render_component(template): - params = request.args.get("params") - context = json.loads(params) if params else {} - return render_template(template, context=context) + params_str = request.args.get("params") + params = json.loads(params_str) if params_str else {} + return render_template(template, params=params) @bp.route("/grid") def grid(): - return render_component("grid.html") + return render_component("utilities/grid.html") diff --git a/test/components/test-templates/accordion.html b/tna_frontend_jinja/templates/components/accordion.html similarity index 71% rename from test/components/test-templates/accordion.html rename to tna_frontend_jinja/templates/components/accordion.html index cc8f4a9..f954007 100644 --- a/test/components/test-templates/accordion.html +++ b/tna_frontend_jinja/templates/components/accordion.html @@ -1,2 +1,2 @@ {%- from "components/accordion/macro.html" import tnaAccordion -%} -{{ tnaAccordion(context) }} \ No newline at end of file +{{ tnaAccordion(params) }} \ No newline at end of file diff --git a/test/components/test-templates/breadcrumbs.html b/tna_frontend_jinja/templates/components/breadcrumbs.html similarity index 71% rename from test/components/test-templates/breadcrumbs.html rename to tna_frontend_jinja/templates/components/breadcrumbs.html index 0a805c4..1c8d702 100644 --- a/test/components/test-templates/breadcrumbs.html +++ b/tna_frontend_jinja/templates/components/breadcrumbs.html @@ -1,2 +1,2 @@ {%- from "components/breadcrumbs/macro.html" import tnaBreadcrumbs -%} -{{ tnaBreadcrumbs(context) }} \ No newline at end of file +{{ tnaBreadcrumbs(params) }} \ No newline at end of file diff --git a/tna_frontend_jinja/templates/wtforms/button.html b/tna_frontend_jinja/templates/components/button.html similarity index 100% rename from tna_frontend_jinja/templates/wtforms/button.html rename to tna_frontend_jinja/templates/components/button.html diff --git a/test/components/test-templates/card.html b/tna_frontend_jinja/templates/components/card.html similarity index 72% rename from test/components/test-templates/card.html rename to tna_frontend_jinja/templates/components/card.html index 2118ba7..2e5ca98 100644 --- a/test/components/test-templates/card.html +++ b/tna_frontend_jinja/templates/components/card.html @@ -1,2 +1,2 @@ {%- from "components/card/macro.html" import tnaCard -%} -{{ tnaCard(context) }} \ No newline at end of file +{{ tnaCard(params) }} \ No newline at end of file diff --git a/test/components/test-templates/checkboxes.html b/tna_frontend_jinja/templates/components/checkboxes.html similarity index 71% rename from test/components/test-templates/checkboxes.html rename to tna_frontend_jinja/templates/components/checkboxes.html index 8b3bfca..a191380 100644 --- a/test/components/test-templates/checkboxes.html +++ b/tna_frontend_jinja/templates/components/checkboxes.html @@ -1,2 +1,2 @@ {%- from "components/checkboxes/macro.html" import tnaCheckboxes -%} -{{ tnaCheckboxes(context) }} \ No newline at end of file +{{ tnaCheckboxes(params) }} \ No newline at end of file diff --git a/test/components/test-templates/compound-filters.html b/tna_frontend_jinja/templates/components/compound-filters.html similarity index 70% rename from test/components/test-templates/compound-filters.html rename to tna_frontend_jinja/templates/components/compound-filters.html index 98d84ee..b3f90c5 100644 --- a/test/components/test-templates/compound-filters.html +++ b/tna_frontend_jinja/templates/components/compound-filters.html @@ -1,2 +1,2 @@ {%- from "components/compound-filters/macro.html" import tnaCompoundFilters -%} -{{ tnaCompoundFilters(context) }} \ No newline at end of file +{{ tnaCompoundFilters(params) }} \ No newline at end of file diff --git a/test/components/test-templates/cookie-banner.html b/tna_frontend_jinja/templates/components/cookie-banner.html similarity index 71% rename from test/components/test-templates/cookie-banner.html rename to tna_frontend_jinja/templates/components/cookie-banner.html index d51fce9..b132091 100644 --- a/test/components/test-templates/cookie-banner.html +++ b/tna_frontend_jinja/templates/components/cookie-banner.html @@ -1,2 +1,2 @@ {%- from "components/cookie-banner/macro.html" import tnaCookieBanner -%} -{{ tnaCookieBanner(context) }} \ No newline at end of file +{{ tnaCookieBanner(params) }} \ No newline at end of file diff --git a/test/components/test-templates/date-input.html b/tna_frontend_jinja/templates/components/date-input.html similarity index 71% rename from test/components/test-templates/date-input.html rename to tna_frontend_jinja/templates/components/date-input.html index b00169f..24126fc 100644 --- a/test/components/test-templates/date-input.html +++ b/tna_frontend_jinja/templates/components/date-input.html @@ -1,2 +1,2 @@ {%- from "components/date-input/macro.html" import tnaDateInput -%} -{{ tnaDateInput(context) }} \ No newline at end of file +{{ tnaDateInput(params) }} \ No newline at end of file diff --git a/test/components/test-templates/date-search.html b/tna_frontend_jinja/templates/components/date-search.html similarity index 71% rename from test/components/test-templates/date-search.html rename to tna_frontend_jinja/templates/components/date-search.html index 8dcbf7a..708006d 100644 --- a/test/components/test-templates/date-search.html +++ b/tna_frontend_jinja/templates/components/date-search.html @@ -1,2 +1,2 @@ {%- from "components/date-search/macro.html" import tnaDateSearch -%} -{{ tnaDateSearch(context) }} \ No newline at end of file +{{ tnaDateSearch(params) }} \ No newline at end of file diff --git a/test/components/test-templates/details.html b/tna_frontend_jinja/templates/components/details.html similarity index 71% rename from test/components/test-templates/details.html rename to tna_frontend_jinja/templates/components/details.html index 9417b61..cc3108e 100644 --- a/test/components/test-templates/details.html +++ b/tna_frontend_jinja/templates/components/details.html @@ -1,2 +1,2 @@ {%- from "components/details/macro.html" import tnaDetails -%} -{{ tnaDetails(context) }} \ No newline at end of file +{{ tnaDetails(params) }} \ No newline at end of file diff --git a/test/components/test-templates/error-summary.html b/tna_frontend_jinja/templates/components/error-summary.html similarity index 71% rename from test/components/test-templates/error-summary.html rename to tna_frontend_jinja/templates/components/error-summary.html index 795de36..d207307 100644 --- a/test/components/test-templates/error-summary.html +++ b/tna_frontend_jinja/templates/components/error-summary.html @@ -1,2 +1,2 @@ {%- from "components/error-summary/macro.html" import tnaErrorSummary -%} -{{ tnaErrorSummary(context) }} \ No newline at end of file +{{ tnaErrorSummary(params) }} \ No newline at end of file diff --git a/test/components/test-templates/files-list.html b/tna_frontend_jinja/templates/components/files-list.html similarity index 71% rename from test/components/test-templates/files-list.html rename to tna_frontend_jinja/templates/components/files-list.html index dda6631..bbf5f3d 100644 --- a/test/components/test-templates/files-list.html +++ b/tna_frontend_jinja/templates/components/files-list.html @@ -1,2 +1,2 @@ {%- from "components/files-list/macro.html" import tnaFilesList -%} -{{ tnaFilesList(context) }} \ No newline at end of file +{{ tnaFilesList(params) }} \ No newline at end of file diff --git a/test/components/test-templates/footer.html b/tna_frontend_jinja/templates/components/footer.html similarity index 71% rename from test/components/test-templates/footer.html rename to tna_frontend_jinja/templates/components/footer.html index 48842ca..9f55ca2 100644 --- a/test/components/test-templates/footer.html +++ b/tna_frontend_jinja/templates/components/footer.html @@ -1,2 +1,2 @@ {%- from "components/footer/macro.html" import tnaFooter -%} -{{ tnaFooter(context) }} \ No newline at end of file +{{ tnaFooter(params) }} \ No newline at end of file diff --git a/test/components/test-templates/gallery.html b/tna_frontend_jinja/templates/components/gallery.html similarity index 71% rename from test/components/test-templates/gallery.html rename to tna_frontend_jinja/templates/components/gallery.html index ad69656..8993e88 100644 --- a/test/components/test-templates/gallery.html +++ b/tna_frontend_jinja/templates/components/gallery.html @@ -1,2 +1,2 @@ {%- from "components/gallery/macro.html" import tnaGallery -%} -{{ tnaGallery(context) }} \ No newline at end of file +{{ tnaGallery(params) }} \ No newline at end of file diff --git a/test/components/test-templates/global-header.html b/tna_frontend_jinja/templates/components/global-header.html similarity index 71% rename from test/components/test-templates/global-header.html rename to tna_frontend_jinja/templates/components/global-header.html index e3e3439..163afff 100644 --- a/test/components/test-templates/global-header.html +++ b/tna_frontend_jinja/templates/components/global-header.html @@ -1,2 +1,2 @@ {%- from "components/global-header/macro.html" import tnaGlobalHeader -%} -{{ tnaGlobalHeader(context) }} \ No newline at end of file +{{ tnaGlobalHeader(params) }} \ No newline at end of file diff --git a/test/components/test-templates/header.html b/tna_frontend_jinja/templates/components/header.html similarity index 71% rename from test/components/test-templates/header.html rename to tna_frontend_jinja/templates/components/header.html index e94c10b..0482782 100644 --- a/test/components/test-templates/header.html +++ b/tna_frontend_jinja/templates/components/header.html @@ -1,2 +1,2 @@ {%- from "components/header/macro.html" import tnaHeader -%} -{{ tnaHeader(context) }} \ No newline at end of file +{{ tnaHeader(params) }} \ No newline at end of file diff --git a/test/components/test-templates/hero.html b/tna_frontend_jinja/templates/components/hero.html similarity index 72% rename from test/components/test-templates/hero.html rename to tna_frontend_jinja/templates/components/hero.html index f13a9cb..a0355fe 100644 --- a/test/components/test-templates/hero.html +++ b/tna_frontend_jinja/templates/components/hero.html @@ -1,2 +1,2 @@ {%- from "components/hero/macro.html" import tnaHero -%} -{{ tnaHero(context) }} \ No newline at end of file +{{ tnaHero(params) }} \ No newline at end of file diff --git a/test/components/test-templates/index-grid.html b/tna_frontend_jinja/templates/components/index-grid.html similarity index 71% rename from test/components/test-templates/index-grid.html rename to tna_frontend_jinja/templates/components/index-grid.html index 2109a08..893d9bf 100644 --- a/test/components/test-templates/index-grid.html +++ b/tna_frontend_jinja/templates/components/index-grid.html @@ -1,2 +1,2 @@ {%- from "components/index-grid/macro.html" import tnaIndexGrid -%} -{{ tnaIndexGrid(context) }} \ No newline at end of file +{{ tnaIndexGrid(params) }} \ No newline at end of file diff --git a/test/components/test-templates/pagination.html b/tna_frontend_jinja/templates/components/pagination.html similarity index 71% rename from test/components/test-templates/pagination.html rename to tna_frontend_jinja/templates/components/pagination.html index adb5bd7..9845a8a 100644 --- a/test/components/test-templates/pagination.html +++ b/tna_frontend_jinja/templates/components/pagination.html @@ -1,2 +1,2 @@ {%- from "components/pagination/macro.html" import tnaPagination -%} -{{ tnaPagination(context) }} \ No newline at end of file +{{ tnaPagination(params) }} \ No newline at end of file diff --git a/test/components/test-templates/phase-banner.html b/tna_frontend_jinja/templates/components/phase-banner.html similarity index 71% rename from test/components/test-templates/phase-banner.html rename to tna_frontend_jinja/templates/components/phase-banner.html index b0c4e3b..9e2b708 100644 --- a/test/components/test-templates/phase-banner.html +++ b/tna_frontend_jinja/templates/components/phase-banner.html @@ -1,2 +1,2 @@ {%- from "components/phase-banner/macro.html" import tnaPhaseBanner -%} -{{ tnaPhaseBanner(context) }} \ No newline at end of file +{{ tnaPhaseBanner(params) }} \ No newline at end of file diff --git a/test/components/test-templates/picture.html b/tna_frontend_jinja/templates/components/picture.html similarity index 71% rename from test/components/test-templates/picture.html rename to tna_frontend_jinja/templates/components/picture.html index 5bd4341..250fc32 100644 --- a/test/components/test-templates/picture.html +++ b/tna_frontend_jinja/templates/components/picture.html @@ -1,2 +1,2 @@ {%- from "components/picture/macro.html" import tnaPicture -%} -{{ tnaPicture(context) }} \ No newline at end of file +{{ tnaPicture(params) }} \ No newline at end of file diff --git a/test/components/test-templates/quick-filters.html b/tna_frontend_jinja/templates/components/quick-filters.html similarity index 71% rename from test/components/test-templates/quick-filters.html rename to tna_frontend_jinja/templates/components/quick-filters.html index ec2d5bf..aa24d2d 100644 --- a/test/components/test-templates/quick-filters.html +++ b/tna_frontend_jinja/templates/components/quick-filters.html @@ -1,2 +1,2 @@ {%- from "components/quick-filters/macro.html" import tnaQuickFilters -%} -{{ tnaQuickFilters(context) }} \ No newline at end of file +{{ tnaQuickFilters(params) }} \ No newline at end of file diff --git a/test/components/test-templates/radios.html b/tna_frontend_jinja/templates/components/radios.html similarity index 71% rename from test/components/test-templates/radios.html rename to tna_frontend_jinja/templates/components/radios.html index 03059d9..d69b07a 100644 --- a/test/components/test-templates/radios.html +++ b/tna_frontend_jinja/templates/components/radios.html @@ -1,2 +1,2 @@ {%- from "components/radios/macro.html" import tnaRadios -%} -{{ tnaRadios(context) }} \ No newline at end of file +{{ tnaRadios(params) }} \ No newline at end of file diff --git a/test/components/test-templates/records-list.html b/tna_frontend_jinja/templates/components/records-list.html similarity index 71% rename from test/components/test-templates/records-list.html rename to tna_frontend_jinja/templates/components/records-list.html index 8982e10..b070c3b 100644 --- a/test/components/test-templates/records-list.html +++ b/tna_frontend_jinja/templates/components/records-list.html @@ -1,2 +1,2 @@ {%- from "components/records-list/macro.html" import tnaRecordsList -%} -{{ tnaRecordsList(context) }} \ No newline at end of file +{{ tnaRecordsList(params) }} \ No newline at end of file diff --git a/test/components/test-templates/search-field.html b/tna_frontend_jinja/templates/components/search-field.html similarity index 71% rename from test/components/test-templates/search-field.html rename to tna_frontend_jinja/templates/components/search-field.html index fec0d05..f877cdf 100644 --- a/test/components/test-templates/search-field.html +++ b/tna_frontend_jinja/templates/components/search-field.html @@ -1,2 +1,2 @@ {%- from "components/search-field/macro.html" import tnaSearchField -%} -{{ tnaSearchField(context) }} \ No newline at end of file +{{ tnaSearchField(params) }} \ No newline at end of file diff --git a/test/components/test-templates/select.html b/tna_frontend_jinja/templates/components/select.html similarity index 71% rename from test/components/test-templates/select.html rename to tna_frontend_jinja/templates/components/select.html index 68d4c6b..c9f9f98 100644 --- a/test/components/test-templates/select.html +++ b/tna_frontend_jinja/templates/components/select.html @@ -1,2 +1,2 @@ {%- from "components/select/macro.html" import tnaSelect -%} -{{ tnaSelect(context) }} \ No newline at end of file +{{ tnaSelect(params) }} \ No newline at end of file diff --git a/test/components/test-templates/sidebar.html b/tna_frontend_jinja/templates/components/sidebar.html similarity index 71% rename from test/components/test-templates/sidebar.html rename to tna_frontend_jinja/templates/components/sidebar.html index 976562f..aefdff7 100644 --- a/test/components/test-templates/sidebar.html +++ b/tna_frontend_jinja/templates/components/sidebar.html @@ -1,2 +1,2 @@ {%- from "components/sidebar/macro.html" import tnaSidebar -%} -{{ tnaSidebar(context) }} \ No newline at end of file +{{ tnaSidebar(params) }} \ No newline at end of file diff --git a/test/components/test-templates/skip-link.html b/tna_frontend_jinja/templates/components/skip-link.html similarity index 71% rename from test/components/test-templates/skip-link.html rename to tna_frontend_jinja/templates/components/skip-link.html index 31e5050..f8e7f8c 100644 --- a/test/components/test-templates/skip-link.html +++ b/tna_frontend_jinja/templates/components/skip-link.html @@ -1,2 +1,2 @@ {%- from "components/skip-link/macro.html" import tnaSkipLink -%} -{{ tnaSkipLink(context) }} \ No newline at end of file +{{ tnaSkipLink(params) }} \ No newline at end of file diff --git a/test/components/test-templates/tabs.html b/tna_frontend_jinja/templates/components/tabs.html similarity index 72% rename from test/components/test-templates/tabs.html rename to tna_frontend_jinja/templates/components/tabs.html index 5dd4964..d349912 100644 --- a/test/components/test-templates/tabs.html +++ b/tna_frontend_jinja/templates/components/tabs.html @@ -1,2 +1,2 @@ {%- from "components/tabs/macro.html" import tnaTabs -%} -{{ tnaTabs(context) }} \ No newline at end of file +{{ tnaTabs(params) }} \ No newline at end of file diff --git a/tna_frontend_jinja/templates/wtforms/text-input.html b/tna_frontend_jinja/templates/components/text-input.html similarity index 100% rename from tna_frontend_jinja/templates/wtforms/text-input.html rename to tna_frontend_jinja/templates/components/text-input.html diff --git a/test/components/test-templates/textarea.html b/tna_frontend_jinja/templates/components/textarea.html similarity index 71% rename from test/components/test-templates/textarea.html rename to tna_frontend_jinja/templates/components/textarea.html index 62a187a..e035c26 100644 --- a/test/components/test-templates/textarea.html +++ b/tna_frontend_jinja/templates/components/textarea.html @@ -1,2 +1,2 @@ {%- from "components/textarea/macro.html" import tnaTextarea -%} -{{ tnaTextarea(context) }} \ No newline at end of file +{{ tnaTextarea(params) }} \ No newline at end of file diff --git a/test/components/test-templates/warning.html b/tna_frontend_jinja/templates/components/warning.html similarity index 71% rename from test/components/test-templates/warning.html rename to tna_frontend_jinja/templates/components/warning.html index 2a43811..79e4583 100644 --- a/test/components/test-templates/warning.html +++ b/tna_frontend_jinja/templates/components/warning.html @@ -1,2 +1,2 @@ {%- from "components/warning/macro.html" import tnaWarning -%} -{{ tnaWarning(context) }} \ No newline at end of file +{{ tnaWarning(params) }} \ No newline at end of file diff --git a/test/utilities/test-templates/grid.html b/tna_frontend_jinja/templates/utilities/grid.html similarity index 71% rename from test/utilities/test-templates/grid.html rename to tna_frontend_jinja/templates/utilities/grid.html index 56fb6db..388f317 100644 --- a/test/utilities/test-templates/grid.html +++ b/tna_frontend_jinja/templates/utilities/grid.html @@ -1,2 +1,2 @@ {%- from "utilities/grid/macro.html" import tnaGrid -%} -{{ tnaGrid(context) }} \ No newline at end of file +{{ tnaGrid(params) }} \ No newline at end of file diff --git a/tna_frontend_jinja/wtforms/widgets.py b/tna_frontend_jinja/wtforms/widgets.py index f3a54e2..9f86463 100644 --- a/tna_frontend_jinja/wtforms/widgets.py +++ b/tna_frontend_jinja/wtforms/widgets.py @@ -83,7 +83,7 @@ def flatten_errors(errors, prefix="", id_map={}): ) -class GovFormBase(object): +class TnaFormBase(object): """Collection of helpers These are mixed into the WTForms classes which we are subclassing @@ -154,7 +154,7 @@ def render(self, params): return Markup(render_template(self.template, params=params)) -class GovIterableBase(GovFormBase): +class TnaIterableBase(TnaFormBase): def __call__(self, field, **kwargs): kwargs.setdefault("id", field.id) @@ -207,7 +207,7 @@ def map_gov_params(self, field, **kwargs): return params -# from govuk_frontend_wtf.gov_form_base import GovFormBase, GovIterableBase +# from govuk_frontend_wtf.gov_form_base import TnaFormBase, TnaIterableBase """Lifted from WTForms and modified to generate GOV.UK markup @@ -216,7 +216,7 @@ def map_gov_params(self, field, **kwargs): """ -class GovInput(GovFormBase, Input): +class TnaInput(TnaFormBase, Input): """Render a basic ```` field. This is used as the basis for most of the other input fields. @@ -225,7 +225,7 @@ class GovInput(GovFormBase, Input): to provide the ``value=`` HTML attribute. """ - template = "wtforms/text-input.html" + template = "components/text-input.html" def __call__(self, field, **kwargs): kwargs.setdefault("id", field.id) @@ -240,20 +240,179 @@ def __call__(self, field, **kwargs): return super().__call__(field, **kwargs) -class GovTextInput(GovInput, TextInput): +class TnaTextInput(TnaInput, TextInput): """Render a single-line text input.""" input_type = "text" -class GovSubmitInput(GovInput, SubmitInput): +class TnaPasswordInput(TnaInput, PasswordInput): + """Render a password input.""" + + input_type = "password" + + +class TnaCheckboxesInput(TnaIterableBase): + """Multiple checkboxes, from a SelectMultipleField + + This widget type doesn't exist in WTForms - the recommendation + there is to use a combination of the list and checkbox widgets. + However in the GOV.UK macros this type of field is not simply + a list of smaller widgets - multiple checkboxes are a single + construct of their own. + """ + + template = "components/checkboxes.html" + input_type = "checkbox" + + def map_gov_params(self, field, **kwargs): + params = super().map_gov_params(field, **kwargs) + params.setdefault( + "label", + field.label.text, + ) + return params + + +class TnaCheckboxInput(TnaCheckboxesInput): + """Render a single checkbox (i.e. a WTForms BooleanField).""" + + def __call__(self, field, **kwargs): + # We are subclassing TnaCheckboxesInput which expects + # the field to be an iterable yielding each checkbox "subfield" + # In order to make our single BooleanField comply with this, we + # need to provide it with a similar construct, but which only + # yields a single checkbox + class IterableField(object): + def __init__(self, field): + self.field = field + self.max = 1 + + def __iter__(self): + self.index = 0 + return self + + def __next__(self): + if self.index < self.max: + self.index += 1 + + return self.field + else: + raise StopIteration + + def __getattr__(self, name): + return getattr(self.field, name) + + field_group = IterableField(field) + + return super().__call__(field_group, **kwargs) + + def map_gov_params(self, field, **kwargs): + params = super().map_gov_params(field, **kwargs) + params.pop("label") + return params + + +class TnaRadioInput(TnaIterableBase): + """Render radio button inputs. + + Uses the field label as the fieldset legend. + """ + + template = "components/radios.html" + input_type = "radio" + + def map_gov_params(self, field, **kwargs): + params = super().map_gov_params(field, **kwargs) + params.setdefault( + "label", + field.label.text, + ) + return params + + +class TnaDateInput(TnaFormBase): + """Renders three input fields representing Day, Month and Year. + + To be used as a widget for WTForms' DateField or DateTimeField. + The input field labels are hardcoded to "Day", "Month" and "Year". + The provided label is set as a legend above the input fields. + The field names MUST all be the same for this widget to work. + """ + + template = "components/date-input.html" + + def __call__(self, field, **kwargs): + kwargs.setdefault("id", field.id) + if "value" not in kwargs: + kwargs["value"] = field._value() + if "required" not in kwargs and "required" in getattr( + field, "flags", [] + ): + kwargs["required"] = True + return super().__call__(field, **kwargs) + + def map_gov_params(self, field, **kwargs): + params = super().map_gov_params(field, **kwargs) + day, month, year = [None] * 3 + if field.raw_data is not None: + day, month, year = field.raw_data + elif field.data: + day, month, year = field.data.strftime("%d %m %Y").split(" ") + + params.setdefault("label", field.label.text) + params.setdefault( + "items", + [ + { + "label": "Day", + "id": "{}-day".format(field.name), + "name": field.name, + "classes": " ".join( + [ + "govuk-input--width-2", + "govuk-input--error" if field.errors else "", + ] + ).strip(), + "value": day, + }, + { + "label": "Month", + "id": "{}-month".format(field.name), + "name": field.name, + "classes": " ".join( + [ + "govuk-input--width-2", + "govuk-input--error" if field.errors else "", + ] + ).strip(), + "value": month, + }, + { + "label": "Year", + "id": "{}-year".format(field.name), + "name": field.name, + "classes": " ".join( + [ + "govuk-input--width-4", + "govuk-input--error" if field.errors else "", + ] + ).strip(), + "value": year, + }, + ], + ) + return params + + +class TnaSubmitInput(TnaInput, SubmitInput): """Renders a submit button. The field's label is used as the text of the submit button instead of the data on the field. """ - template = "wtforms/button.html" + template = "components/button.html" def __call__(self, field, **kwargs): return super().__call__(field, **kwargs) @@ -266,3 +425,68 @@ def map_gov_params(self, field, **kwargs): params.setdefault("buttonType", "submit") return params + + +class TnaTextArea(TnaFormBase, TextArea): + """Renders a multi-line text area. + + `rows` and `cols` ought to be passed as keyword args when rendering. + """ + + template = "components/textarea.html" + + def __call__(self, field, **kwargs): + kwargs.setdefault("id", field.id) + if "value" not in kwargs: + kwargs["value"] = field._value() + if "required" not in kwargs and "required" in getattr( + field, "flags", [] + ): + kwargs["required"] = True + return super().__call__(field, **kwargs) + + +class TnaSelect(TnaFormBase, Select): + """Renders a select field. + + If `multiple` is True, then the `size` property should be specified on + rendering to make the field useful. + + The field must provide an `iter_choices()` method which the widget will + call on rendering; this method must yield tuples of + `(value, label, selected)`. + """ + + template = "components/select.html" + + def __call__(self, field, **kwargs): + if self.multiple: + raise Exception( + "Please do not render mutliselect elements as a select box" + " - you should use checkboxes instead in order to comply with" + " the GOV.UK service manual" + ) + + kwargs.setdefault("id", field.id) + + if "required" not in kwargs and "required" in getattr( + field, "flags", [] + ): + kwargs["required"] = True + + kwargs["items"] = [] + + # Construct select box choices + for val, label, selected, render_kw in field.iter_choices(): + item = {"text": label, "value": val, "selected": selected} + + kwargs["items"].append(item) + + return super().__call__(field, **kwargs) + + def map_gov_params(self, field, **kwargs): + params = super().map_gov_params(field, **kwargs) + + params["items"] = kwargs["items"] + + return params