Browse Source

initialisation depuis le cookiecutter

main
Antoine 7 months ago
commit
0420e04eae
  1. 11
      .babelrc.json
  2. 14
      .browserslistrc
  3. 26
      .editorconfig
  4. 41
      .eslintrc.json
  5. 3
      .gitattributes
  6. 42
      .gitignore
  7. 5
      .prettierrc
  8. 11
      .stylelintrc.json
  9. 8
      CHANGELOG.md
  10. 1
      CONTRIBUTORS.txt
  11. 661
      LICENSE
  12. 150
      Makefile
  13. 181
      README.md
  14. 37
      assets/js/main.js
  15. 13
      assets/js/vendor/bootstrap.js
  16. 30
      assets/scss/abstracts/_variables.scss
  17. 3
      assets/scss/base/_fonts.scss
  18. 9
      assets/scss/components/_forms.scss
  19. 20
      assets/scss/main.scss
  20. 53
      assets/scss/vendor/_bootstrap.scss
  21. 105
      config.env.example
  22. 27
      manage.py
  23. 51
      package.json
  24. 26
      pyproject.toml
  25. 1
      radiomoulins/__init__.py
  26. 1
      radiomoulins/core/__init__.py
  27. 6
      radiomoulins/core/apps.py
  28. 0
      radiomoulins/core/tests/__init__.py
  29. 24
      radiomoulins/settings/__init__.py
  30. 269
      radiomoulins/settings/base.py
  31. 50
      radiomoulins/settings/development.py
  32. 113
      radiomoulins/settings/production.py
  33. 54
      radiomoulins/settings/test.py
  34. 7
      radiomoulins/templates/404.html
  35. 7
      radiomoulins/templates/500.html
  36. 44
      radiomoulins/templates/base.html
  37. 40
      radiomoulins/urls.py
  38. 21
      radiomoulins/wsgi.py
  39. 3
      requirements.txt
  40. 8
      requirements/base.txt
  41. 6
      requirements/development.txt
  42. 3
      requirements/production.txt
  43. 15
      requirements/test.txt
  44. 40
      setup.cfg
  45. 169
      styleguide/cheatsheet.css
  46. 46
      styleguide/cheatsheet.js
  47. 1735
      styleguide/index.html
  48. 157
      webpack.config.js

11
.babelrc.json

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
{
"presets": [
[
"@babel/env",
{
"useBuiltIns": "usage",
"corejs": "3.9"
}
]
]
}

14
.browserslistrc

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
# Browsers that we support
# see: https://github.com/browserslist/browserslist#readme
>= 0.5%
last 2 major versions
not dead
Chrome >= 60
Firefox >= 60
not Edge < 79
Firefox ESR
iOS >= 10
Safari >= 10
Android >= 6
not Explorer <= 11

26
.editorconfig

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{py,rst,ini}]
indent_style = space
indent_size = 4
[*.py]
line_length = 80
[*.{html,css,scss,js,json,yml}]
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

41
.eslintrc.json

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
{
"root": true,
"parser": "@babel/eslint-parser",
"extends": [
"xo/browser",
"plugin:prettier/recommended"
],
"rules": {
"capitalized-comments": "off",
"indent": [
"error",
2,
{
"MemberExpression": "off",
"SwitchCase": 1
}
],
"max-params": [
"warn",
5
],
"multiline-ternary": [
"error",
"always-multiline"
],
"new-cap": [
"error",
{
"properties": false
}
],
"object-curly-spacing": [
"error",
"always"
],
"quote-props": [
"error",
"consistent-as-needed"
]
}
}

