Format with prettier

This commit is contained in:
Bruno Alla 2023-04-05 20:31:10 +01:00
parent 6832bffef5
commit e99293829b
No known key found for this signature in database
12 changed files with 131 additions and 137 deletions

View File

@ -1,4 +1,4 @@
exclude: "{{cookiecutter.project_slug}}" exclude: '{{cookiecutter.project_slug}}'
default_stages: [commit] default_stages: [commit]
repos: repos:
@ -18,10 +18,10 @@ repos:
- id: detect-private-key - id: detect-private-key
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
rev: "v3.0.0-alpha.6" rev: 'v3.0.0-alpha.6'
hooks: hooks:
- id: prettier - id: prettier
args: ["--tab-width", "2"] args: ['--tab-width', '2', '--single-quote']
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.3.1 rev: v3.3.1

View File

@ -1,4 +1,4 @@
exclude: "^docs/|/migrations/" exclude: '^docs/|/migrations/'
default_stages: [commit] default_stages: [commit]
repos: repos:
@ -44,7 +44,7 @@ repos:
rev: 6.0.0 rev: 6.0.0
hooks: hooks:
- id: flake8 - id: flake8
args: ["--config=setup.cfg"] args: ['--config=setup.cfg']
additional_dependencies: [flake8-isort] additional_dependencies: [flake8-isort]
# sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date # sets up .pre-commit-ci.yaml to ensure pre-commit dependencies stay up to date

View File

@ -8,7 +8,7 @@ version: 2
build: build:
os: ubuntu-22.04 os: ubuntu-22.04
tools: tools:
python: "3.10" python: '3.10'
# Build documentation in the docs/ directory with Sphinx # Build documentation in the docs/ directory with Sphinx
sphinx: sphinx:

View File

@ -18,11 +18,11 @@ Moved to [settings](http://cookiecutter-django.readthedocs.io/en/latest/settings
### Setting Up Your Users ### Setting Up Your Users
- To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go. - To create a **normal user account**, just go to Sign Up and fill out the form. Once you submit it, you'll see a "Verify Your E-mail Address" page. Go to your console to see a simulated email verification message. Copy the link into your browser. Now the user's email should be verified and ready to go.
- To create a **superuser account**, use this command: - To create a **superuser account**, use this command:
$ python manage.py createsuperuser $ python manage.py createsuperuser
For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users. For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users.
@ -56,23 +56,23 @@ This app comes with Celery.
To run a celery worker: To run a celery worker:
``` bash ```bash
cd {{cookiecutter.project_slug}} cd {{cookiecutter.project_slug}}
celery -A config.celery_app worker -l info celery -A config.celery_app worker -l info
``` ```
Please note: For Celery's import magic to work, it is important *where* the celery commands are run. If you are in the same folder with *manage.py*, you should be right. Please note: For Celery's import magic to work, it is important _where_ the celery commands are run. If you are in the same folder with _manage.py_, you should be right.
To run [periodic tasks](https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html), you'll need to start the celery beat scheduler service. You can start it as a standalone process: To run [periodic tasks](https://docs.celeryq.dev/en/stable/userguide/periodic-tasks.html), you'll need to start the celery beat scheduler service. You can start it as a standalone process:
``` bash ```bash
cd {{cookiecutter.project_slug}} cd {{cookiecutter.project_slug}}
celery -A config.celery_app beat celery -A config.celery_app beat
``` ```
or you can embed the beat service inside a worker with the `-B` option (not recommended for production use): or you can embed the beat service inside a worker with the `-B` option (not recommended for production use):
``` bash ```bash
cd {{cookiecutter.project_slug}} cd {{cookiecutter.project_slug}}
celery -A config.celery_app worker -B -l info celery -A config.celery_app worker -B -l info
``` ```

View File

@ -4,7 +4,7 @@ log:
entryPoints: entryPoints:
web: web:
# http # http
address: ":80" address: ':80'
http: http:
# https://docs.traefik.io/routing/entrypoints/#entrypoint # https://docs.traefik.io/routing/entrypoints/#entrypoint
redirections: redirections:
@ -13,18 +13,18 @@ entryPoints:
web-secure: web-secure:
# https # https
address: ":443" address: ':443'
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
flower: flower:
address: ":5555" address: ':5555'
{%- endif %} {%- endif %}
certificatesResolvers: certificatesResolvers:
letsencrypt: letsencrypt:
# https://docs.traefik.io/master/https/acme/#lets-encrypt # https://docs.traefik.io/master/https/acme/#lets-encrypt
acme: acme:
email: "{{ cookiecutter.email }}" email: '{{ cookiecutter.email }}'
storage: /etc/traefik/acme/acme.json storage: /etc/traefik/acme/acme.json
# https://docs.traefik.io/master/https/acme/#httpchallenge # https://docs.traefik.io/master/https/acme/#httpchallenge
httpChallenge: httpChallenge:
@ -34,9 +34,9 @@ http:
routers: routers:
web-secure-router: web-secure-router:
{%- if cookiecutter.domain_name.count('.') == 1 %} {%- if cookiecutter.domain_name.count('.') == 1 %}
rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)" rule: 'Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)'
{%- else %} {%- else %}
rule: "Host(`{{ cookiecutter.domain_name }}`)" rule: 'Host(`{{ cookiecutter.domain_name }}`)'
{%- endif %} {%- endif %}
entryPoints: entryPoints:
- web-secure - web-secure
@ -49,7 +49,7 @@ http:
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
flower-secure-router: flower-secure-router:
rule: "Host(`{{ cookiecutter.domain_name }}`)" rule: 'Host(`{{ cookiecutter.domain_name }}`)'
entryPoints: entryPoints:
- flower - flower
service: flower service: flower
@ -61,9 +61,9 @@ http:
web-media-router: web-media-router:
{%- if cookiecutter.domain_name.count('.') == 1 %} {%- if cookiecutter.domain_name.count('.') == 1 %}
rule: "(Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)) && PathPrefix(`/media/`)" rule: '(Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)) && PathPrefix(`/media/`)'
{%- else %} {%- else %}
rule: "Host(`{{ cookiecutter.domain_name }}`) && PathPrefix(`/media/`)" rule: 'Host(`{{ cookiecutter.domain_name }}`) && PathPrefix(`/media/`)'
{%- endif %} {%- endif %}
entryPoints: entryPoints:
- web-secure - web-secure
@ -79,7 +79,7 @@ http:
# https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders # https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders
# https://docs.djangoproject.com/en/dev/ref/csrf/#ajax # https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
headers: headers:
hostsProxyHeaders: ["X-CSRFToken"] hostsProxyHeaders: ['X-CSRFToken']
services: services:
django: django:

