├── app ├── models.py ├── main │ ├── __init__.py │ ├── forms.py │ └── routes.py ├── templates │ ├── main │ │ ├── 429.html │ │ ├── 503.html │ │ ├── 404.html │ │ ├── 500.html │ │ ├── privacy.html │ │ ├── index.html │ │ ├── cookies.html │ │ └── accessibility.html │ └── base.html └── __init__.py ├── tests ├── __init__.py ├── test_init.py └── test_main_routes.py ├── .nvmrc ├── .python-version ├── .flaskenv ├── migrations ├── README ├── script.py.mako ├── alembic.ini └── env.py ├── govuk-frontend-flask.py ├── web ├── src │ ├── js │ │ ├── main.mjs │ │ └── modules │ │ │ ├── govuk-frontend.mjs │ │ │ └── cookie-banner.mjs │ ├── assets │ │ └── manifest.json │ └── scss │ │ └── main.scss ├── .browserslistrc ├── eslint.config.mjs ├── Dockerfile ├── package.json ├── webpack.config.js ├── nginx.conf ├── README.md └── .gitignore ├── requirements_dev.in ├── requirements.in ├── setup.cfg ├── .dockerignore ├── docker-entrypoint.sh ├── .github ├── dependabot.yml ├── workflows │ ├── webpack.yml │ ├── dependency-review.yml │ ├── python-app.yml │ ├── codeql.yml │ └── docker-publish.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── CHANGELOG.md ├── LICENSE ├── Dockerfile ├── config.py ├── requirements.txt ├── CONTRIBUTING.md ├── compose.yml ├── CODE_OF_CONDUCT.md ├── requirements_dev.txt ├── README.md └── .gitignore /app/models.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/krypton 2 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.14 2 | -------------------------------------------------------------------------------- /.flaskenv: -------------------------------------------------------------------------------- 1 | FLASK_APP=govuk-frontend-flask.py -------------------------------------------------------------------------------- /migrations/README: -------------------------------------------------------------------------------- 1 | Single-database configuration for Flask. 2 | -------------------------------------------------------------------------------- /govuk-frontend-flask.py: -------------------------------------------------------------------------------- 1 | from app import create_app 2 | 3 | app = create_app() 4 | -------------------------------------------------------------------------------- /web/src/js/main.mjs: -------------------------------------------------------------------------------- 1 | import "./modules/cookie-banner.mjs"; 2 | import "./modules/govuk-frontend.mjs"; 3 | -------------------------------------------------------------------------------- /requirements_dev.in: -------------------------------------------------------------------------------- 1 | bandit 2 | black 3 | flake8-bugbear 4 | isort 5 | mypy 6 | pep8-naming 7 | pip-audit 8 | pip-tools 9 | pur 10 | pytest-cov 11 | python-dotenv 12 | -------------------------------------------------------------------------------- /requirements.in: -------------------------------------------------------------------------------- 1 | email_validator 2 | flask 3 | flask-limiter[redis] 4 | flask-migrate 5 | flask-sqlalchemy 6 | govuk-frontend-jinja 7 | govuk-frontend-wtf 8 | gunicorn 9 | psycopg2 10 | -------------------------------------------------------------------------------- /app/main/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | bp: Blueprint = Blueprint("main", __name__, template_folder="../templates/main") 4 | 5 | from app.main import routes # noqa: E402,F401 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude=venv 3 | ignore=E203,W503 4 | max-complexity=10 5 | max-line-length=120 6 | 7 | [isort] 8 | profile=black 9 | multi_line_output=3 10 | line_length=120 11 | -------------------------------------------------------------------------------- /web/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 0.1% in GB and not dead 2 | last 6 Chrome versions 3 | last 6 Firefox versions 4 | last 6 Edge versions 5 | last 2 Samsung versions 6 | Firefox ESR 7 | Safari >= 11 8 | iOS >= 11 -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | .env 3 | .flaskenv 4 | .git 5 | .github 6 | .mypy_cache 7 | .nvmrc 8 | .python-version 9 | .vscode 10 | **/__pycache__ 11 | **/.gitignore 12 | **/dist 13 | CHANGELOG.md 14 | CODE_OF_CONDUCT.md 15 | compose.yml 16 | CONTRIBUTING.md 17 | env 18 | LICENSE 19 | README.md 20 | requirements_dev.in 21 | requirements_dev.txt 22 | requirements.in 23 | setup.cfg 24 | tests 25 | venv -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | echo "Running DB migrations..." 5 | flask db upgrade 6 | 7 | # Dynamic worker count (default: 2×CPU + 1) 8 | : "${WORKERS:=$(( $(nproc) * 2 + 1 ))}" 9 | : "${BIND_ADDR:=0.0.0.0:5000}" 10 | : "${ACCESS_LOG:=-}" 11 | 12 | echo "Starting Gunicorn on $BIND_ADDR with $WORKERS workers..." 13 | exec gunicorn --bind "$BIND_ADDR" -w "$WORKERS" --access-logfile "$ACCESS_LOG" govuk-frontend-flask:app 14 | -------------------------------------------------------------------------------- /web/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "eslint/config"; 2 | import globals from "globals"; 3 | import js from "@eslint/js"; 4 | import eslintConfigPrettier from "eslint-config-prettier/flat"; 5 | 6 | export default defineConfig([ 7 | { files: ["**/*.{js,mjs,cjs}"] }, 8 | { 9 | files: ["**/*.{js,mjs,cjs}"], 10 | languageOptions: { globals: globals.browser }, 11 | }, 12 | { 13 | files: ["**/*.{js,mjs,cjs}"], 14 | plugins: { js }, 15 | extends: ["js/recommended"], 16 | }, 17 | eslintConfigPrettier, 18 | ]); 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /app/templates/main/429.html: -------------------------------------------------------------------------------- 1 | {%- extends "base.html" -%} 2 | 3 | {%- block pageTitle -%}Too many requests – {{config['SERVICE_NAME']}} – GOV.UK{%- endblock -%} 4 | 5 | {%- set mainClasses = "govuk-main-wrapper--l" -%} 6 | 7 | {%- block content -%} 8 |
There have been too many attempts to access this page.
12 |Try again later.
13 |You will be able to use the service from 9am on Monday 19 November 2018.
12 |If you typed the web address, check it is correct.
12 |If you pasted the web address, check you copied the entire address.
13 |Try again later.
12 |We saved your answers. They will be available for 30 days.
13 |This is a template Flask 12 | app using the GOV.UK Frontend and 13 | GOV.UK Design System which is designed to 14 | get a new project started quicker. 15 |
16 |It is also the reference implementation of two core packages:
17 |The app is provided intentionally bare, with just the essential parts that all services need, 24 | such as error pages, accessibility statement, cookie banner, cookie page and privacy notice.
25 | 26 |A number of other packages are used to provide the features listed below with sensible and 28 | best-practice defaults:
29 |Detailed documentation on the features listed above and the next steps to start building out your app on top of this template is on GitHub
45 |Cookies are small files saved on your phone, tablet or computer when you visit a website. 22 |
23 |We use cookies to make {{config['SERVICE_NAME']}} work and collect information about how you use our 24 | service.
25 | 26 |Essential cookies keep your information secure while you use {{config['SERVICE_NAME']}}. We do not need 28 | to ask permission to use them.
29 | {{ govukTable( 30 | { 31 | 'head': [{'text': 'Name'}, {'text': 'Purpose'}, {'text': 'Expires'}], 32 | 'rows': [ 33 | [{'text': 'cookie_policy'}, {'text': 'Saves your cookie consent settings'}, {'text': '1 year'}], 34 | [{'text': 'session'}, {'text': 'Temporary storage'}, {'text': 'Session'}] 35 | ] 36 | } 37 | )}} 38 | 39 |Functional cookies allow you to take advantage of some functionality, for example remembering 41 | settings between visits. The service will work without them.
42 | {{ govukTable( 43 | { 44 | 'head': [{'text': 'Name'}, {'text': 'Purpose'}, {'text': 'Expires'}], 45 | 'rows': [ 46 | [{'text': 'foo'}, {'text': 'bar'}, {'text': 'baz'}] 47 | ] 48 | } 49 | )}} 50 | 51 |With your permission, we use Google Analytics to collect data about how you use {{config['SERVICE_NAME']}}. This 53 | information helps us to improve our service.
54 |Google is not allowed to use or share our analytics data with anyone.
55 |Google Analytics stores anonymised information about:
56 |We use some essential cookies to make this service work.
24 |We’d like to set additional cookies so we can remember your settings, understand how people use the service and make improvements.
25 | {%- endset -%} 26 | 27 | {%- set acceptHtml -%} 28 |You’ve accepted additional cookies. You can change your cookie settings at any time.
29 | {%- endset -%} 30 | 31 | {%- set rejectHtml -%} 32 |You’ve rejected additional cookies. You can change your cookie settings at any time.
33 | {%- endset -%} 34 | 35 | {{ govukCookieBanner({ 36 | 'ariaLabel': "Cookies on " ~ config['SERVICE_NAME'], 37 | 'attributes': { 38 | 'id': "cookie-banner" 39 | }, 40 | 'messages': [ 41 | { 42 | 'attributes': { 43 | 'id': "default-message" 44 | }, 45 | 'headingText': "Cookies on " ~ config['SERVICE_NAME'], 46 | 'html': html, 47 | 'actions': [ 48 | { 49 | 'attributes': { 50 | 'id': "accept-cookies" 51 | }, 52 | 'text': "Accept additional cookies", 53 | 'type': "button", 54 | 'name': "cookies", 55 | 'value': "accept" 56 | }, 57 | { 58 | 'attributes': { 59 | 'id': "reject-cookies" 60 | }, 61 | 'text': "Reject additional cookies", 62 | 'type': "button", 63 | 'name': "cookies", 64 | 'value': "reject" 65 | }, 66 | { 67 | 'text': "View cookies", 68 | 'href': url_for('main.cookies') 69 | } 70 | ] 71 | }, 72 | { 73 | 'attributes': { 74 | 'id': "accepted-message" 75 | }, 76 | 'html': acceptHtml, 77 | 'role': "alert", 78 | 'hidden': true, 79 | 'actions': [ 80 | { 81 | 'attributes': { 82 | 'id': "accepted-hide" 83 | }, 84 | 'text': "Hide this message" 85 | } 86 | ] 87 | }, 88 | { 89 | 'attributes': { 90 | 'id': "rejected-message" 91 | }, 92 | 'html': rejectHtml, 93 | 'role': "alert", 94 | 'hidden': true, 95 | 'actions': [ 96 | { 97 | 'attributes': { 98 | 'id': "rejected-hide" 99 | }, 100 | 'text': "Hide this message" 101 | } 102 | ] 103 | } 104 | ] 105 | }) }} 106 | {%- endif -%} 107 | {%- endblock -%} 108 | 109 | {%- block header -%} 110 | {{ govukHeader({}) }} 111 | {{ govukServiceNavigation({ 112 | 'serviceName': config['SERVICE_NAME'], 113 | 'serviceUrl': url_for('main.index'), 114 | 'navigation': [ 115 | { 116 | 'href': "#", 117 | 'text': "Navigation item 1" 118 | }, 119 | { 120 | 'href': "#", 121 | 'text': "Navigation item 2", 122 | 'active': true 123 | }, 124 | { 125 | 'href': "#", 126 | 'text': "Navigation item 3" 127 | } 128 | ] 129 | }) }} 130 | {%- endblock -%} 131 | 132 | {%- block beforeContent -%} 133 | {{ govukPhaseBanner({ 134 | 'tag': { 135 | 'text': config['SERVICE_PHASE'] 136 | }, 137 | 'html': 'This is a new service – your feedback will help us to improve it.' 138 | }) }} 139 | {%- endblock -%} 140 | 141 | {%- block content -%} 142 | {%- if form and form.errors -%} 143 | {{ govukErrorSummary(wtforms_errors(form)) }} 144 | {%- endif -%} 145 | 146 | {%- with messages = get_flashed_messages(with_categories=true) -%} 147 | {%- if messages -%} 148 | {%- for category, message in messages -%} 149 | {{ govukNotificationBanner({'type': category, 'html': message}) }} 150 | {%- endfor -%} 151 | {%- endif -%} 152 | {%- endwith -%} 153 | {%- endblock -%} 154 | 155 | {%- block footer -%} 156 | {{ govukFooter({ 157 | 'meta': { 158 | 'items': [ 159 | { 160 | 'href': url_for('main.accessibility'), 161 | 'text': "Accessibility" 162 | }, 163 | { 164 | 'href': url_for('main.cookies'), 165 | 'text': "Cookies" 166 | }, 167 | { 168 | 'href': url_for('main.privacy'), 169 | 'text': "Privacy" 170 | } 171 | ], 172 | 'html': 'Built by ' + config['DEPARTMENT_NAME'] + '' 173 | } 174 | }) }} 175 | {%- endblock -%} 176 | 177 | {%- block bodyEnd -%} 178 | 179 | {%- endblock -%} -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/git,node,sass,linux,macos,windows,jetbrains+all,visualstudiocode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=git,node,sass,linux,macos,windows,jetbrains+all,visualstudiocode 3 | 4 | ### Git ### 5 | # Created by git for backups. To disable backups in Git: 6 | # $ git config --global mergetool.keepBackup false 7 | *.orig 8 | 9 | # Created by git when using merge tools for conflicts 10 | *.BACKUP.* 11 | *.BASE.* 12 | *.LOCAL.* 13 | *.REMOTE.* 14 | *_BACKUP_*.txt 15 | *_BASE_*.txt 16 | *_LOCAL_*.txt 17 | *_REMOTE_*.txt 18 | 19 | ### JetBrains+all ### 20 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 21 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 22 | 23 | # User-specific stuff 24 | .idea/**/workspace.xml 25 | .idea/**/tasks.xml 26 | .idea/**/usage.statistics.xml 27 | .idea/**/dictionaries 28 | .idea/**/shelf 29 | 30 | # AWS User-specific 31 | .idea/**/aws.xml 32 | 33 | # Generated files 34 | .idea/**/contentModel.xml 35 | 36 | # Sensitive or high-churn files 37 | .idea/**/dataSources/ 38 | .idea/**/dataSources.ids 39 | .idea/**/dataSources.local.xml 40 | .idea/**/sqlDataSources.xml 41 | .idea/**/dynamic.xml 42 | .idea/**/uiDesigner.xml 43 | .idea/**/dbnavigator.xml 44 | 45 | # Gradle 46 | .idea/**/gradle.xml 47 | .idea/**/libraries 48 | 49 | # Gradle and Maven with auto-import 50 | # When using Gradle or Maven with auto-import, you should exclude module files, 51 | # since they will be recreated, and may cause churn. Uncomment if using 52 | # auto-import. 53 | # .idea/artifacts 54 | # .idea/compiler.xml 55 | # .idea/jarRepositories.xml 56 | # .idea/modules.xml 57 | # .idea/*.iml 58 | # .idea/modules 59 | # *.iml 60 | # *.ipr 61 | 62 | # CMake 63 | cmake-build-*/ 64 | 65 | # Mongo Explorer plugin 66 | .idea/**/mongoSettings.xml 67 | 68 | # File-based project format 69 | *.iws 70 | 71 | # IntelliJ 72 | out/ 73 | 74 | # mpeltonen/sbt-idea plugin 75 | .idea_modules/ 76 | 77 | # JIRA plugin 78 | atlassian-ide-plugin.xml 79 | 80 | # Cursive Clojure plugin 81 | .idea/replstate.xml 82 | 83 | # SonarLint plugin 84 | .idea/sonarlint/ 85 | 86 | # Crashlytics plugin (for Android Studio and IntelliJ) 87 | com_crashlytics_export_strings.xml 88 | crashlytics.properties 89 | crashlytics-build.properties 90 | fabric.properties 91 | 92 | # Editor-based Rest Client 93 | .idea/httpRequests 94 | 95 | # Android studio 3.1+ serialized cache file 96 | .idea/caches/build_file_checksums.ser 97 | 98 | ### JetBrains+all Patch ### 99 | # Ignore everything but code style settings and run configurations 100 | # that are supposed to be shared within teams. 101 | 102 | .idea/* 103 | 104 | !.idea/codeStyles 105 | !.idea/runConfigurations 106 | 107 | ### Linux ### 108 | *~ 109 | 110 | # temporary files which can be created if a process still has a handle open of a deleted file 111 | .fuse_hidden* 112 | 113 | # KDE directory preferences 114 | .directory 115 | 116 | # Linux trash folder which might appear on any partition or disk 117 | .Trash-* 118 | 119 | # .nfs files are created when an open file is removed but is still being accessed 120 | .nfs* 121 | 122 | ### macOS ### 123 | # General 124 | .DS_Store 125 | .AppleDouble 126 | .LSOverride 127 | 128 | # Icon must end with two \r 129 | Icon 130 | 131 | 132 | # Thumbnails 133 | ._* 134 | 135 | # Files that might appear in the root of a volume 136 | .DocumentRevisions-V100 137 | .fseventsd 138 | .Spotlight-V100 139 | .TemporaryItems 140 | .Trashes 141 | .VolumeIcon.icns 142 | .com.apple.timemachine.donotpresent 143 | 144 | # Directories potentially created on remote AFP share 145 | .AppleDB 146 | .AppleDesktop 147 | Network Trash Folder 148 | Temporary Items 149 | .apdisk 150 | 151 | ### macOS Patch ### 152 | # iCloud generated files 153 | *.icloud 154 | 155 | ### Node ### 156 | # Logs 157 | logs 158 | *.log 159 | npm-debug.log* 160 | yarn-debug.log* 161 | yarn-error.log* 162 | lerna-debug.log* 163 | .pnpm-debug.log* 164 | 165 | # Diagnostic reports (https://nodejs.org/api/report.html) 166 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 167 | 168 | # Runtime data 169 | pids 170 | *.pid 171 | *.seed 172 | *.pid.lock 173 | 174 | # Directory for instrumented libs generated by jscoverage/JSCover 175 | lib-cov 176 | 177 | # Coverage directory used by tools like istanbul 178 | coverage 179 | *.lcov 180 | 181 | # nyc test coverage 182 | .nyc_output 183 | 184 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 185 | .grunt 186 | 187 | # Bower dependency directory (https://bower.io/) 188 | bower_components 189 | 190 | # node-waf configuration 191 | .lock-wscript 192 | 193 | # Compiled binary addons (https://nodejs.org/api/addons.html) 194 | build/Release 195 | 196 | # Dependency directories 197 | node_modules/ 198 | jspm_packages/ 199 | 200 | # Snowpack dependency directory (https://snowpack.dev/) 201 | web_modules/ 202 | 203 | # TypeScript cache 204 | *.tsbuildinfo 205 | 206 | # Optional npm cache directory 207 | .npm 208 | 209 | # Optional eslint cache 210 | .eslintcache 211 | 212 | # Optional stylelint cache 213 | .stylelintcache 214 | 215 | # Microbundle cache 216 | .rpt2_cache/ 217 | .rts2_cache_cjs/ 218 | .rts2_cache_es/ 219 | .rts2_cache_umd/ 220 | 221 | # Optional REPL history 222 | .node_repl_history 223 | 224 | # Output of 'npm pack' 225 | *.tgz 226 | 227 | # Yarn Integrity file 228 | .yarn-integrity 229 | 230 | # dotenv environment variable files 231 | .env 232 | .env.development.local 233 | .env.test.local 234 | .env.production.local 235 | .env.local 236 | 237 | # parcel-bundler cache (https://parceljs.org/) 238 | .cache 239 | .parcel-cache 240 | 241 | # Next.js build output 242 | .next 243 | out 244 | 245 | # Nuxt.js build / generate output 246 | .nuxt 247 | dist 248 | 249 | # Gatsby files 250 | .cache/ 251 | # Comment in the public line in if your project uses Gatsby and not Next.js 252 | # https://nextjs.org/blog/next-9-1#public-directory-support 253 | # public 254 | 255 | # vuepress build output 256 | .vuepress/dist 257 | 258 | # vuepress v2.x temp and cache directory 259 | .temp 260 | 261 | # Docusaurus cache and generated files 262 | .docusaurus 263 | 264 | # Serverless directories 265 | .serverless/ 266 | 267 | # FuseBox cache 268 | .fusebox/ 269 | 270 | # DynamoDB Local files 271 | .dynamodb/ 272 | 273 | # TernJS port file 274 | .tern-port 275 | 276 | # Stores VSCode versions used for testing VSCode extensions 277 | .vscode-test 278 | 279 | # yarn v2 280 | .yarn/cache 281 | .yarn/unplugged 282 | .yarn/build-state.yml 283 | .yarn/install-state.gz 284 | .pnp.* 285 | 286 | ### Node Patch ### 287 | # Serverless Webpack directories 288 | .webpack/ 289 | 290 | # Optional stylelint cache 291 | 292 | # SvelteKit build / generate output 293 | .svelte-kit 294 | 295 | ### Sass ### 296 | .sass-cache/ 297 | *.css.map 298 | *.sass.map 299 | *.scss.map 300 | 301 | ### VisualStudioCode ### 302 | .vscode/* 303 | !.vscode/settings.json 304 | !.vscode/tasks.json 305 | !.vscode/launch.json 306 | !.vscode/extensions.json 307 | !.vscode/*.code-snippets 308 | 309 | # Local History for Visual Studio Code 310 | .history/ 311 | 312 | # Built Visual Studio Code Extensions 313 | *.vsix 314 | 315 | ### VisualStudioCode Patch ### 316 | # Ignore all local history of files 317 | .history 318 | .ionide 319 | 320 | ### Windows ### 321 | # Windows thumbnail cache files 322 | Thumbs.db 323 | Thumbs.db:encryptable 324 | ehthumbs.db 325 | ehthumbs_vista.db 326 | 327 | # Dump file 328 | *.stackdump 329 | 330 | # Folder config file 331 | [Dd]esktop.ini 332 | 333 | # Recycle Bin used on file shares 334 | $RECYCLE.BIN/ 335 | 336 | # Windows Installer files 337 | *.cab 338 | *.msi 339 | *.msix 340 | *.msm 341 | *.msp 342 | 343 | # Windows shortcuts 344 | *.lnk 345 | 346 | # End of https://www.toptal.com/developers/gitignore/api/git,node,sass,linux,macos,windows,jetbrains+all,visualstudiocode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 | 3 | # GOV.UK Frontend - Flask App Template 4 | 5 | Start building **accessible**, **secure**, **production-ready** and **maintainable** GOV.UK-style services, fast. 6 | 7 | A [Flask](https://flask.palletsprojects.com) application integrating the [GOV.UK Design System](https://design-system.service.gov.uk/) with a realistic, containerised stack. 8 | 9 | > **GOV.UK Frontend Flask App Template is a [community tool](https://design-system.service.gov.uk/community/resources-and-tools/) of the [GOV.UK Design System](https://design-system.service.gov.uk/). The Design System team is not responsible for it and cannot support you with using it. Contact the [maintainers](#contributors) directly if you need [help](#support) or you want to request a feature.** 10 | 11 | ## Highlights 12 | 13 | - **GOV.UK components built in** – Accessible [Jinja templates](https://github.com/LandRegistry/govuk-frontend-jinja) and [WTForms helpers](https://github.com/LandRegistry/govuk-frontend-wtf) for compliant UI and forms. 14 | - **Secure Flask foundation** – HTTPS, CSRF, CSP, rate limits, [SQLAlchemy](https://www.sqlalchemy.org/) and migrations ready to go. 15 | - **Containerised by default** – [Nginx](https://nginx.org/en/) , [PostgreSQL](https://www.postgresql.org/), [Redis](https://redis.io/) and [Node](https://nodejs.org/en) pipeline managed via [Docker Compose](https://docs.docker.com/compose/). 16 | - **Fast, lean builds** – Multi-stage Dockerfiles, wheel caching, non-root runtime, and CI via [GitHub Actions](https://github.com/features/actions). 17 | - **Compliance-ready pages** – 404/500 errors, cookie banner, accessibility statement and privacy notice included. 18 | - **Developer-first setup** – Example blueprints, templates, macros, and GOV.UK-style flash messages for instant feedback. 19 | 20 | ## Security 21 | 22 | Secure by default with hardened containers, strong HTTP headers and built-in rate limiting. 23 | 24 | - Applies strict CSP, HSTS, and other security headers. 25 | - CSRF protection via Flask-WTF, with safe error handling. 26 | - Rate limiting backed by Redis using Flask-Limiter. 27 | - Non-root containers with read-only filesystem for runtime services. 28 | - Secrets and credentials injected via environment variables (no in-repo secrets). 29 | - Dependency scanning and Python version pinning via CI workflows. 30 | 31 | ## Performance 32 | 33 | Optimised for speed and reliability through caching, minimal layers and lean builds. 34 | 35 | - Multi-stage Docker builds minimise image size and attack surface. 36 | - Static assets compiled once and cached efficiently. 37 | - Connection pooling for SQLAlchemy database access. 38 | - Redis caching support for transient or computed data. 39 | - Nginx configured for compression and cache control. 40 | - CI validates image build times and wheel caching efficiency. 41 | 42 | ## Developer Experience 43 | 44 | Built to feel frictionless for rapid iteration, testing and deployment. 45 | 46 | - Works identically across local and production environments. 47 | - Uses docker compose watch for hot reloads of Python and static assets. 48 | - Includes blueprints, forms, templates and example routes to extend quickly. 49 | - Built-in error pages, logging and debug toolbar (development mode). 50 | - Extensive comments and .env.example for easy onboarding. 51 | - CI workflows for linting, tests, builds and security scans. 52 | 53 | ## Requirements 54 | 55 | - Docker (Engine & Compose) 56 | 57 | ## Quick start 58 | 59 | ### 1. Create a new repository 60 | 61 | [Create a new repository](https://github.com/LandRegistry/govuk-frontend-flask/generate) using this template, with the same directory structure and files. Then clone a local copy of your newly created repository. 62 | 63 | ### 2. Configure environment 64 | 65 | Create a `.env` file in the root of the repo and enter your specific config based on this example: 66 | 67 | ```dotenv 68 | CONTACT_EMAIL=[contact email] 69 | CONTACT_PHONE=[contact phone] 70 | DEPARTMENT_NAME=[name of department] 71 | DEPARTMENT_URL=[url of department] 72 | POSTGRES_DB=db 73 | POSTGRES_HOST=db 74 | POSTGRES_PASSWORD=db_password 75 | POSTGRES_PORT=5432 76 | POSTGRES_USER=db_user 77 | REDIS_HOST=cache 78 | REDIS_PORT=6379 79 | SECRET_KEY=[see below] 80 | SERVICE_NAME=[name of service] 81 | SERVICE_PHASE=[phase] 82 | SERVICE_URL=[url of service] 83 | ``` 84 | 85 | You **must** set a new `SECRET_KEY`, which is used to securely sign the session cookie and CSRF tokens. It should be a long random `bytes` or `str`. You can use the output of this Python command to generate a new key: 86 | 87 | ```shell 88 | python -c 'import secrets; print(secrets.token_hex())' 89 | ``` 90 | 91 | ### 3. Start the stack 92 | 93 | ```shell 94 | docker compose up --build 95 | ``` 96 | 97 | VisitThis accessibility statement applies to {{config['SERVICE_URL']}}.
22 | 23 |This website is run by {{config['DEPARTMENT_NAME']}}. We want as many people as possible to be able 24 | to use this website. For example, that means you should be able to:
25 |We’ve also made the website text as simple as possible to understand.
34 |AbilityNet has advice on making your device easier to use if you have a disability.
35 | 36 |We know some parts of this website are not fully accessible:
39 |If you need information on this website in a different format like accessible PDF, large 52 | print, easy read, audio recording or braille:
53 |We’ll consider your request and get back to you in [number] days.
59 |If you cannot view the map on our ‘contact us’ page, call or email us [add link to contact 60 | details page] for directions.
61 | 62 |We’re always looking to improve the accessibility of this website. If you find any problems 65 | not listed on this page or think we’re not meeting accessibility requirements, contact: [provide both details of 66 | how to report these issues to your organisation, and contact details for the unit or person responsible for 67 | dealing with these reports].
68 | 69 |The Equality and Human Rights Commission (EHRC) is responsible for enforcing the Public Sector 72 | Bodies (Websites and Mobile Applications) (No. 2) Accessibility Regulations 2018 (the ‘accessibility 73 | regulations’). If you’re not happy with how we respond to your complaint, contact the Equality Advisory and 75 | Support Service (EASS).
76 | 77 |We provide a text relay service for people who are D/deaf, hearing impaired or have a speech 80 | impediment.
81 |Our offices have audio induction loops, or if you contact us before your visit we can arrange 82 | a British Sign Language (BSL) interpreter.
83 |Find out how to contact us [add link to contact details page].
84 | 85 |{{config['DEPARTMENT_NAME']}} is committed to making its website accessible, in accordance with the 88 | Public Sector Bodies (Websites and Mobile Applications) (No. 2) Accessibility Regulations 2018.
89 | 90 |This website is fully compliant with the Web Content Accessibility Guidelines version 2.1 AA 93 | standard.
94 |This website is partially compliant with the Web Content Accessibility Guidelines version 2.1 95 | AA standard, due to [insert one of the following: ‘the non-compliances’, ‘the exemptions’ or ‘the non-compliances 96 | and exemptions’] listed below.
97 |This website is not compliant with the Web Content Accessibility Guidelines version 2.1 AA 98 | standard. The [insert one of the following: ‘non-compliances’, ‘exemptions’ or ‘non-compliances and exemptions’] 99 | are listed below.
100 | 101 |The content listed below is non-accessible for the following reasons.
104 | 105 |Some images do not have a text alternative, so people using a screen reader cannot access the 108 | information. This fails WCAG 2.1 success criterion 1.1.1 (non-text content).
109 | 110 |We plan to add text alternatives for all images by September 2020. When we publish new content 111 | we’ll make sure our use of images meets accessibility standards.
112 | 113 |There’s no way to skip the repeated content in the page header (for example, a ‘skip to main 118 | content’ option).
119 | 120 |It’s not always possible to change the device orientation from horizontal to vertical without 121 | making it more difficult to view the content.
122 | 123 |It’s not possible for users to change text size without some of the content overlapping.
124 | 125 |Some of our interactive forms are difficult to navigate using a keyboard. For example, because 128 | some form controls are missing a ‘label’ tag.
129 | 130 |Our forms are built and hosted through third party software and ‘skinned’ to look like our 131 | website.
132 | 133 |We’ve assessed the cost of fixing the issues with navigation and accessing information, and 134 | with interactive tools and transactions. We believe that doing so now would be a disproportionate burden within 135 | the meaning of the accessibility regulations. We will make another assessment when the supplier contract is up for 136 | renewal, likely to be in [rough timing].
137 | 138 |Some of our PDFs and Word documents are essential to providing our services. For example, we 143 | have PDFs with information on how users can access our services, and forms published as Word documents. By 144 | September 2020, we plan to either fix these or replace them with accessible HTML pages.
145 | 146 |The accessibility regulations do not require us to fix PDFs or other documents published 147 | before 23 September 2018 if they’re not essential to providing our services. For example, we do not plan to fix 148 | [example of non-essential document].
149 | 150 |Any new PDFs or Word documents we publish will meet accessibility standards.
151 | 152 |We do not plan to add captions to live video streams because live video is exempt from meeting 155 | the accessibility regulations.
156 | 157 |Our accessibility roadmap [add link to roadmap] shows how and when we plan to improve 160 | accessibility on this website.
161 | 162 |This statement was prepared on [date when it was first published]. It was last reviewed on 165 | [date when it was last reviewed].
166 | 167 |This website was last tested on [date]. The test was carried out by [add name of organisation 168 | that carried out test, or indicate that you did your own testing].
169 | 170 |We used this approach to deciding on a sample of pages to test [add link to explanation of how 171 | you decided which pages to test].
172 | 173 |You can read the full accessibility test report [add link to report].
174 |