3
.gitattributes vendored

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
radiomoulins/static/** -diff
assets/img/** -diff
assets/fonts/** -diff

42
.gitignore vendored

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
# Editors
*~
*.sw[po]
# Python
*.py[cod]
__pycache__
# Virtual environment
.env
venv
# Logs
logs
*.log
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
htmlcov
# Translations
*.mo
*.pot
# Builds
radiomoulins/static/
docs/build/
# NPM
node_modules/
# Databases
sqlite.db
# Local configuration
config.env
# Local overrides and variable content
local/
var/

5
.prettierrc

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
{
"arrowParens": "always",
"quoteProps": "consistent",
"singleQuote": true
}

11
.stylelintrc.json

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
{
"extends": [
"stylelint-config-twbs-bootstrap/scss",
"stylelint-prettier/recommended"
],
"rules": {
"scss/at-function-named-arguments": null,
"scss/at-mixin-argumentless-call-parentheses": "never",
"scss/at-rule-conditional-no-parentheses": true
}
}

8
CHANGELOG.md

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
# Changelog
All notable changes to radiomoulins will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added

1
CONTRIBUTORS.txt

@ -0,0 +1 @@ @@ -0,0 +1 @@
This software is developped by Cliss XXI.

661
LICENSE

File diff suppressed because it is too large Load Diff

150
Makefile

@ -0,0 +1,150 @@ @@ -0,0 +1,150 @@
# -*- mode: makefile-gmake -*-
## Définition des variables
# Le nom de l'exécutable Python à utiliser ou son chemin absolu
# (ex. : python ou python3).
PYTHON_EXE := python3
# S'il faut utiliser un environnement virtuel (y ou n).
USE_VENV := y
# Configuration de l'environnement virtuel.
VENV_DIR := venv
VENV_OPT := --system-site-packages
# Définis les chemins et options des exécutables.
PYTHON_EXE_BASENAME := $(shell basename $(PYTHON_EXE))
VENV_PYTHON := --python=$(PYTHON_EXE_BASENAME)
ifeq ($(USE_VENV), y)
PYTHON := $(VENV_DIR)/bin/$(PYTHON_EXE_BASENAME)
PIP := $(VENV_DIR)/bin/pip
else
PYTHON := $(shell which $(PYTHON_EXE))
PIP := $(shell which pip)
endif
# Détermine l'environnement à utiliser.
ifndef ENV
ifdef DJANGO_SETTINGS_MODULE
ENV = $(shell echo $(DJANGO_SETTINGS_MODULE) | cut -d. -f3)
else
DEFAULT_ENV := production
ENV = $(shell \
sed -n '/^ENV/s/[^=]*=\(.*\)/\1/p' config.env 2> /dev/null \
| tail -n 1 | grep -Ee '^..*' || echo "$(DEFAULT_ENV)")
endif
endif
# Définis EDITOR pour l'édition interactive.
ifndef EDITOR
ifdef VISUAL
EDITOR := $(VISUAL)
else
EDITOR := vi
endif
endif
# Définition des cibles -------------------------------------------------------
.PHONY: clean-pyc clean-build clean-static clear-venv help check check-config
.DEFAULT_GOAL := help
# Commentaire d'une cible : #-> interne ##-> aide production+dev ###-> aide dev
help: ## affiche cette aide
ifeq ($(ENV), production)
@perl -nle'print $& if m{^[a-zA-Z_-]+:[^#]*?## .*$$}' $(MAKEFILE_LIST) \
| sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
else
@perl -nle'print $& if m{^[a-zA-Z_-]+:[^#]*?###? .*$$}' $(MAKEFILE_LIST) \
| sort | awk 'BEGIN {FS = ":.*?###? "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}'
endif
clean: clean-build clean-pyc clean-static ## nettoie tous les fichiers temporaires
clean-build: ### nettoie les fichiers de construction du paquet
rm -rf build/
rm -rf dist/
rm -rf *.egg-info
clean-pyc: ### nettoie les fichiers temporaires python
find radiomoulins/ \
\( -name '*.pyc' -o -name '*.pyo' -o -name '*~' \) -exec rm -f {} +
clean-static: ### nettoie les fichiers "static" collectés
rm -rf var/static
init: create-venv config.env ## initialise l'environnement et l'application
@$(MAKE) --no-print-directory update
config.env:
cp config.env.example config.env
chmod go-rwx config.env
$(EDITOR) config.env
update: check-config install-deps migrate static ## mets à jour l'application et ses dépendances
touch radiomoulins/wsgi.py
check: check-config ## vérifie la configuration de l'instance
$(PYTHON) manage.py check
check-config:
@find . -maxdepth 1 -name config.env -perm /o+rwx -exec false {} + || \
{ echo "\033[31mErreur :\033[0m les permissions de config.env ne sont pas bonnes, \
vous devriez au moins faire : chmod o-rwx config.env"; false; }
install-deps: ## installe les dépendances de l'application
$(PIP) install --upgrade --requirement requirements/$(ENV).txt
migrate: ## mets à jour le schéma de la base de données
$(PYTHON) manage.py migrate
static: ## collecte les fichiers statiques
ifeq ($(ENV), production)
@echo "Collecte des fichiers statiques..."
$(PYTHON) manage.py collectstatic --no-input --verbosity 0
endif
## Cibles liées à l'environnement virtuel
create-venv: $(PYTHON)
$(PYTHON):
ifeq ($(USE_VENV), y)
virtualenv $(VENV_OPT) $(VENV_PYTHON) $(VENV_DIR)
else
@echo "\033[31mErreur !\033[0m Impossible de trouver l'exécutable Python $(PYTHON)"
@exit 1
endif
clear-venv: ## supprime l'environnement virtuel
-rm -rf $(VENV_DIR)
## Cibles pour le développement
serve: ### démarre un serveur local pour l'application
$(PYTHON) manage.py runserver
test: ### lance les tests de l'application
$(PYTHON) -m pytest --cov --cov-report=term:skip-covered
test-wip: #### lance les tests marqués 'wip'
$(PYTHON) -m pytest -vv -m 'wip' --pdb
test-failed: #### lance les tests qui ont échoué
$(PYTHON) -m pytest --lf
coverage: test ### vérifie la couverture de code
$(PYTHON) -m coverage html
@echo open htmlcov/index.html
lint: ### vérifie la syntaxe du code Python
@$(PYTHON) -m flake8 radiomoulins || \
{ echo "\033[31mErreur !\033[0m Veuillez corriger la syntaxe avec : make format"; false; }
format: ### formate le code Python
$(PYTHON) -m isort radiomoulins
$(PYTHON) -m black radiomoulins
shell: ### lance un shell Python dans l'environnement
ifeq ($(ENV), production)
$(PYTHON) manage.py shell
else
$(PYTHON) manage.py shell_plus
endif

181
README.md

@ -0,0 +1,181 @@ @@ -0,0 +1,181 @@
# radiomoulins
Le site web de radiomoulins
**Table of content**
- [Give a try](#give-a-try)
- [Installation](#installation)
- [Deployment](#deployment)
- [Structure](#structure)
- [Development](#development)
## Give a try
On a Debian-based host - running at least Debian Stretch:
```shell
$ sudo apt install python3 virtualenv git make
$ git clone https://forge.cliss21.org/cliss21/radiomoulins
$ cd radiomoulins/
$ make init
# A configuration file will be created interactively; you can uncomment:
# ENV=development
$ make serve
```
Then visit [http://127.0.0.1:8000/](http://127.0.0.1:8000/) in your web browser.
## Installation
### Requirements
On a Debian-based host - running at least Debian Stretch, you will need the
following packages:
- `python3`
- `virtualenv`
- `make`
- `git` (recommended for getting the source)
- `python3-mysqldb` (optional, in case of a MySQL / MariaDB database)
- `python3-psycopg2` (optional, in case of a PostgreSQL database)
### Quick start
It assumes that you already have the application source code locally - the best
way is by cloning this repository - and that you are in this folder.
1. Define your local configuration in a file named `config.env`, which can be
copied from `config.env.example` and edited to suits your needs.
Depending on your environment, you will have to create your database and the
user at first.
2. Run `make init`.
Note that if there is no `config.env` file, it will be created interactively.
That's it! Your environment is now initialized with the application installed.
To update it, once the source code is checked out, simply run `make update`.
You can also check that your application is well configured by running
`make check`.
## Deployment
### Web application
Here is an example deployment using NGINX - as the Web server - and uWSGI - as
the application server.
#### uWSGI
The uWSGI configuration doesn't require a special configuration, except that we
are using Python 3 and a virtual environment. Note that if you serve the
application on a sub-location, you will have to add `route-run = fixpathinfo:`
to your uWSGI configuration (available since [v2.0.11][1]).
[1]: https://uwsgi-docs.readthedocs.io/en/latest/Changelog-2.0.11.html#fixpathinfo-routing-action
#### NGINX
In the `server` block of your NGINX configuration, add the following blocks and
set the path to your application instance and to the uWSGI socket:
```nginx
location / {
include uwsgi_params;
uwsgi_pass unix:<uwsgi_socket_path>;
}
location /media {
alias <app_instance_path>/var/media;
}
location /static {
alias <app_instance_path>/var/static;
# Optional: don't log access to assets
access_log off;
}
location = /favicon.ico {
alias <app_instance_path>/var/static/favicon/favicon.ico;
# Optional: don't log access to the favicon
access_log off;
}
```
## Structure
### Overview
All the application files - e.g. Django code including settings, templates and
statics - are located into `radiomoulins/`.
Two environments are defined - either for requirements and settings:
- `development`: for local application development and testing. It uses a
SQLite3 database and enable debugging by default, add some useful settings
and applications for development purpose - i.e. the `django-debug-toolbar`.
- `production`: for production. It checks that configuration is set and
correct, try to optimize performances and enforce some settings - i.e. HTTPS
related ones.
### Local changes
You can override and extend statics and templates locally. This can be useful
if you have to change the logo for a specific instance for example. For that,
just put your files under the `local/static/` and `local/templates/` folders.
Regarding the statics, do not forget to collect them after that. Note also that
the `local/` folder is ignored by *git*.
### Variable content
All the variable content - e.g. user-uploaded media, collected statics - are
stored inside the `var/` folder. It is also ignored by *git* as it's specific
to each application installation.
So, you will have to configure your Web server to serve the `var/media/` and
`var/static/` folders, which should point to `/media/` and `/static/`,
respectively.
## Development
The easiest way to deploy a development environment is by using the `Makefile`.
Before running `make init`, ensure that you have either set `ENV=development`
in the `config.env` file or have this environment variable. Note that you can
still change this variable later and run `make init` again.
There is some additional rules when developing, which are mainly wrappers for
`manage.py`. You can list all of them by running `make help`. Here are the main ones:
- `make serve`: run a development server
- `make test`: test the whole application
- `make lint`: check the Python code syntax
### Assets
The assets - e.g. CSS, JavaScript, images, fonts - are generated using a
[Webpack](https://webpack.js.org/)-powered build system with these features:
- SCSS compilation and prefixing
- JavaScript module bundling and code splitting
- Built-in development server
- Compression for production builds
You will need to have Node.js >= 12.13.0 and npm installed. Note that this is
included in Debian since Bullseye through `nodejs` and `npm` packages.
Start by installing the application dependencies - which are defined in
`package.json` - by running: `npm install`.
The following tasks are then available:
- `npm run build`: build all the assets for development and production use,
and put them in the static folder - e.g `radiomoulins/static`.
- `npm run dev`: run a proxy server to the app - which must already be served on
`localhost:8000` - with the styleguide on `/styleguide` and watch for file
changes.
- `npm run lint`: lint the JavaScript and the SCSS code.
In production, only the static files will be used. It is recommended to commit
the compiled assets just before a new release only. This will prevent to have a
growing repository due to the minified files.
## License
radiomoulins is developed by Cliss XXI and licensed under the
[AGPLv3+](LICENSE).

37
assets/js/main.js

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
// eslint-disable-next-line no-undef,camelcase
__webpack_public_path__ = window.STATIC_URL || '/';
import './vendor/bootstrap';
import Popover from 'bootstrap/popover';
import Toast from 'bootstrap/toast';
import Tooltip from 'bootstrap/tooltip';
import SelectorEngine from 'bootstrap/dom/selector-engine';
/**
* ------------------------------------------------------------------------
* Main application
* ------------------------------------------------------------------------
*/
window.addEventListener('load', () => {
document.documentElement.classList.remove('no-js');
// Initialize Popover, Tooltip and Toast components
SelectorEngine.find('[data-bs-toggle="popover"]').forEach(
(popover) => new Popover(popover)
);
SelectorEngine.find('[data-bs-toggle="tooltip"]').forEach(
(tooltip) => new Tooltip(tooltip)
);
SelectorEngine.find('.toast').forEach((toastNode) => {
const toast = new Toast(toastNode, {
autohide: false,
});
toast.show();
});
});