View File

@ -3,29 +3,29 @@
//////////////////////////////// ////////////////////////////////
// Gulp and package // Gulp and package
const { src, dest, parallel, series, watch } = require('gulp') const { src, dest, parallel, series, watch } = require('gulp');
const pjson = require('./package.json') const pjson = require('./package.json');
// Plugins // Plugins
const autoprefixer = require('autoprefixer') const autoprefixer = require('autoprefixer');
const browserSync = require('browser-sync').create() const browserSync = require('browser-sync').create();
const concat = require('gulp-concat') const concat = require('gulp-concat');
const tildeImporter = require('node-sass-tilde-importer'); const tildeImporter = require('node-sass-tilde-importer');
const cssnano = require ('cssnano') const cssnano = require('cssnano');
const imagemin = require('gulp-imagemin') const imagemin = require('gulp-imagemin');
const pixrem = require('pixrem') const pixrem = require('pixrem');
const plumber = require('gulp-plumber') const plumber = require('gulp-plumber');
const postcss = require('gulp-postcss') const postcss = require('gulp-postcss');
const reload = browserSync.reload const reload = browserSync.reload;
const rename = require('gulp-rename') const rename = require('gulp-rename');
const sass = require('gulp-sass')(require('sass')) const sass = require('gulp-sass')(require('sass'));
const spawn = require('child_process').spawn const spawn = require('child_process').spawn;
const uglify = require('gulp-uglify-es').default const uglify = require('gulp-uglify-es').default;
// Relative paths function // Relative paths function
function pathsConfig(appName) { function pathsConfig(appName) {
this.app = `./${pjson.name}` this.app = `./${pjson.name}`;
const vendorsRoot = 'node_modules' const vendorsRoot = 'node_modules';
return { return {
vendorsJs: [ vendorsJs: [
@ -39,10 +39,10 @@ function pathsConfig(appName) {
fonts: `${this.app}/static/fonts`, fonts: `${this.app}/static/fonts`,
images: `${this.app}/static/images`, images: `${this.app}/static/images`,
js: `${this.app}/static/js`, js: `${this.app}/static/js`,
} };
} }
const paths = pathsConfig() const paths = pathsConfig();
//////////////////////////////// ////////////////////////////////
// Tasks // Tasks
@ -51,27 +51,27 @@ const paths = pathsConfig()
// Styles autoprefixing and minification // Styles autoprefixing and minification
function styles() { function styles() {
const processCss = [ const processCss = [
autoprefixer(), // adds vendor prefixes autoprefixer(), // adds vendor prefixes
pixrem(), // add fallbacks for rem units pixrem(), // add fallbacks for rem units
] ];
const minifyCss = [ const minifyCss = [
cssnano({ preset: 'default' }) // minify result cssnano({ preset: 'default' }), // minify result
] ];
return src(`${paths.sass}/project.scss`) return src(`${paths.sass}/project.scss`)
.pipe(sass({ .pipe(
importer: tildeImporter, sass({
includePaths: [ importer: tildeImporter,
paths.sass includePaths: [paths.sass],
] }).on('error', sass.logError),
}).on('error', sass.logError)) )
.pipe(plumber()) // Checks for errors .pipe(plumber()) // Checks for errors
.pipe(postcss(processCss)) .pipe(postcss(processCss))
.pipe(dest(paths.css)) .pipe(dest(paths.css))
.pipe(rename({ suffix: '.min' })) .pipe(rename({ suffix: '.min' }))
.pipe(postcss(minifyCss)) // Minifies the result .pipe(postcss(minifyCss)) // Minifies the result
.pipe(dest(paths.css)) .pipe(dest(paths.css));
} }
// Javascript minification // Javascript minification
@ -80,7 +80,7 @@ function scripts() {
.pipe(plumber()) // Checks for errors .pipe(plumber()) // Checks for errors
.pipe(uglify()) // Minifies the js .pipe(uglify()) // Minifies the js
.pipe(rename({ suffix: '.min' })) .pipe(rename({ suffix: '.min' }))
.pipe(dest(paths.js)) .pipe(dest(paths.js));
} }
// Vendor Javascript minification // Vendor Javascript minification
@ -91,97 +91,91 @@ function vendorScripts() {
.pipe(plumber()) // Checks for errors .pipe(plumber()) // Checks for errors
.pipe(uglify()) // Minifies the js .pipe(uglify()) // Minifies the js
.pipe(rename({ suffix: '.min' })) .pipe(rename({ suffix: '.min' }))
.pipe(dest(paths.js, { sourcemaps: '.' })) .pipe(dest(paths.js, { sourcemaps: '.' }));
} }
// Image compression // Image compression
function imgCompression() { function imgCompression() {
return src(`${paths.images}/*`) return src(`${paths.images}/*`)
.pipe(imagemin()) // Compresses PNG, JPEG, GIF and SVG images .pipe(imagemin()) // Compresses PNG, JPEG, GIF and SVG images
.pipe(dest(paths.images)) .pipe(dest(paths.images));
} }
{%- if cookiecutter.use_async == 'y' -%} {%- if cookiecutter.use_async == 'y' -%}
// Run django server // Run django server
function asyncRunServer() { function asyncRunServer() {
const cmd = spawn('gunicorn', [ const cmd = spawn(
'config.asgi', '-k', 'uvicorn.workers.UvicornWorker', '--reload' 'gunicorn',
], {stdio: 'inherit'} ['config.asgi', '-k', 'uvicorn.workers.UvicornWorker', '--reload'],
) {stdio: 'inherit'},
cmd.on('close', function(code) { );
console.log('gunicorn exited with code ' + code) cmd.on('close', function (code) {
console.log('gunicorn exited with code ' + code);
}) })
} }
{%- else %} {%- else %}
// Run django server // Run django server
function runServer(cb) { function runServer(cb) {
const cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'}) const cmd = spawn('python', ['manage.py', 'runserver'], { stdio: 'inherit' });
cmd.on('close', function(code) { cmd.on('close', function (code) {
console.log('runServer exited with code ' + code) console.log('runServer exited with code ' + code);
cb(code) cb(code);
}) });
} }
{%- endif %} {%- endif %}
// Browser sync server for live reload // Browser sync server for live reload
function initBrowserSync() { function initBrowserSync() {
browserSync.init( browserSync.init(
[ [`${paths.css}/*.css`, `${paths.js}/*.js`, `${paths.templates}/*.html`],
`${paths.css}/*.css`, {
`${paths.js}/*.js`,
`${paths.templates}/*.html`
], {
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
// https://www.browsersync.io/docs/options/#option-open // https://www.browsersync.io/docs/options/#option-open
// Disable as it doesn't work from inside a container // Disable as it doesn't work from inside a container
open: false, open: false,
{%- endif %} {%- endif %}
// https://www.browsersync.io/docs/options/#option-proxy // https://www.browsersync.io/docs/options/#option-proxy
proxy: { proxy: {
{%- if cookiecutter.use_docker == 'n' %} {%- if cookiecutter.use_docker == 'n' %}
target: '127.0.0.1:8000', target: '127.0.0.1:8000',
{%- else %} {%- else %}
target: 'django:8000', target: 'django:8000',
{%- endif %} {%- endif %}
proxyReq: [ proxyReq: [
function(proxyReq, req) { function (proxyReq, req) {
// Assign proxy "host" header same as current request at Browsersync server // Assign proxy 'host' header same as current request at Browsersync server
proxyReq.setHeader('Host', req.headers.host) proxyReq.setHeader('Host', req.headers.host);
} },
] ],
} },
} },
) );
} }
// Watch // Watch
function watchPaths() { function watchPaths() {
watch(`${paths.sass}/*.scss`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, styles) watch(`${paths.sass}/*.scss`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, styles);
watch(`${paths.templates}/**/*.html`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}).on("change", reload) watch(`${paths.templates}/**/*.html`{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}).on('change', reload);
watch([`${paths.js}/*.js`, `!${paths.js}/*.min.js`]{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, scripts).on("change", reload) watch([`${paths.js}/*.js`, `!${paths.js}/*.min.js`]{% if cookiecutter.windows == 'y' %}, { usePolling: true }{% endif %}, scripts).on(
'change',
reload,
);
} }
// Generate all assets // Generate all assets
const generateAssets = parallel( const generateAssets = parallel(styles, scripts, vendorScripts, imgCompression);
styles,
scripts,
vendorScripts,
imgCompression
)
// Set up dev environment // Set up dev environment
const dev = parallel( {%- if cookiecutter.use_docker == 'n' %}
{%- if cookiecutter.use_docker == 'n' %} {%- if cookiecutter.use_async == 'y' %}
{%- if cookiecutter.use_async == 'y' %} const dev = parallel(asyncRunServer, initBrowserSync, watchPaths);
asyncRunServer, {%- else %}
{%- else %} const dev = parallel(runServer, initBrowserSync, watchPaths);
runServer, {%- endif %}
{%- endif %} {%- else %}
{%- endif %} const dev = parallel(initBrowserSync, watchPaths);
initBrowserSync, {%- endif %}
watchPaths
)
exports.default = series(generateAssets, dev) exports.default = series(generateAssets, dev);
exports["generate-assets"] = generateAssets exports['generate-assets'] = generateAssets;
exports["dev"] = dev exports['dev'] = dev;

View File

@ -25,7 +25,7 @@ services:
- ./.envs/.local/.django - ./.envs/.local/.django
- ./.envs/.local/.postgres - ./.envs/.local/.postgres
ports: ports:
- "8000:8000" - '8000:8000'
command: /start command: /start
postgres: postgres:
@ -53,7 +53,7 @@ services:
- ./config:/app/config:z - ./config:/app/config:z
- ./{{ cookiecutter.project_slug }}:/app/{{ cookiecutter.project_slug }}:z - ./{{ cookiecutter.project_slug }}:/app/{{ cookiecutter.project_slug }}:z
ports: ports:
- "9000:9000" - '9000:9000'
command: /start-docs command: /start-docs
{%- if cookiecutter.use_mailhog == 'y' %} {%- if cookiecutter.use_mailhog == 'y' %}
@ -101,7 +101,7 @@ services:
image: {{ cookiecutter.project_slug }}_local_flower image: {{ cookiecutter.project_slug }}_local_flower
container_name: {{ cookiecutter.project_slug }}_local_flower container_name: {{ cookiecutter.project_slug }}_local_flower
ports: ports:
- "5555:5555" - '5555:5555'
command: /start-flower command: /start-flower
{%- endif %} {%- endif %}
@ -121,10 +121,10 @@ services:
- /app/node_modules - /app/node_modules
command: npm run dev command: npm run dev
ports: ports:
- "3000:3000" - '3000:3000'
{%- if cookiecutter.frontend_pipeline == 'Gulp' %} {%- if cookiecutter.frontend_pipeline == 'Gulp' %}
# Expose browsersync UI: https://www.browsersync.io/docs/options/#option-ui # Expose browsersync UI: https://www.browsersync.io/docs/options/#option-ui
- "3001:3001" - '3001:3001'
{%- endif %} {%- endif %}
{%- endif %} {%- endif %}

View File

@ -60,10 +60,10 @@ services:
volumes: volumes:
- production_traefik:/etc/traefik/acme - production_traefik:/etc/traefik/acme
ports: ports:
- "0.0.0.0:80:80" - '0.0.0.0:80:80'
- "0.0.0.0:443:443" - '0.0.0.0:443:443'
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
- "0.0.0.0:5555:5555" - '0.0.0.0:5555:5555'
{%- endif %} {%- endif %}
redis: redis:

View File

@ -3,20 +3,25 @@ const BundleTracker = require('webpack-bundle-tracker');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = { module.exports = {
target: "web", target: 'web',
context: path.join(__dirname, '../'), context: path.join(__dirname, '../'),
entry: { entry: {
'project': path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/project'), project: path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/project'),
'vendors': path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/vendors'), vendors: path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/js/vendors'),
}, },
output: { output: {
path: path.resolve(__dirname, '../{{cookiecutter.project_slug}}/static/webpack_bundles/'), path: path.resolve(
__dirname,
'../{{cookiecutter.project_slug}}/static/webpack_bundles/',
),
publicPath: '/static/webpack_bundles/', publicPath: '/static/webpack_bundles/',
filename: 'js/[name]-[fullhash].js', filename: 'js/[name]-[fullhash].js',
chunkFilename: 'js/[name]-[hash].js', chunkFilename: 'js/[name]-[hash].js',
}, },
plugins: [ plugins: [
new BundleTracker({filename: path.resolve(__dirname, '../webpack-stats.json')}), new BundleTracker({
filename: path.resolve(__dirname, '../webpack-stats.json'),
}),
new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' }), new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash].css' }),
], ],
module: { module: {
@ -35,11 +40,7 @@ module.exports = {
loader: 'postcss-loader', loader: 'postcss-loader',
options: { options: {
postcssOptions: { postcssOptions: {
plugins: [ plugins: ['postcss-preset-env', 'autoprefixer', 'pixrem'],
'postcss-preset-env',
'autoprefixer',
'pixrem',
],
}, },
}, },
}, },

View File

@ -5,9 +5,9 @@ const commonConfig = require('./common.config');
{%- if cookiecutter.use_whitenoise == 'n' %} {%- if cookiecutter.use_whitenoise == 'n' %}
{%- if cookiecutter.cloud_provider == 'AWS' %} {%- if cookiecutter.cloud_provider == 'AWS' %}
const s3BucketName = process.env.DJANGO_AWS_STORAGE_BUCKET_NAME; const s3BucketName = process.env.DJANGO_AWS_STORAGE_BUCKET_NAME;
const awsS3Domain = process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN ? const awsS3Domain = process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN
process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN ? process.env.DJANGO_AWS_S3_CUSTOM_DOMAIN
: `${s3BucketName}.s3.amazonaws.com`; : `${s3BucketName}.s3.amazonaws.com`;
const staticUrl = `https://${awsS3Domain}/static/`; const staticUrl = `https://${awsS3Domain}/static/`;
{%- elif cookiecutter.cloud_provider == 'GCP' %} {%- elif cookiecutter.cloud_provider == 'GCP' %}
const staticUrl = `https://storage.googleapis.com/${process.env.DJANGO_GCP_STORAGE_BUCKET_NAME}/static/`; const staticUrl = `https://storage.googleapis.com/${process.env.DJANGO_GCP_STORAGE_BUCKET_NAME}/static/`;

View File

@ -1,5 +1,5 @@
{%- if cookiecutter.frontend_pipeline == 'Webpack' %} {%- if cookiecutter.frontend_pipeline == 'Webpack' -%}
import '../sass/project.scss'; import '../sass/project.scss';
{%- endif %}
{% endif -%}
/* Project specific Javascript goes here. */ /* Project specific Javascript goes here. */

View File

@ -1,12 +1,11 @@
@import "custom_bootstrap_vars"; @import 'custom_bootstrap_vars';
@import "~bootstrap/scss/bootstrap"; @import '~bootstrap/scss/bootstrap';
// project specific CSS goes here // project specific CSS goes here
//////////////////////////////// ///////////////
//Variables// // Variables //
//////////////////////////////// ///////////////
// Alert colors // Alert colors
@ -17,9 +16,9 @@ $pink: #f2dede;
$dark-pink: #eed3d7; $dark-pink: #eed3d7;
$red: #b94a48; $red: #b94a48;
//////////////////////////////// ////////////
//Alerts// // Alerts //
//////////////////////////////// ////////////
// bootstrap alert CSS, translated to the django-standard levels of // bootstrap alert CSS, translated to the django-standard levels of
// debug, info, success, warning, error // debug, info, success, warning, error