13
assets/js/vendor/bootstrap.js vendored

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
// -----------------------------------------------------------------------------
// Bootstrap v5
// -----------------------------------------------------------------------------
// see: https://getbootstrap.com/docs/5.0/getting-started/javascript/
import 'bootstrap/alert';
import 'bootstrap/button';
import 'bootstrap/carousel';
import 'bootstrap/collapse';
import 'bootstrap/dropdown';
import 'bootstrap/modal';
import 'bootstrap/scrollspy';
import 'bootstrap/tab';

30
assets/scss/abstracts/_variables.scss

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
// -----------------------------------------------------------------------------
// Application-wide variables
// -----------------------------------------------------------------------------
// Path to fonts and images folders, relative to app.scss
$font-path: '../fonts';
$img-path: '../img';
//
// Bootstrap
//
// see: ../../node_modules/bootstrap/scss/_variables.scss
// Color system
// You could generate a color scheme with: https://palx.jxnblk.com
$blue: #0d6efd;
$indigo: #6610f2;
$purple: #6f42c1;
$pink: #d63384;
$red: #dc3545;
$orange: #fd7e14;
$yellow: #ffc107;
$green: #198754;
$teal: #20c997;
$cyan: #0dcaf0;
// Options
$enable-validation-icons: false;

3
assets/scss/base/_fonts.scss

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
// -----------------------------------------------------------------------------
// Font faces declarations
// -----------------------------------------------------------------------------

9
assets/scss/components/_forms.scss

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
// -----------------------------------------------------------------------------
// Forms component's extension
// -----------------------------------------------------------------------------
// Indicate that a form field is required.
.required {
font-size: 90%;
color: $danger;
}

20
assets/scss/main.scss

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
@charset "utf-8";
// Configuration and helpers
@import 'abstracts/variables';
// Vendors
@import 'vendor/bootstrap';
// Base styles
@import 'base/fonts';
// Layout-related sections
//@import "layout/header";
//@import "layout/footer";
// Components
@import 'components/forms';
// Page-specific styles
//@import "pages/home";

53
assets/scss/vendor/_bootstrap.scss vendored

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
// ----------------------------------------------------------------------------
// Bootstrap v5
// ----------------------------------------------------------------------------
// see: https://getbootstrap.com/docs/5.0/customize/sass/
// Import all Bootstrap components
@import '~bootstrap/scss/bootstrap';
// or import them individually
// Configuration
//@import "~bootstrap/scss/functions";
//@import "~bootstrap/scss/variables";
//@import "~bootstrap/scss/mixins";
//@import "~bootstrap/scss/utilities";
// Layout & components
//@import "~bootstrap/scss/root";
//@import "~bootstrap/scss/reboot";
//@import "~bootstrap/scss/type";
//@import "~bootstrap/scss/images";
//@import "~bootstrap/scss/containers";
//@import "~bootstrap/scss/grid";
//@import "~bootstrap/scss/tables";
//@import "~bootstrap/scss/forms";
//@import "~bootstrap/scss/buttons";
//@import "~bootstrap/scss/transitions";
//@import "~bootstrap/scss/dropdown";
//@import "~bootstrap/scss/button-group";
//@import "~bootstrap/scss/nav";
//@import "~bootstrap/scss/navbar";
//@import "~bootstrap/scss/card";
//@import "~bootstrap/scss/accordion";
//@import "~bootstrap/scss/breadcrumb";
//@import "~bootstrap/scss/pagination";
//@import "~bootstrap/scss/badge";
//@import "~bootstrap/scss/alert";
//@import "~bootstrap/scss/progress";
//@import "~bootstrap/scss/list-group";
//@import "~bootstrap/scss/close";
//@import "~bootstrap/scss/toasts";
//@import "~bootstrap/scss/modal";
//@import "~bootstrap/scss/tooltip";
//@import "~bootstrap/scss/popover";
//@import "~bootstrap/scss/carousel";
//@import "~bootstrap/scss/spinners";
// Helpers
//@import "~bootstrap/scss/helpers";
// Utilities
//@import "~bootstrap/scss/utilities/api";

105
config.env.example

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
###########################################################
# #
# Edit the following configuration to suits your needs. #
# #
###########################################################
###############################################################################
# MAINS SETTINGS
###############################################################################
# Environment to use within the application.
#
# The environment is used to load the proper settings for your application
# instance. There is two ways for defining it, with the following precedence:
# - DJANGO_SETTINGS_MODULE: the Python path to the settings module to use. It
# allows you to define and use your own settings module. Use it with care!
# Note: the module name will be used as the environment.
# - ENV: the environment to use, which is one of 'production' or 'development'.
#
# Default is the 'production' environment.
#ENV=production
#ENV=development
# The secret key used to provide cryptographic signing.
#
# It should be set to a unique, unpredictable value. On a GNU/Linux system, you
# could generate a new one with:
#
# $ head -c50 /dev/urandom | base64
#
# /!\ Required in production.
#DJANGO_SECRET_KEY=CHANGEME!!!
# A coma-separated string representing the host/domain names that this
# application instance can serve.
#
# /!\ Required in production.
#DJANGO_ALLOWED_HOSTS=example.org,
###############################################################################
# DATABASE SETTINGS
###############################################################################
# Database configuration, as an URI.
#
# In production, the recommended database backend for better performances is
# PostgreSQL - or MySQL if you prefer.
#
# Default is a SQLite database in development only.
#
# /!\ Required in production.
#DJANGO_DATABASE_URL=postgres://user:password@127.0.0.1:5432/radiomoulins
#DJANGO_DATABASE_URL=mysql://user:password@127.0.0.1:3306/radiomoulins
###############################################################################
# EMAILS SETTINGS
###############################################################################
# Email configuration for sending messages, as an URI.
#
# In production, you should either use a local SMTP server or a relay one. The
# URI will be in that case of the form:
#
# PROTOCOL://[USER:PASSWORD@]HOST[:PORT]
#
# PROTOCOL can be smtp, smtp+ssl or smtp+tls. Note that special characters
# in USER and PASSWORD - e.g. @ - must be escaped. It can be achieve with:
#
# $ python3 -c 'from urllib.parse import quote as q;print(q("USER")+":"+q("PASSWORD"))'
#
# Default is the local SMTP server in production and the console in development.
#DJANGO_EMAIL_URL=smtp://localhost:25
# Default email address to use for various automated correspondence.
#
# /!\ Required in production.
#DEFAULT_FROM_EMAIL=webmaster@example.org
# A comma separated list of all the people who get production error
# notifications, following rfc2822 format
#ADMINS='Cliss XXI <tech@cliss21.com>'
###############################################################################
# MISC SETTINGS
###############################################################################
# URL prefix on which the application is served.
#
# This is used to generate the static and media URLs, but also links to the
# application which require an absolute URL.
#
# Default is '/', e.g. at the domain root.
#APP_LOCATION=/
# Base directory of the app instance, where the local and var folders are
# located.
#
# Default is the current directory.
#BASE_DIR=
# Turn on/off debug mode.
#
# Note that it's always disabled in production.
#DJANGO_DEBUG=off
#DJANGO_DEBUG_TOOLBAR=on

27
manage.py

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
#!/usr/bin/env python
import os
import sys
from radiomoulins.settings import DJANGO_SETTINGS_MODULE
if __name__ == "__main__":
# Set the default settings module to use.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', DJANGO_SETTINGS_MODULE)
try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django # noqa
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv)

51
package.json

@ -0,0 +1,51 @@ @@ -0,0 +1,51 @@
{
"name": "radiomoulins",
"version": "0.1.0",
"description": "Le site web de radiomoulins",
"author": "Cliss XXI <tech@cliss21.com>",
"license": "AGPL-3.0+",
"scripts": {
"build": "webpack --mode production --progress",
"dev": " webpack serve --mode development --progress --open",
"lint:js": "eslint --report-unused-disable-directives assets/js/",
"lint:style": "stylelint \"assets/scss/**/*.scss\"",
"lint": "npm-run-all --parallel lint:style lint:js",
"format": "prettier \"assets/**/*.{js,scss}\" --write"
},
"dependencies": {
"@popperjs/core": "^2.10.1",
"bootstrap": "~5.1.0",
"core-js": "~3.17.2"
},
"devDependencies": {
"@babel/core": "^7.15.5",
"@babel/eslint-parser": "^7.15.4",
"@babel/preset-env": "^7.15.4",
"autoprefixer": "^10.3.4",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-xo": "^0.38.0",
"eslint-plugin-prettier": "^4.0.0",
"mini-css-extract-plugin": "^2.2.2",
"npm-run-all": "^4.1.5",
"postcss": "^8.3.6",
"postcss-loader": "^6.1.1",
"prettier": "^2.3.2",
"sass": "^1.39.0",
"sass-loader": "^12.1.0",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-twbs-bootstrap": "^2.2.3",
"stylelint-prettier": "^1.2.0",
"terser-webpack-plugin": "^5.2.3",
"webpack": "^5.52.0",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.1.0"
},
"private": true
}

26
pyproject.toml

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
[tool.black]
line-length = 79
skip-string-normalization = true
exclude = '''
/(
\.git
| venv
| local
| var
| migrations
| node_modules
| assets
)/
| urls(|_.+|/.+).py
'''
[tool.isort]
profile = 'black'
line_length = 80
known_django = 'django'
known_wagtail = 'wagtail'
known_first_party = 'radiomoulins'
sections = [
'FUTURE', 'STDLIB', 'DJANGO', 'WAGTAIL', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER'
]
skip_glob = '**/migrations/*.py'

1
radiomoulins/__init__.py

@ -0,0 +1 @@ @@ -0,0 +1 @@
__version__ = '0.1.0'

1
radiomoulins/core/__init__.py

@ -0,0 +1 @@ @@ -0,0 +1 @@
default_app_config = 'radiomoulins.core.apps.CoreConfig'

6
radiomoulins/core/apps.py

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
name = 'radiomoulins.core'
verbose_name = "Core"

0
radiomoulins/core/tests/__init__.py

24
radiomoulins/settings/__init__.py

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
import environ
"""The default environment to use."""
DEFAULT_ENVIRONMENT = 'production'
"""The environment variables of the app instance."""
env = environ.Env()
"""Path to the package root - e.g. Django project."""
root_dir = environ.Path(__file__) - 2
"""Path to the base directory of the app instance."""
base_dir = env.path('BASE_DIR', default=str(root_dir - 1))
# Load config.env, OS environment variables will take precedence
env.read_env(str(base_dir.path('config.env')))
"""The Django settings module's name to use."""
DJANGO_SETTINGS_MODULE = env(
'DJANGO_SETTINGS_MODULE',
default='radiomoulins.settings.{}'.format(
env('ENV', default=DEFAULT_ENVIRONMENT)
),
)

269
radiomoulins/settings/base.py

@ -0,0 +1,269 @@ @@ -0,0 +1,269 @@
"""
Django settings for radiomoulins project.
For more information on this file, see
https://docs.djangoproject.com/en/stable/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/stable/ref/settings/
"""
import os.path
from email.utils import getaddresses
from django.contrib.messages import constants as messages
from . import base_dir, env, root_dir
# ENVIRONMENT VARIABLES AND PATHS
# ------------------------------------------------------------------------------
# Local directory used for static and templates overrides
local_dir = base_dir.path('local')
# Directory for variable stuffs, i.e. user-uploaded media
var_dir = base_dir.path('var')
if not os.path.isdir(var_dir()):
os.mkdir(var_dir(), mode=0o755)
# Location on which the application is served
APP_LOCATION = env('APP_LOCATION', default='/')
# GENERAL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#debug
DEBUG = env.bool('DJANGO_DEBUG', default=True)
# Local time zone for this installation
TIME_ZONE = 'Europe/Paris'
# https://docs.djangoproject.com/en/stable/ref/settings/#language-code
LANGUAGE_CODE = 'fr'
# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
SITE_ID = 1
# https://docs.djangoproject.com/en/stable/ref/settings/#use-i18n
USE_I18N = True
# https://docs.djangoproject.com/en/stable/ref/settings/#use-l10n
USE_L10N = True
# https://docs.djangoproject.com/en/stable/ref/settings/#use-tz
USE_TZ = True
# DATABASES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#databases
# https://django-environ.readthedocs.io/en/stable/#supported-types
DATABASES = {
'default': env.db(
'DJANGO_DATABASE_URL',
default='sqlite:///{}'.format(base_dir('sqlite.db')),
)
}
# URLS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#root-urlconf
ROOT_URLCONF = 'radiomoulins.urls'
# https://docs.djangoproject.com/en/stable/ref/settings/#wsgi-application
WSGI_APPLICATION = 'radiomoulins.wsgi.application'
# APP CONFIGURATION
# ------------------------------------------------------------------------------
DJANGO_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
WAGTAIL_APPS = [
'wagtail.contrib.forms',
'wagtail.contrib.redirects',
'wagtail.embeds',
'wagtail.sites',
'wagtail.users',
'wagtail.snippets',
'wagtail.documents',
'wagtail.images',
'wagtail.search',
'wagtail.admin',
'wagtail.core',
'modelcluster',
'taggit',
]
# Project dependencies
THIRD_PARTY_APPS = []
# Project applications
LOCAL_APPS = ['radiomoulins.core']
# https://docs.djangoproject.com/en/stable/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + WAGTAIL_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# PASSWORDS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#password-hashers
PASSWORD_HASHERS = [
# https://docs.djangoproject.com/en/stable/topics/auth/passwords/#using-argon2-with-django
# 'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
]
# https://docs.djangoproject.com/en/stable/topics/auth/passwords/#password-validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': (
'django.contrib.auth.password_validation.'
'UserAttributeSimilarityValidator'
)
},
{
'NAME': (
'django.contrib.auth.password_validation.MinimumLengthValidator'
)
},
{
'NAME': (
'django.contrib.auth.password_validation.'
'CommonPasswordValidator'
)
},
{
'NAME': (
'django.contrib.auth.password_validation.'
'NumericPasswordValidator'
)
},
]
# MIDDLEWARE
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#middleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'wagtail.contrib.redirects.middleware.RedirectMiddleware',
]
# STATIC
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#static-files
STATIC_ROOT = var_dir('static')
# https://docs.djangoproject.com/en/stable/ref/settings/#static-url
STATIC_URL = os.path.join(APP_LOCATION, 'static/')
# https://docs.djangoproject.com/en/stable/ref/settings/#staticfiles-dirs
STATICFILES_DIRS = [root_dir('static')]
if os.path.isdir(local_dir('static')):
STATICFILES_DIRS.insert(0, local_dir('static'))
# https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/#staticfiles-finders
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]
# MEDIA
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#media-root
MEDIA_ROOT = var_dir('media')
# https://docs.djangoproject.com/en/stable/ref/settings/#media-url
MEDIA_URL = os.path.join(APP_LOCATION, 'media/')
# https://docs.djangoproject.com/en/stable/ref/settings/#file-upload-directory-permissions
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
# https://docs.djangoproject.com/en/stable/ref/settings/#file-upload-permissions
FILE_UPLOAD_PERMISSIONS = 0o644
# TEMPLATES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [root_dir('templates')],
'OPTIONS': {
'debug': DEBUG,
'loaders': [
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
},
}
]
if os.path.isdir(local_dir('templates')):
TEMPLATES[0]['DIRS'].insert(0, local_dir('templates'))
# FIXTURES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#fixture-dirs
FIXTURE_DIRS = [root_dir('fixtures')]
# EMAIL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/topics/email/#email-backends
# https://django-environ.readthedocs.io/en/stable/#supported-types
vars().update(env.email_url('DJANGO_EMAIL_URL', default='smtp://localhost:25'))
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='webmaster@localhost')
# Use the same email address for error messages
SERVER_EMAIL = DEFAULT_FROM_EMAIL
# ADMIN
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#admins
ADMINS = getaddresses([env('ADMINS', default='Cliss XXI <tech@cliss21.com>')])
# https://docs.djangoproject.com/en/stable/ref/settings/#managers
MANAGERS = ADMINS
# SESSIONS AND COOKIES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#session-cookie-path
SESSION_COOKIE_PATH = APP_LOCATION
# https://docs.djangoproject.com/en/stable/ref/settings/#csrf-cookie-path
CSRF_COOKIE_PATH = APP_LOCATION
# MESSAGES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#message-tags
MESSAGE_TAGS = {
messages.ERROR: 'danger',
}
# WAGTAIL
# ------------------------------------------------------------------------------
# http://docs.wagtail.io/en/stable/advanced_topics/settings.html
WAGTAIL_SITE_NAME = "radiomoulins"
# Disable Gravatar provider
WAGTAIL_GRAVATAR_PROVIDER_URL = None
# Disable update checking on the dashboard
WAGTAIL_ENABLE_UPDATE_CHECK = False
# ------------------------------------------------------------------------------
# APPLICATION AND 3RD PARTY LIBRARY SETTINGS
# ------------------------------------------------------------------------------

50
radiomoulins/settings/development.py

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
"""
Development settings.
- use Console backend for emails sending by default
- add the django-debug-toolbar
"""
from .base import * # noqa
from .base import INSTALLED_APPS, MIDDLEWARE, env
# GENERAL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#secret-key
SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!')
# https://docs.djangoproject.com/en/stable/ref/settings/#allowed-hosts
ALLOWED_HOSTS = env.list(
'DJANGO_ALLOWED_HOSTS', default=['localhost', '0.0.0.0', '127.0.0.1']
)
# EMAIL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/topics/email/#email-backends
# https://django-environ.readthedocs.io/en/stable/#supported-types
vars().update(env.email_url('DJANGO_EMAIL_URL', default='consolemail://'))
# WAGTAIL
# ------------------------------------------------------------------------------
# http://docs.wagtail.io/en/stable/contributing/styleguide.html
INSTALLED_APPS += ['wagtail.contrib.styleguide']
# ------------------------------------------------------------------------------
# APPLICATION AND 3RD PARTY LIBRARY SETTINGS
# ------------------------------------------------------------------------------
# DJANGO DEBUG TOOLBAR
# ------------------------------------------------------------------------------
# https://django-debug-toolbar.readthedocs.io/en/stable/installation.html
if env.bool('DJANGO_DEBUG_TOOLBAR', default=False):
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
INSTALLED_APPS += ['debug_toolbar']
INTERNAL_IPS = ['127.0.0.1']
DEBUG_TOOLBAR_CONFIG = {
'DISABLE_PANELS': ['debug_toolbar.panels.redirects.RedirectsPanel'],
'SHOW_TEMPLATE_CONTEXT': True,
}
# DJANGO EXTENSIONS
# ------------------------------------------------------------------------------
# https://django-extensions.readthedocs.io/en/stable/index.html
INSTALLED_APPS += ['django_extensions']

113
radiomoulins/settings/production.py

@ -0,0 +1,113 @@ @@ -0,0 +1,113 @@
"""
Production settings.
- validate the configuration
- disable debug mode
- load secret key from environment variables
- set other production configurations
"""
import os
from django.core.exceptions import ImproperlyConfigured
from .base import * # noqa
from .base import TEMPLATES, env, var_dir
# CONFIGURATION VALIDATION
# ------------------------------------------------------------------------------
# Ensure that the database configuration has been set
if not env('DJANGO_DATABASE_URL', default=None):
raise ImproperlyConfigured(
"No database configuration has been set, you should check "
"the value of your DATABASE_URL environment variable."
)
# Ensure that the default email address has been set
if not env('DEFAULT_FROM_EMAIL', default=None):
raise ImproperlyConfigured(
"No default email address has been set, you should check "
"the value of your DEFAULT_FROM_EMAIL environment variable."
)
# GENERAL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#debug
DEBUG = False
# https://docs.djangoproject.com/en/stable/ref/settings/#secret-key
SECRET_KEY = env('DJANGO_SECRET_KEY')
# https://docs.djangoproject.com/en/stable/ref/settings/#allowed-hosts
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=[])
# STATIC
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#static-files
STATICFILES_STORAGE = (
'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
)
# TEMPLATES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#templates
TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
TEMPLATES[0]['OPTIONS']['loaders'] = [
(
'django.template.loaders.cached.Loader',
[
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
)
]
# LOGGING
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/topics/logging/
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(asctime)s - %(levelname)s - %(module)s: %(message)s'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler',
},
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': var_dir('log/radiomoulins.log'),
'formatter': 'verbose',
'when': 'midnight',
'interval': 1,
'backupCount': 30,
},
},
'loggers': {
'django': {
'level': 'WARNING',
'handlers': ['file'],
'propagate': True,
},
'django.request': {
'level': 'WARNING',
'handlers': ['file', 'mail_admins'],
'propagate': True,
},
'radiomoulins': {
'level': 'INFO',
'handlers': ['file', 'mail_admins'],
'propagate': True,
},
},
}
if not os.path.isdir(var_dir('log')):
os.mkdir(var_dir('log'), mode=0o750)
# ------------------------------------------------------------------------------
# APPLICATION AND 3RD PARTY LIBRARY SETTINGS
# ------------------------------------------------------------------------------

54
radiomoulins/settings/test.py

@ -0,0 +1,54 @@ @@ -0,0 +1,54 @@
"""
With these settings, tests run faster.
"""
from .base import * # noqa
from .base import TEMPLATES, env
# GENERAL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#debug
DEBUG = False
# https://docs.djangoproject.com/en/stable/ref/settings/#secret-key
SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!')
# https://docs.djangoproject.com/en/stable/ref/settings/#test-runner
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
# CACHES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#caches
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': '',
}
}
# PASSWORDS
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#password-hashers
PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher']
# TEMPLATES
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#templates
TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
TEMPLATES[0]['OPTIONS']['loaders'] = [
(
'django.template.loaders.cached.Loader',
[
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
],
)
]
# EMAIL
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/stable/ref/settings/#email-backend
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
# https://docs.djangoproject.com/en/stable/ref/settings/#email-host
EMAIL_HOST = 'localhost'
# https://docs.djangoproject.com/en/stable/ref/settings/#email-port
EMAIL_PORT = 1025

7
radiomoulins/templates/404.html

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block title %}Page introuvable{% endblock %}
{% block content %}
<p>La page que vous demandez semble introuvable...</p>
{% endblock %}

7
radiomoulins/templates/500.html

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block title %}Erreur interne{% endblock %}
{% block content %}
<p>Une erreur inattendue est survenue...</p>
{% endblock %}

44
radiomoulins/templates/base.html

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
{% load static %}<!DOCTYPE html>
<html class="no-js" lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>
{% block title %}{{ page.seo_title|default:page.title }}{% endblock %}
{% block title_suffix %}- radiomoulins{% endblock %}
</title>
<meta name="description" content="">
<meta name="keywords" content="">
{% block css %}
<link rel="stylesheet" href="{% static "main.css" %}">
{% endblock %}
<script>
window.STATIC_URL = '{% get_static_prefix %}';
</script>
{% block extra_head %}{% endblock %}
</head>
<body>
<div class="container">
{% if messages %}
{% for message in messages %}
<div class="alert{% if message.tags %} {{ message.tags }}{% endif %}">{{ message }}</div>
{% endfor %}
{% endif %}
{% block content %}
<p>Utilisez ce modèle pour démarrer rapidement une nouvelle application.</p>
{% endblock %}
</div><!-- .container -->
{% block javascript %}
<script src="{% static "main.js" %}"></script>
{% endblock %}
</body>
</html>

40
radiomoulins/urls.py

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
from django.conf import settings
from django.urls import include, path
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.core import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls
urlpatterns = [
# Wagtail's applications
path('admin/', include(wagtailadmin_urls)),
path('documents/', include(wagtaildocs_urls)),
# Local applications
# ...
]
if settings.DEBUG:
from django.conf.urls.static import static
from django.views import defaults as default_views
# This allows the error pages to be debugged during development, just visit
# these url in browser to see how these error pages look like.
urlpatterns += [
path('400/', default_views.bad_request,
kwargs={'exception': Exception('Bad Request!')}),
path('403/', default_views.permission_denied,
kwargs={'exception': Exception('Permission Denied')}),
path('404/', default_views.page_not_found,
kwargs