mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2024-11-28 12:34:01 +03:00
Merge commit 'b4b3c0425359225c8eccf15b687cde156631d3ad'
This commit is contained in:
commit
93a714f9b1
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -226,3 +226,5 @@ pip-selfcheck.json
|
||||||
# to 'run' anything within it since any particular cookiecutter
|
# to 'run' anything within it since any particular cookiecutter
|
||||||
# is declarative by nature.
|
# is declarative by nature.
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
.pytest_cache/
|
||||||
|
|
|
@ -13,15 +13,6 @@ env:
|
||||||
- TOX_ENV=py36
|
- TOX_ENV=py36
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- sudo sh -c 'echo "deb https://apt.dockerproject.org/repo ubuntu-precise main" > /etc/apt/sources.list.d/docker.list'
|
|
||||||
- sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
|
|
||||||
- sudo apt-get update
|
|
||||||
- sudo apt-key update
|
|
||||||
- sudo apt-get --force-yes -qqy -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install docker-engine=1.11.1-0~precise
|
|
||||||
- sudo rm /usr/local/bin/docker-compose
|
|
||||||
- curl -L https://github.com/docker/compose/releases/download/1.7.0/docker-compose-`uname -s`-`uname -m` > docker-compose
|
|
||||||
- chmod +x docker-compose
|
|
||||||
- sudo mv docker-compose /usr/local/bin
|
|
||||||
- docker-compose -v
|
- docker-compose -v
|
||||||
- docker -v
|
- docker -v
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
All enhancements and patches to Cookiecutter Django will be documented in this file.
|
All enhancements and patches to Cookiecutter Django will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [2018-02-16]
|
||||||
|
### Changed
|
||||||
|
- Upgraded to Django 2.0 (@epicwhale)
|
||||||
|
|
||||||
## [2018-01-15]
|
## [2018-01-15]
|
||||||
### Changed
|
### Changed
|
||||||
- Removed Elastic Beanstalk support (@pydanny)
|
- Removed Elastic Beanstalk support (@pydanny)
|
||||||
|
|
|
@ -16,8 +16,9 @@ Fábio C. Barrionuevo da Luz `@luzfcb`_ @luzfcb
|
||||||
Saurabh Kumar `@theskumar`_ @_theskumar
|
Saurabh Kumar `@theskumar`_ @_theskumar
|
||||||
Jannis Gebauer `@jayfk`_
|
Jannis Gebauer `@jayfk`_
|
||||||
Burhan Khalid `@burhan`_ @burhan
|
Burhan Khalid `@burhan`_ @burhan
|
||||||
Shupeyko Nikita `@webyneter`_ @webyneter
|
Nikita Shupeyko `@webyneter`_ @webyneter
|
||||||
Bruno Alla `@browniebroke`_ @_BrunoAlla
|
Bruno Alla `@browniebroke`_ @_BrunoAlla
|
||||||
|
Wan Liuyang `@sfdye`_ @sfdye
|
||||||
=========================== ================ ===========
|
=========================== ================ ===========
|
||||||
|
|
||||||
*Audrey is also the creator of Cookiecutter. Audrey and
|
*Audrey is also the creator of Cookiecutter. Audrey and
|
||||||
|
@ -30,6 +31,7 @@ Daniel are on the Cookiecutter core team.*
|
||||||
.. _@jayfk: https://github.com/jayfk
|
.. _@jayfk: https://github.com/jayfk
|
||||||
.. _@webyneter: https://github.com/webyneter
|
.. _@webyneter: https://github.com/webyneter
|
||||||
.. _@browniebroke: https://github.com/browniebroke
|
.. _@browniebroke: https://github.com/browniebroke
|
||||||
|
.. _@sfdye: https://github.com/sfdye
|
||||||
|
|
||||||
Other Contributors
|
Other Contributors
|
||||||
------------------
|
------------------
|
||||||
|
@ -44,6 +46,7 @@ Listed in alphabetical order.
|
||||||
Aaron Eikenberry `@aeikenberry`_
|
Aaron Eikenberry `@aeikenberry`_
|
||||||
Adam Bogdał `@bogdal`_
|
Adam Bogdał `@bogdal`_
|
||||||
Adam Dobrawy `@ad-m`_
|
Adam Dobrawy `@ad-m`_
|
||||||
|
Adam Steele `@adammsteele`
|
||||||
Agam Dua
|
Agam Dua
|
||||||
Alberto Sanchez `@alb3rto`_
|
Alberto Sanchez `@alb3rto`_
|
||||||
Alex Tsai `@caffodian`_
|
Alex Tsai `@caffodian`_
|
||||||
|
@ -88,6 +91,7 @@ Listed in alphabetical order.
|
||||||
Dong Huynh `@trungdong`_
|
Dong Huynh `@trungdong`_
|
||||||
Emanuel Calso `@bloodpet`_ @bloodpet
|
Emanuel Calso `@bloodpet`_ @bloodpet
|
||||||
Eraldo Energy `@eraldo`_
|
Eraldo Energy `@eraldo`_
|
||||||
|
Eric Groom `@ericgroom`_
|
||||||
Eyad Al Sibai `@eyadsibai`_
|
Eyad Al Sibai `@eyadsibai`_
|
||||||
Felipe Arruda `@arruda`_
|
Felipe Arruda `@arruda`_
|
||||||
Garry Cairns `@garry-cairns`_
|
Garry Cairns `@garry-cairns`_
|
||||||
|
@ -114,6 +118,7 @@ Listed in alphabetical order.
|
||||||
Luis Nell `@originell`_
|
Luis Nell `@originell`_
|
||||||
Lukas Klein
|
Lukas Klein
|
||||||
Lyla Fischer
|
Lyla Fischer
|
||||||
|
Malik Sulaimanov `@flyudvik`_ @flyudvik
|
||||||
Martin Blech
|
Martin Blech
|
||||||
Mathijs Hoogland `@MathijsHoogland`_
|
Mathijs Hoogland `@MathijsHoogland`_
|
||||||
Matt Braymer-Hayes `@mattayes`_ @mattayes
|
Matt Braymer-Hayes `@mattayes`_ @mattayes
|
||||||
|
@ -151,7 +156,6 @@ Listed in alphabetical order.
|
||||||
Travis McNeill `@Travistock`_ @tavistock_esq
|
Travis McNeill `@Travistock`_ @tavistock_esq
|
||||||
Vitaly Babiy
|
Vitaly Babiy
|
||||||
Vivian Guillen `@viviangb`_
|
Vivian Guillen `@viviangb`_
|
||||||
Wan Liuyang `@sfdye`_ @sfdye
|
|
||||||
Will Farley `@goldhand`_ @g01dhand
|
Will Farley `@goldhand`_ @g01dhand
|
||||||
William Archinal `@archinal`_
|
William Archinal `@archinal`_
|
||||||
Yaroslav Halchenko
|
Yaroslav Halchenko
|
||||||
|
@ -159,6 +163,7 @@ Listed in alphabetical order.
|
||||||
|
|
||||||
.. _@a7p: https://github.com/a7p
|
.. _@a7p: https://github.com/a7p
|
||||||
.. _@ad-m: https://github.com/ad-m
|
.. _@ad-m: https://github.com/ad-m
|
||||||
|
.. _@adammsteele: https://github.com/adammsteele
|
||||||
.. _@aeikenberry: https://github.com/aeikenberry
|
.. _@aeikenberry: https://github.com/aeikenberry
|
||||||
.. _@alb3rto: https://github.com/alb3rto
|
.. _@alb3rto: https://github.com/alb3rto
|
||||||
.. _@ameistad: https://github.com/ameistad
|
.. _@ameistad: https://github.com/ameistad
|
||||||
|
@ -193,6 +198,7 @@ Listed in alphabetical order.
|
||||||
.. _@eraldo: https://github.com/eraldo
|
.. _@eraldo: https://github.com/eraldo
|
||||||
.. _@eriol: https://github.com/eriol
|
.. _@eriol: https://github.com/eriol
|
||||||
.. _@eyadsibai: https://github.com/eyadsibai
|
.. _@eyadsibai: https://github.com/eyadsibai
|
||||||
|
.. _@flyudvik: https://github.com/flyudvik
|
||||||
.. _@garry-cairns: https://github.com/garry-cairns
|
.. _@garry-cairns: https://github.com/garry-cairns
|
||||||
.. _@garrypolley: https://github.com/garrypolley
|
.. _@garrypolley: https://github.com/garrypolley
|
||||||
.. _@goldhand: https://github.com/goldhand
|
.. _@goldhand: https://github.com/goldhand
|
||||||
|
@ -229,7 +235,6 @@ Listed in alphabetical order.
|
||||||
.. _@romanosipenko: https://github.com/romanosipenko
|
.. _@romanosipenko: https://github.com/romanosipenko
|
||||||
.. _@shireenrao: https://github.com/shireenrao
|
.. _@shireenrao: https://github.com/shireenrao
|
||||||
.. _@show0k: https://github.com/show0k
|
.. _@show0k: https://github.com/show0k
|
||||||
.. _@sfdye: https://github.com/sfdye
|
|
||||||
.. _@shultz: https://github.com/shultz
|
.. _@shultz: https://github.com/shultz
|
||||||
.. _@siauPatrick: https://github.com/siauPatrick
|
.. _@siauPatrick: https://github.com/siauPatrick
|
||||||
.. _@slafs: https://github.com/slafs
|
.. _@slafs: https://github.com/slafs
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
||||||
Copyright (c) 2013-2018, Daniel Greenfeld
|
Copyright (c) 2013-2018, Daniel Roy Greenfeld
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
|
32
README.rst
32
README.rst
|
@ -16,6 +16,10 @@ Cookiecutter Django
|
||||||
:target: https://www.codetriage.com/pydanny/cookiecutter-django
|
:target: https://www.codetriage.com/pydanny/cookiecutter-django
|
||||||
:alt: Code Helpers Badge
|
:alt: Code Helpers Badge
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||||
|
:target: https://github.com/ambv/black
|
||||||
|
:alt: Code style: black
|
||||||
|
|
||||||
Powered by Cookiecutter_, Cookiecutter Django is a framework for jumpstarting
|
Powered by Cookiecutter_, Cookiecutter Django is a framework for jumpstarting
|
||||||
production-ready Django projects quickly.
|
production-ready Django projects quickly.
|
||||||
|
|
||||||
|
@ -38,10 +42,10 @@ production-ready Django projects quickly.
|
||||||
Features
|
Features
|
||||||
---------
|
---------
|
||||||
|
|
||||||
* For Django 1.11
|
* For Django 2.0
|
||||||
* Works with Python 3.6
|
* Works with Python 3.6
|
||||||
* Renders Django projects with 100% starting test coverage
|
* Renders Django projects with 100% starting test coverage
|
||||||
* Twitter Bootstrap_ v4.0.0 - beta 1 (`maintained Foundation fork`_ also available)
|
* Twitter Bootstrap_ v4.0.0 (`maintained Foundation fork`_ also available)
|
||||||
* 12-Factor_ based settings via django-environ_
|
* 12-Factor_ based settings via django-environ_
|
||||||
* Secure by default. We believe in SSL.
|
* Secure by default. We believe in SSL.
|
||||||
* Optimized development and production settings
|
* Optimized development and production settings
|
||||||
|
@ -68,7 +72,6 @@ Optional Integrations
|
||||||
* Configuration for Celery_
|
* Configuration for Celery_
|
||||||
* Integration with MailHog_ for local email testing
|
* Integration with MailHog_ for local email testing
|
||||||
* Integration with Sentry_ for error logging
|
* Integration with Sentry_ for error logging
|
||||||
* Integration with Opbeat_ for performance monitoring
|
|
||||||
|
|
||||||
.. _Bootstrap: https://github.com/twbs/bootstrap
|
.. _Bootstrap: https://github.com/twbs/bootstrap
|
||||||
.. _django-environ: https://github.com/joke2k/django-environ
|
.. _django-environ: https://github.com/joke2k/django-environ
|
||||||
|
@ -83,7 +86,6 @@ Optional Integrations
|
||||||
.. _MailHog: https://github.com/mailhog/MailHog
|
.. _MailHog: https://github.com/mailhog/MailHog
|
||||||
.. _Sentry: https://sentry.io/welcome/
|
.. _Sentry: https://sentry.io/welcome/
|
||||||
.. _docker-compose: https://github.com/docker/compose
|
.. _docker-compose: https://github.com/docker/compose
|
||||||
.. _Opbeat: https://opbeat.com/
|
|
||||||
.. _PythonAnywhere: https://www.pythonanywhere.com/
|
.. _PythonAnywhere: https://www.pythonanywhere.com/
|
||||||
.. _Caddy: https://caddyserver.com/
|
.. _Caddy: https://caddyserver.com/
|
||||||
.. _LetsEncrypt: https://letsencrypt.org/
|
.. _LetsEncrypt: https://letsencrypt.org/
|
||||||
|
@ -111,7 +113,7 @@ Two Scoops of Django 1.11
|
||||||
:name: Two Scoops of Django 1.11 Cover
|
:name: Two Scoops of Django 1.11 Cover
|
||||||
:align: center
|
:align: center
|
||||||
:alt: Two Scoops of Django
|
:alt: Two Scoops of Django
|
||||||
:target: http://twoscoopspress.org/products/two-scoops-of-django-1-11
|
:target: http://twoscoopspress.com/products/two-scoops-of-django-1-11
|
||||||
|
|
||||||
Two Scoops of Django is the best dessert-themed Django reference in the universe
|
Two Scoops of Django is the best dessert-themed Django reference in the universe
|
||||||
|
|
||||||
|
@ -129,7 +131,7 @@ Pyup brings you automated security and dependency updates used by Google and oth
|
||||||
Usage
|
Usage
|
||||||
------
|
------
|
||||||
|
|
||||||
Let's pretend you want to create a Django project called "redditclone". Rather than using `startproject`
|
Let's pretend you want to create a Django project called "redditclone". Rather than using ``startproject``
|
||||||
and then editing the results to include your name, email, and various configuration issues that always get forgotten until the worst possible moment, get cookiecutter_ to do all the work.
|
and then editing the results to include your name, email, and various configuration issues that always get forgotten until the worst possible moment, get cookiecutter_ to do all the work.
|
||||||
|
|
||||||
First, get Cookiecutter. Trust me, it's awesome::
|
First, get Cookiecutter. Trust me, it's awesome::
|
||||||
|
@ -163,20 +165,20 @@ Answer the prompts with your own desired options_. For example::
|
||||||
use_whitenoise [y]: n
|
use_whitenoise [y]: n
|
||||||
use_celery [n]: y
|
use_celery [n]: y
|
||||||
use_mailhog [n]: n
|
use_mailhog [n]: n
|
||||||
use_sentry_for_error_reporting [y]: y
|
use_sentry [y]: y
|
||||||
use_opbeat [n]: y
|
|
||||||
use_pycharm [n]: y
|
use_pycharm [n]: y
|
||||||
windows [n]: n
|
windows [n]: n
|
||||||
use_docker [y]: n
|
use_docker [y]: n
|
||||||
use_heroku [n]: y
|
use_heroku [n]: y
|
||||||
use_compressor [n]: y
|
use_compressor [n]: y
|
||||||
Select postgresql_version:
|
Select postgresql_version:
|
||||||
1 - 10
|
1 - 10.3
|
||||||
2 - 9.6
|
2 - 10.2
|
||||||
3 - 9.5
|
3 - 10.1
|
||||||
4 - 9.4
|
4 - 9.6
|
||||||
5 - 9.3
|
5 - 9.5
|
||||||
6 - 9.2
|
6 - 9.4
|
||||||
|
7 - 9.3
|
||||||
Choose from 1, 2, 3, 4 [1]: 1
|
Choose from 1, 2, 3, 4 [1]: 1
|
||||||
Select js_task_runner:
|
Select js_task_runner:
|
||||||
1 - Gulp
|
1 - Gulp
|
||||||
|
@ -191,6 +193,8 @@ Answer the prompts with your own desired options_. For example::
|
||||||
4 - Apache Software License 2.0
|
4 - Apache Software License 2.0
|
||||||
5 - Not open source
|
5 - Not open source
|
||||||
Choose from 1, 2, 3, 4, 5 [1]: 1
|
Choose from 1, 2, 3, 4, 5 [1]: 1
|
||||||
|
keep_local_envs_in_vcs [y]: y
|
||||||
|
debug[n]: n
|
||||||
|
|
||||||
Enter the project and take a look around::
|
Enter the project and take a look around::
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,45 @@
|
||||||
{
|
{
|
||||||
"project_name": "Project Name",
|
"project_name": "My Awesome Project",
|
||||||
"project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_') }}",
|
"project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_') }}",
|
||||||
"author_name": "Daniel Roy Greenfeld",
|
"description": "Behold My Awesome Project!",
|
||||||
"email": "you@example.com",
|
"author_name": "Daniel Roy Greenfeld",
|
||||||
"description": "A short description of the project.",
|
"email": "{{ cookiecutter.author_name.lower()|replace(' ', '-') }}@example.com",
|
||||||
"domain_name": "example.com",
|
"domain_name": "example.com",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"timezone": "UTC",
|
"open_source_license": [
|
||||||
"use_whitenoise": "y",
|
"MIT",
|
||||||
"use_celery": "n",
|
"BSD",
|
||||||
"use_mailhog": "n",
|
"GPLv3",
|
||||||
"use_sentry_for_error_reporting": "y",
|
"Apache Software License 2.0",
|
||||||
"use_opbeat": "n",
|
"Not open source"
|
||||||
"use_pycharm": "n",
|
],
|
||||||
"windows": "n",
|
"timezone": "UTC",
|
||||||
"use_docker": "n",
|
"windows": "n",
|
||||||
"use_heroku": "n",
|
"use_pycharm": "n",
|
||||||
"use_compressor": "n",
|
"use_docker": "n",
|
||||||
"postgresql_version": ["10", "9.6", "9.5", "9.4", "9.3", "9.2"],
|
"postgresql_version": [
|
||||||
"js_task_runner": ["Gulp", "Grunt", "None"],
|
"10.3",
|
||||||
"custom_bootstrap_compilation": "n",
|
"10.2",
|
||||||
"open_source_license": ["MIT", "BSD", "GPLv3", "Apache Software License 2.0", "Not open source"]
|
"10.1",
|
||||||
|
"9.6",
|
||||||
|
"9.5",
|
||||||
|
"9.4",
|
||||||
|
"9.3"
|
||||||
|
],
|
||||||
|
"js_task_runner": [
|
||||||
|
"None",
|
||||||
|
"Gulp",
|
||||||
|
"Grunt"
|
||||||
|
],
|
||||||
|
"custom_bootstrap_compilation": "n",
|
||||||
|
"use_compressor": "n",
|
||||||
|
"use_celery": "n",
|
||||||
|
"use_mailhog": "n",
|
||||||
|
"use_sentry": "y",
|
||||||
|
"use_whitenoise": "y",
|
||||||
|
"use_heroku": "n",
|
||||||
|
"use_travisci": "n",
|
||||||
|
"keep_local_envs_in_vcs": "y",
|
||||||
|
|
||||||
|
"debug": "n"
|
||||||
}
|
}
|
||||||
|
|
58
docs/conf.py
58
docs/conf.py
|
@ -29,29 +29,29 @@ now = datetime.now()
|
||||||
extensions = []
|
extensions = []
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
# The suffix of source filenames.
|
# The suffix of source filenames.
|
||||||
source_suffix = '.rst'
|
source_suffix = ".rst"
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
# source_encoding = 'utf-8-sig'
|
# source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = "index"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = 'Cookiecutter Django'
|
project = "Cookiecutter Django"
|
||||||
copyright = "2013-2016, Daniel Roy Greenfeld".format(now.year)
|
copyright = "2013-2018, Daniel Roy Greenfeld".format(now.year)
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '{}.{}.{}'.format(*now.isocalendar())
|
version = "{}.{}.{}".format(*now.isocalendar())
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '{}.{}.{}'.format(*now.isocalendar())
|
release = "{}.{}.{}".format(*now.isocalendar())
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
@ -65,7 +65,7 @@ release = '{}.{}.{}'.format(*now.isocalendar())
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
exclude_patterns = ['_build']
|
exclude_patterns = ["_build"]
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
# default_role = None
|
# default_role = None
|
||||||
|
@ -82,7 +82,7 @@ exclude_patterns = ['_build']
|
||||||
# show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = "sphinx"
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
# modindex_common_prefix = []
|
# modindex_common_prefix = []
|
||||||
|
@ -92,7 +92,7 @@ pygments_style = 'sphinx'
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'default'
|
html_theme = "default"
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
|
@ -121,7 +121,7 @@ html_theme = 'default'
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ["_static"]
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
|
@ -165,7 +165,7 @@ html_static_path = ['_static']
|
||||||
# html_file_suffix = None
|
# html_file_suffix = None
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = 'cookiecutter-djangodoc'
|
htmlhelp_basename = "cookiecutter-djangodoc"
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
@ -173,10 +173,8 @@ htmlhelp_basename = 'cookiecutter-djangodoc'
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
# 'papersize': 'letterpaper',
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
# 'pointsize': '10pt',
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
# 'preamble': '',
|
# 'preamble': '',
|
||||||
}
|
}
|
||||||
|
@ -184,10 +182,13 @@ latex_elements = {
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index',
|
(
|
||||||
'cookiecutter-django.tex',
|
"index",
|
||||||
'cookiecutter-django Documentation',
|
"cookiecutter-django.tex",
|
||||||
'cookiecutter-django', 'manual'),
|
"cookiecutter-django Documentation",
|
||||||
|
"cookiecutter-django",
|
||||||
|
"manual",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
@ -216,8 +217,13 @@ latex_documents = [
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('index', 'Cookiecutter Django', 'Cookiecutter Django documentation',
|
(
|
||||||
['Daniel Roy Greenfeld'], 1)
|
"index",
|
||||||
|
"Cookiecutter Django",
|
||||||
|
"Cookiecutter Django documentation",
|
||||||
|
["Daniel Roy Greenfeld"],
|
||||||
|
1,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
|
@ -230,9 +236,15 @@ man_pages = [
|
||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', 'Cookiecutter Django', 'Cookiecutter Django documentation',
|
(
|
||||||
'Daniel Roy Greenfeld', 'Cookiecutter Django',
|
"index",
|
||||||
'A Cookiecutter template for creating production-ready Django projects quickly.', 'Miscellaneous'),
|
"Cookiecutter Django",
|
||||||
|
"Cookiecutter Django documentation",
|
||||||
|
"Daniel Roy Greenfeld",
|
||||||
|
"Cookiecutter Django",
|
||||||
|
"A Cookiecutter template for creating production-ready Django projects quickly.",
|
||||||
|
"Miscellaneous",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
|
|
|
@ -16,24 +16,23 @@ Run these commands to deploy the project to Heroku:
|
||||||
heroku addons:create heroku-redis:hobby-dev
|
heroku addons:create heroku-redis:hobby-dev
|
||||||
heroku addons:create mailgun
|
heroku addons:create mailgun
|
||||||
|
|
||||||
heroku config:set DJANGO_ADMIN_URL="$(openssl rand -base64 32)"
|
heroku config:set WEB_CONCURRENCY=4
|
||||||
|
# Generating a 32 character-long random string without any of the visually similiar characters "IOl01":
|
||||||
|
heroku config:set DJANGO_ADMIN_URL="^$(openssl rand -base64 4096 | tr -dc 'A-HJ-NP-Za-km-z2-9' | head -c 32)/"
|
||||||
heroku config:set DJANGO_SECRET_KEY="$(openssl rand -base64 64)"
|
heroku config:set DJANGO_SECRET_KEY="$(openssl rand -base64 64)"
|
||||||
heroku config:set DJANGO_SETTINGS_MODULE='config.settings.production'
|
heroku config:set DJANGO_SETTINGS_MODULE=config.settings.production
|
||||||
heroku config:set DJANGO_ALLOWED_HOSTS='.herokuapp.com'
|
heroku config:set DJANGO_ALLOWED_HOSTS='.herokuapp.com'
|
||||||
|
|
||||||
heroku config:set DJANGO_AWS_ACCESS_KEY_ID=YOUR_AWS_ID_HERE
|
heroku config:set DJANGO_AWS_ACCESS_KEY_ID=YOUR_AWS_ID_HERE
|
||||||
heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY_HERE
|
heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY_HERE
|
||||||
heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=YOUR_AWS_S3_BUCKET_NAME_HERE
|
heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=YOUR_AWS_S3_BUCKET_NAME_HERE
|
||||||
|
|
||||||
heroku config:set DJANGO_MAILGUN_SERVER_NAME=YOUR_MALGUN_SERVER
|
# This is to be set only if you're using Sentry:
|
||||||
heroku config:set DJANGO_MAILGUN_API_KEY=YOUR_MAILGUN_API_KEY
|
heroku config:set DJANGO_SENTRY_DSN=YOUR_SENTRY_DSN
|
||||||
heroku config:set MAILGUN_SENDER_DOMAIN=YOUR_MAILGUN_SENDER_DOMAIN
|
|
||||||
|
|
||||||
heroku config:set PYTHONHASHSEED=random
|
heroku config:set PYTHONHASHSEED=random
|
||||||
heroku config:set DJANGO_ADMIN_URL=\^somelocation/
|
|
||||||
|
|
||||||
git push heroku master
|
git push heroku master
|
||||||
heroku run python manage.py migrate
|
|
||||||
heroku run python manage.py check --deploy
|
heroku run python manage.py check --deploy
|
||||||
heroku run python manage.py createsuperuser
|
heroku run python manage.py createsuperuser
|
||||||
heroku open
|
heroku open
|
||||||
|
|
|
@ -63,13 +63,13 @@ Add these exports
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
|
export WEB_CONCURRENCY=4
|
||||||
export DJANGO_SETTINGS_MODULE='config.settings.production'
|
export DJANGO_SETTINGS_MODULE='config.settings.production'
|
||||||
export DJANGO_SECRET_KEY='<secret key goes here>'
|
export DJANGO_SECRET_KEY='<secret key goes here>'
|
||||||
export DJANGO_ALLOWED_HOSTS='<www.your-domain.com>'
|
export DJANGO_ALLOWED_HOSTS='<www.your-domain.com>'
|
||||||
export DJANGO_ADMIN_URL='<not admin/>'
|
export DJANGO_ADMIN_URL='<not admin/>'
|
||||||
export DJANGO_MAILGUN_API_KEY='<mailgun key>'
|
export MAILGUN_API_KEY='<mailgun key>'
|
||||||
export DJANGO_MAILGUN_SERVER_NAME='<mailgun server name>'
|
export MAILGUN_DOMAIN='<mailgun sender domain (e.g. mg.yourdomain.com)>'
|
||||||
export MAILGUN_SENDER_DOMAIN='<mailgun sender domain (e.g. mg.yourdomain.com)>'
|
|
||||||
export DJANGO_AWS_ACCESS_KEY_ID=
|
export DJANGO_AWS_ACCESS_KEY_ID=
|
||||||
export DJANGO_AWS_SECRET_ACCESS_KEY=
|
export DJANGO_AWS_SECRET_ACCESS_KEY=
|
||||||
export DJANGO_AWS_STORAGE_BUCKET_NAME=
|
export DJANGO_AWS_STORAGE_BUCKET_NAME=
|
||||||
|
@ -83,7 +83,7 @@ Database setup:
|
||||||
|
|
||||||
Go to the PythonAnywhere **Databases tab** and configure your database.
|
Go to the PythonAnywhere **Databases tab** and configure your database.
|
||||||
|
|
||||||
* For Postgres, setup your superuser password, then open a Postgres console and run a `CREATE DATABASE my-db-name`. You should probably also set up a specific role and permissions for your app, rather than using the superuser credentials. Make a note of the address and port of your postgres server.
|
* For Postgres, setup your superuser password, then open a Postgres console and run a ``CREATE DATABASE my-db-name``. You should probably also set up a specific role and permissions for your app, rather than using the superuser credentials. Make a note of the address and port of your postgres server.
|
||||||
|
|
||||||
* For MySQL, set the password and create a database. More info here: https://help.pythonanywhere.com/pages/UsingMySQL
|
* For MySQL, set the password and create a database. More info here: https://help.pythonanywhere.com/pages/UsingMySQL
|
||||||
|
|
||||||
|
@ -138,9 +138,8 @@ Click through to the **WSGI configuration file** link (near the top) and edit th
|
||||||
os.environ['DJANGO_SECRET_KEY'] = '<as above>'
|
os.environ['DJANGO_SECRET_KEY'] = '<as above>'
|
||||||
os.environ['DJANGO_ALLOWED_HOSTS'] = '<as above>'
|
os.environ['DJANGO_ALLOWED_HOSTS'] = '<as above>'
|
||||||
os.environ['DJANGO_ADMIN_URL'] = '<as above>'
|
os.environ['DJANGO_ADMIN_URL'] = '<as above>'
|
||||||
os.environ['DJANGO_MAILGUN_API_KEY'] = '<as above>'
|
os.environ['MAILGUN_API_KEY'] = '<as above>'
|
||||||
os.environ['DJANGO_MAILGUN_SERVER_NAME'] = '<as above>'
|
os.environ['MAILGUN_DOMAIN'] = '<as above>'
|
||||||
os.environ['MAILGUN_SENDER_DOMAIN'] = '<as above>'
|
|
||||||
os.environ['DJANGO_AWS_ACCESS_KEY_ID'] = ''
|
os.environ['DJANGO_AWS_ACCESS_KEY_ID'] = ''
|
||||||
os.environ['DJANGO_AWS_SECRET_ACCESS_KEY'] = ''
|
os.environ['DJANGO_AWS_SECRET_ACCESS_KEY'] = ''
|
||||||
os.environ['DJANGO_AWS_STORAGE_BUCKET_NAME'] = ''
|
os.environ['DJANGO_AWS_STORAGE_BUCKET_NAME'] = ''
|
||||||
|
|
|
@ -1,83 +1,82 @@
|
||||||
Deployment with Docker
|
Deployment with Docker
|
||||||
=======================
|
======================
|
||||||
|
|
||||||
|
.. index:: deployment, docker, docker-compose, compose
|
||||||
|
|
||||||
.. index:: Docker, deployment
|
|
||||||
|
|
||||||
Prerequisites
|
Prerequisites
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
* Docker (at least 1.10)
|
* Docker 1.10+.
|
||||||
* Docker Compose (at least 1.6)
|
* Docker Compose 1.6+
|
||||||
|
|
||||||
Understand the Compose Setup
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
Before you start, check out the `production.yml` file in the root of this project. This is where each component
|
Understanding the Docker Compose Setup
|
||||||
of this application gets its configuration from. Notice how it provides configuration for these services:
|
--------------------------------------
|
||||||
|
|
||||||
* `postgres` service that runs the database
|
Before you begin, check out the ``production.yml`` file in the root of this project. Keep note of how it provides configuration for the following services:
|
||||||
* `redis` for caching
|
|
||||||
* `caddy` as webserver
|
|
||||||
* `django` is the Django project run by gunicorn
|
|
||||||
|
|
||||||
If you chose the `use_celery` option, there are two more services:
|
* ``django``: your application running behind ``Gunicorn``;
|
||||||
|
* ``postgres``: PostgreSQL database with the application's relational data;
|
||||||
|
* ``redis``: Redis instance for caching;
|
||||||
|
* ``caddy``: Caddy web server with HTTPS on by default.
|
||||||
|
|
||||||
* `celeryworker` which runs the celery worker process
|
Provided you have opted for Celery (via setting ``use_celery`` to ``y``) there are two more services:
|
||||||
* `celerybeat` which runs the celery beat process
|
|
||||||
|
|
||||||
Populate .env With Your Environment Variables
|
* ``celeryworker`` running a Celery worker process;
|
||||||
---------------------------------------------
|
* ``celerybeat`` running a Celery beat process.
|
||||||
|
|
||||||
Some of these services rely on environment variables set by you. There is an `env.example` file in the
|
|
||||||
root directory of this project as a starting point. Add your own variables to the file and rename it to `.env`. This
|
|
||||||
file won't be tracked by git by default so you'll have to make sure to use some other mechanism to copy your secret if
|
|
||||||
you are relying solely on git.
|
|
||||||
|
|
||||||
It is **highly recommended** that before you build your production application, you set your POSTGRES_USER value here. This will create a non-default user for the postgres image. If you do not set this user before building the application, the default user 'postgres' will be created, and this user will not be able to create or restore backups.
|
Configuring the Stack
|
||||||
|
---------------------
|
||||||
|
|
||||||
To obtain logs and information about crashes in a production setup, make sure that you have access to an external Sentry instance (e.g. by creating an account with `sentry.io`_), and set the `DJANGO_SENTRY_DSN` variable. This should be enough to report crashes to Sentry.
|
The majority of services above are configured through the use of environment variables. Just check out :ref:`envs` and you will know the drill.
|
||||||
|
|
||||||
|
To obtain logs and information about crashes in a production setup, make sure that you have access to an external Sentry instance (e.g. by creating an account with `sentry.io`_), and set the ``DJANGO_SENTRY_DSN`` variable.
|
||||||
|
|
||||||
You will probably also need to setup the Mail backend, for example by adding a `Mailgun`_ API key and a `Mailgun`_ sender domain, otherwise, the account creation view will crash and result in a 500 error when the backend attempts to send an email to the account owner.
|
You will probably also need to setup the Mail backend, for example by adding a `Mailgun`_ API key and a `Mailgun`_ sender domain, otherwise, the account creation view will crash and result in a 500 error when the backend attempts to send an email to the account owner.
|
||||||
|
|
||||||
.. _sentry.io: https://sentry.io/welcome
|
.. _sentry.io: https://sentry.io/welcome
|
||||||
.. _Mailgun: https://mailgun.com
|
.. _Mailgun: https://mailgun.com
|
||||||
|
|
||||||
HTTPS is on by default
|
|
||||||
|
Optional: Use AWS IAM Role for EC2 instance
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
If you are deploying to AWS, you can use the IAM role to substitute AWS credentials, after which it's safe to remove the ``AWS_ACCESS_KEY_ID`` AND ``AWS_SECRET_ACCESS_KEY`` from ``.envs/.production/.django``. To do it, create an `IAM role`_ and `attach`_ it to the existing EC2 instance or create a new EC2 instance with that role. The role should assume, at minimum, the ``AmazonS3FullAccess`` permission.
|
||||||
|
|
||||||
|
.. _IAM role: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
|
||||||
|
.. _attach: https://aws.amazon.com/blogs/security/easily-replace-or-attach-an-iam-role-to-an-existing-ec2-instance-by-using-the-ec2-console/
|
||||||
|
|
||||||
|
|
||||||
|
HTTPS is On by Default
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
SSL (Secure Sockets Layer) is a standard security technology for establishing an encrypted link between a server and a client, typically in this case, a web server (website) and a browser. Not having HTTPS means that malicious network users can sniff authentication credentials between your website and end users' browser.
|
SSL (Secure Sockets Layer) is a standard security technology for establishing an encrypted link between a server and a client, typically in this case, a web server (website) and a browser. Not having HTTPS means that malicious network users can sniff authentication credentials between your website and end users' browser.
|
||||||
|
|
||||||
It is always better to deploy a site behind HTTPS and will become crucial as the web services extend to the IoT (Internet of Things). For this reason, we have set up a number of security defaults to help make your website secure:
|
It is always better to deploy a site behind HTTPS and will become crucial as the web services extend to the IoT (Internet of Things). For this reason, we have set up a number of security defaults to help make your website secure:
|
||||||
|
|
||||||
* In the `.env.example`, we have made it simpler for you to change the default `Django Admin` into a custom name through an environmental variable. This should make it harder to guess the access to the admin panel.
|
* If you are not using a subdomain of the domain name set in the project, then remember to put the your staging/production IP address in the ``DJANGO_ALLOWED_HOSTS`` environment variable (see :ref:`settings`) before you deploy your website. Failure to do this will mean you will not have access to your website through the HTTP protocol.
|
||||||
|
|
||||||
* If you are not using a subdomain of the domain name set in the project, then remember to put the your staging/production IP address in the :code:`DJANGO_ALLOWED_HOSTS` environment variable (see :ref:`settings`) before you deploy your website. Failure to do this will mean you will not have access to your website through the HTTP protocol.
|
|
||||||
|
|
||||||
* Access to the Django admin is set up by default to require HTTPS in production or once *live*.
|
* Access to the Django admin is set up by default to require HTTPS in production or once *live*.
|
||||||
|
|
||||||
|
The Caddy web server used in the default configuration will get you a valid certificate from Lets Encrypt and update it automatically. All you need to do to enable this is to make sure that your DNS records are pointing to the server Caddy runs on.
|
||||||
HTTPS is configured by default
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
The Caddy webserver used in the default configuration will get you a valid certificate from Lets Encrypt and update it automatically. All you need to do to enable this is to make sure that your DNS records are pointing to the server Caddy runs on.
|
|
||||||
|
|
||||||
You can read more about this here at `Automatic HTTPS`_ in the Caddy docs.
|
You can read more about this here at `Automatic HTTPS`_ in the Caddy docs.
|
||||||
|
|
||||||
.. _Automatic HTTPS: https://caddyserver.com/docs/automatic-https
|
.. _Automatic HTTPS: https://caddyserver.com/docs/automatic-https
|
||||||
|
|
||||||
|
|
||||||
Optional: Postgres Data Volume Modifications
|
(Optional) Postgres Data Volume Modifications
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
Postgres is saving its database files to the `postgres_data` volume by default. Change that if you want something else and make sure to make backups since this is not done automatically.
|
Postgres is saving its database files to the ``postgres_data`` volume by default. Change that if you want something else and make sure to make backups since this is not done automatically.
|
||||||
|
|
||||||
Run your app with docker-compose
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
To get started, pull your code from source control (don't forget the `.env` file) and change to your projects root
|
Building & Running Production Stack
|
||||||
directory.
|
-----------------------------------
|
||||||
|
|
||||||
You'll need to build the stack first. To do that, run::
|
You will need to build the stack first. To do that, run::
|
||||||
|
|
||||||
docker-compose -f production.yml build
|
docker-compose -f production.yml build
|
||||||
|
|
||||||
|
@ -87,19 +86,17 @@ Once this is ready, you can run it with::
|
||||||
|
|
||||||
To run a migration, open up a second terminal and run::
|
To run a migration, open up a second terminal and run::
|
||||||
|
|
||||||
docker-compose -f production.yml run django python manage.py migrate
|
docker-compose -f production.yml run --rm django python manage.py migrate
|
||||||
|
|
||||||
To create a superuser, run::
|
To create a superuser, run::
|
||||||
|
|
||||||
docker-compose -f production.yml run django python manage.py createsuperuser
|
docker-compose -f production.yml run --rm django python manage.py createsuperuser
|
||||||
|
|
||||||
If you need a shell, run::
|
If you need a shell, run::
|
||||||
|
|
||||||
docker-compose -f production.yml run django python manage.py shell
|
docker-compose -f production.yml run --rm django python manage.py shell
|
||||||
|
|
||||||
To get an output of all running containers.
|
To check the logs out, run::
|
||||||
|
|
||||||
To check your logs, run::
|
|
||||||
|
|
||||||
docker-compose -f production.yml logs
|
docker-compose -f production.yml logs
|
||||||
|
|
||||||
|
@ -108,21 +105,21 @@ If you want to scale your application, run::
|
||||||
docker-compose -f production.yml scale django=4
|
docker-compose -f production.yml scale django=4
|
||||||
docker-compose -f production.yml scale celeryworker=2
|
docker-compose -f production.yml scale celeryworker=2
|
||||||
|
|
||||||
.. warning:: Don't run the scale command on postgres, celerybeat, or caddy.
|
.. warning:: don't try to scale ``postgres``, ``celerybeat``, or ``caddy``.
|
||||||
|
|
||||||
If you have errors, you can always check your stack with `docker-compose`. Switch to your projects root directory and run::
|
To see how your containers are doing run::
|
||||||
|
|
||||||
docker-compose -f production.yml ps
|
docker-compose -f production.yml ps
|
||||||
|
|
||||||
|
|
||||||
Supervisor Example
|
Example: Supervisor
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
Once you are ready with your initial setup, you want to make sure that your application is run by a process manager to
|
Once you are ready with your initial setup, you want to make sure that your application is run by a process manager to
|
||||||
survive reboots and auto restarts in case of an error. You can use the process manager you are most familiar with. All
|
survive reboots and auto restarts in case of an error. You can use the process manager you are most familiar with. All
|
||||||
it needs to do is to run `docker-compose -f production.yml up` in your projects root directory.
|
it needs to do is to run ``docker-compose -f production.yml up`` in your projects root directory.
|
||||||
|
|
||||||
If you are using `supervisor`, you can use this file as a starting point::
|
If you are using ``supervisor``, you can use this file as a starting point::
|
||||||
|
|
||||||
[program:{{cookiecutter.project_slug}}]
|
[program:{{cookiecutter.project_slug}}]
|
||||||
command=docker-compose -f production.yml up
|
command=docker-compose -f production.yml up
|
||||||
|
@ -132,11 +129,11 @@ If you are using `supervisor`, you can use this file as a starting point::
|
||||||
autorestart=true
|
autorestart=true
|
||||||
priority=10
|
priority=10
|
||||||
|
|
||||||
Place it in `/etc/supervisor/conf.d/{{cookiecutter.project_slug}}.conf` and run::
|
Move it to ``/etc/supervisor/conf.d/{{cookiecutter.project_slug}}.conf`` and run::
|
||||||
|
|
||||||
supervisorctl reread
|
supervisorctl reread
|
||||||
supervisorctl start {{cookiecutter.project_slug}}
|
supervisorctl start {{cookiecutter.project_slug}}
|
||||||
|
|
||||||
To get the status, run::
|
For status check, run::
|
||||||
|
|
||||||
supervisorctl status
|
supervisorctl status
|
||||||
|
|
|
@ -6,23 +6,19 @@ Getting Up and Running Locally With Docker
|
||||||
The steps below will get you up and running with a local development environment.
|
The steps below will get you up and running with a local development environment.
|
||||||
All of these commands assume you are in the root of your generated project.
|
All of these commands assume you are in the root of your generated project.
|
||||||
|
|
||||||
|
|
||||||
Prerequisites
|
Prerequisites
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
You'll need at least Docker 1.10.
|
* Docker; if you don't have it yet, follow the `installation instructions`_;
|
||||||
|
* Docker Compose; refer to the official documentation for the `installation guide`_.
|
||||||
|
|
||||||
If you don't already have it installed, follow the instructions for your OS:
|
.. _`installation instructions`: https://docs.docker.com/install/#supported-platforms
|
||||||
|
.. _`installation guide`: https://docs.docker.com/compose/install/
|
||||||
|
|
||||||
- On Mac OS X, you'll need `Docker for Mac`_
|
|
||||||
- On Windows, you'll need `Docker for Windows`_
|
|
||||||
- On Linux, you'll need `docker-engine`_
|
|
||||||
|
|
||||||
.. _`Docker for Mac`: https://docs.docker.com/engine/installation/mac/
|
Attention, Windows Users
|
||||||
.. _`Docker for Windows`: https://docs.docker.com/engine/installation/windows/
|
------------------------
|
||||||
.. _`docker-engine`: https://docs.docker.com/engine/installation/
|
|
||||||
|
|
||||||
Attention Windows users
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
Currently PostgreSQL (``psycopg2`` python package) is not installed inside Docker containers for Windows users, while it is required by the generated Django project. To fix this, add ``psycopg2`` to the list of requirements inside ``requirements/base.txt``::
|
Currently PostgreSQL (``psycopg2`` python package) is not installed inside Docker containers for Windows users, while it is required by the generated Django project. To fix this, add ``psycopg2`` to the list of requirements inside ``requirements/base.txt``::
|
||||||
|
|
||||||
|
@ -31,23 +27,21 @@ Currently PostgreSQL (``psycopg2`` python package) is not installed inside Docke
|
||||||
|
|
||||||
Doing this will prevent the project from being installed in an Windows-only environment (thus without usage of Docker). If you want to use this project without Docker, make sure to remove ``psycopg2`` from the requirements again.
|
Doing this will prevent the project from being installed in an Windows-only environment (thus without usage of Docker). If you want to use this project without Docker, make sure to remove ``psycopg2`` from the requirements again.
|
||||||
|
|
||||||
|
|
||||||
Build the Stack
|
Build the Stack
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
This can take a while, especially the first time you run this particular command
|
This can take a while, especially the first time you run this particular command on your development system::
|
||||||
on your development system::
|
|
||||||
|
|
||||||
$ docker-compose -f local.yml build
|
$ docker-compose -f local.yml build
|
||||||
|
|
||||||
If you want to build the production environment you use ``production.yml`` as -f argument (``docker-compose.yml`` or ``docker-compose.yaml`` are the defaults).
|
Generally, if you want to emulate production environment use ``production.yml`` instead. And this is true for any other actions you might need to perform: whenever a switch is required, just do it!
|
||||||
|
|
||||||
Boot the System
|
|
||||||
---------------
|
|
||||||
|
|
||||||
This brings up both Django and PostgreSQL.
|
Run the Stack
|
||||||
|
-------------
|
||||||
|
|
||||||
The first time it is run it might take a while to get started, but subsequent
|
This brings up both Django and PostgreSQL. The first time it is run it might take a while to get started, but subsequent runs will occur quickly.
|
||||||
runs will occur quickly.
|
|
||||||
|
|
||||||
Open a terminal at the project root and run the following for local development::
|
Open a terminal at the project root and run the following for local development::
|
||||||
|
|
||||||
|
@ -61,98 +55,117 @@ And then run::
|
||||||
|
|
||||||
$ docker-compose up
|
$ docker-compose up
|
||||||
|
|
||||||
Running management commands
|
To run in a detached (background) mode, just::
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
As with any shell command that we wish to run in our container, this is done
|
$ docker-compose up -d
|
||||||
using the ``docker-compose -f local.yml run`` command.
|
|
||||||
|
|
||||||
To migrate your app and to create a superuser, run::
|
|
||||||
|
|
||||||
$ docker-compose -f local.yml run django python manage.py migrate
|
Execute Management Commands
|
||||||
$ docker-compose -f local.yml run django python manage.py createsuperuser
|
---------------------------
|
||||||
|
|
||||||
Here we specify the ``django`` container as the location to run our management commands.
|
As with any shell command that we wish to run in our container, this is done using the ``docker-compose -f local.yml run --rm`` command: ::
|
||||||
|
|
||||||
Add your Docker development server IP
|
$ docker-compose -f local.yml run --rm django python manage.py migrate
|
||||||
-------------------------------------
|
$ docker-compose -f local.yml run --rm django python manage.py createsuperuser
|
||||||
|
|
||||||
When ``DEBUG`` is set to `True`, the host is validated against ``['localhost', '127.0.0.1', '[::1]']``. This is adequate when running a ``virtualenv``. For Docker, in the ``config.settings.local``, add your host development server IP to ``INTERNAL_IPS`` or ``ALLOWED_HOSTS`` if the variable exists.
|
Here, ``django`` is the target service we are executing the commands against.
|
||||||
|
|
||||||
Production Mode
|
|
||||||
~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Instead of using `local.yml`, you would use `production.yml`.
|
(Optionally) Designate your Docker Development Server IP
|
||||||
|
--------------------------------------------------------
|
||||||
|
|
||||||
Other Useful Tips
|
When ``DEBUG`` is set to ``True``, the host is validated against ``['localhost', '127.0.0.1', '[::1]']``. This is adequate when running a ``virtualenv``. For Docker, in the ``config.settings.local``, add your host development server IP to ``INTERNAL_IPS`` or ``ALLOWED_HOSTS`` if the variable exists.
|
||||||
-----------------
|
|
||||||
|
|
||||||
Make a machine the active unit
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
This tells our computer that all future commands are specifically for the dev1 machine.
|
.. _envs:
|
||||||
Using the ``eval`` command we can switch machines as needed.
|
|
||||||
|
|
||||||
::
|
Configuring the Environment
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
This is the excerpt from your project's ``local.yml``: ::
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./compose/production/postgres/Dockerfile
|
||||||
|
volumes:
|
||||||
|
- postgres_data_local:/var/lib/postgresql/data
|
||||||
|
- postgres_backup_local:/backups
|
||||||
|
env_file:
|
||||||
|
- ./.envs/.local/.postgres
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
The most important thing for us here now is ``env_file`` section enlisting ``./.envs/.local/.postgres``. Generally, the stack's behavior is governed by a number of environment variables (`env(s)`, for short) residing in ``envs/``, for instance, this is what we generate for you: ::
|
||||||
|
|
||||||
|
.envs
|
||||||
|
├── .local
|
||||||
|
│ ├── .django
|
||||||
|
│ └── .postgres
|
||||||
|
└── .production
|
||||||
|
├── .caddy
|
||||||
|
├── .django
|
||||||
|
└── .postgres
|
||||||
|
|
||||||
|
By convention, for any service ``sI`` in environment ``e`` (you know ``someenv`` is an environment when there is a ``someenv.yml`` file in the project root), given ``sI`` requires configuration, a ``.envs/.e/.sI`` `service configuration` file exists.
|
||||||
|
|
||||||
|
Consider the aforementioned ``.envs/.local/.postgres``: ::
|
||||||
|
|
||||||
|
# PostgreSQL
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
POSTGRES_DB=<your project slug>
|
||||||
|
POSTGRES_USER=XgOWtQtJecsAbaIyslwGvFvPawftNaqO
|
||||||
|
POSTGRES_PASSWORD=jSljDz4whHuwO3aJIgVBrqEml5Ycbghorep4uVJ4xjDYQu0LfuTZdctj7y0YcCLu
|
||||||
|
|
||||||
|
The three envs we are presented with here are ``POSTGRES_DB``, ``POSTGRES_USER``, and ``POSTGRES_PASSWORD`` (by the way, their values have also been generated for you). You might have figured out already where these definitions will end up; it's all the same with ``django`` and ``caddy`` service container envs.
|
||||||
|
|
||||||
|
One final touch: should you ever need to merge ``.envs/production/*`` in a single ``.env`` run the ``merge_production_dotenvs_in_dotenv.py``: ::
|
||||||
|
|
||||||
|
$ python merge_production_dotenvs_in_dotenv.py
|
||||||
|
|
||||||
|
The ``.env`` file will then be created, with all your production envs residing beside each other.
|
||||||
|
|
||||||
|
|
||||||
|
Tips & Tricks
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Activate a Docker Machine
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This tells our computer that all future commands are specifically for the dev1 machine. Using the ``eval`` command we can switch machines as needed.::
|
||||||
|
|
||||||
$ eval "$(docker-machine env dev1)"
|
$ eval "$(docker-machine env dev1)"
|
||||||
|
|
||||||
Detached Mode
|
|
||||||
~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
If you want to run the stack in detached mode (in the background), use the ``-d`` argument:
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
$ docker-compose -f local.yml up -d
|
|
||||||
|
|
||||||
Debugging
|
Debugging
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
ipdb
|
ipdb
|
||||||
"""""
|
"""""
|
||||||
|
|
||||||
If you are using the following within your code to debug:
|
If you are using the following within your code to debug: ::
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
import ipdb; ipdb.set_trace()
|
import ipdb; ipdb.set_trace()
|
||||||
|
|
||||||
Then you may need to run the following for it to work as desired:
|
Then you may need to run the following for it to work as desired: ::
|
||||||
|
|
||||||
::
|
$ docker-compose -f local.yml run --rm --service-ports django
|
||||||
|
|
||||||
$ docker-compose -f local.yml run --service-ports django
|
|
||||||
|
|
||||||
|
|
||||||
django-debug-toolbar
|
django-debug-toolbar
|
||||||
""""""""""""""""""""
|
""""""""""""""""""""
|
||||||
|
|
||||||
In order for django-debug-toolbar to work with docker you need to add your docker-machine ip address to ``INTERNAL_IPS`` in ``local.py``
|
In order for ``django-debug-toolbar`` to work designate your Docker Machine IP with ``INTERNAL_IPS`` in ``local.py``.
|
||||||
|
|
||||||
|
|
||||||
.. May be a better place to put this, as it is not Docker specific.
|
Mailhog
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
You may need to add the following to your css in order for the django-debug-toolbar to be visible (this applies whether Docker is being used or not):
|
When developing locally you can go with MailHog_ for email testing provided ``use_mailhog`` was set to ``y`` on setup. To proceed,
|
||||||
|
|
||||||
.. code-block:: css
|
#. make sure ``mailhog`` container is up and running;
|
||||||
|
|
||||||
/* Override Bootstrap 4 styling on Django Debug Toolbar */
|
#. open up ``http://127.0.0.1:8025``.
|
||||||
#djDebug[hidden], #djDebug [hidden] {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#djDebug [hidden][style='display: none;'] {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Using the Mailhog Docker Container
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
In development you can (optionally) use MailHog_ for email testing. If you selected `use_docker`, MailHog is added as a Docker container. To use MailHog:
|
|
||||||
|
|
||||||
1. Make sure, that ``mailhog`` docker container is up and running
|
|
||||||
2. Open your browser and go to ``http://127.0.0.1:8025``
|
|
||||||
|
|
||||||
.. _Mailhog: https://github.com/mailhog/MailHog/
|
.. _Mailhog: https://github.com/mailhog/MailHog/
|
||||||
|
|
|
@ -3,74 +3,98 @@ Getting Up and Running Locally
|
||||||
|
|
||||||
.. index:: pip, virtualenv, PostgreSQL
|
.. index:: pip, virtualenv, PostgreSQL
|
||||||
|
|
||||||
The steps below will get you up and running with a local development environment. We assume you have the following installed:
|
|
||||||
|
|
||||||
* pip
|
Setting Up Development Environment
|
||||||
* virtualenv
|
----------------------------------
|
||||||
* PostgreSQL
|
|
||||||
|
|
||||||
First make sure to create and activate a virtualenv_.
|
Make sure to have the following on your host:
|
||||||
|
|
||||||
.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/
|
* virtualenv_;
|
||||||
|
* pip;
|
||||||
|
* PostgreSQL.
|
||||||
|
|
||||||
Then install the requirements for your local development::
|
First things first.
|
||||||
|
|
||||||
|
#. `Create a virtualenv`_.
|
||||||
|
|
||||||
|
#. Activate the virtualenv you have just created.
|
||||||
|
|
||||||
|
#. Install development requirements: ::
|
||||||
|
|
||||||
$ pip install -r requirements/local.txt
|
$ pip install -r requirements/local.txt
|
||||||
|
|
||||||
Then, create a PostgreSQL database with the following command, where `[project_slug]` is what value you entered for your project's `project_slug`::
|
#. Create a new PostgreSQL database (note: if this is the first time a database is created on your machine you might need to alter a localhost-related entry in your ``pg_hba.conf`` so as to utilize ``trust`` policy): ::
|
||||||
|
|
||||||
$ createdb [project_slug]
|
$ createdb <what you've entered as the project_slug at setup stage>
|
||||||
|
|
||||||
You can now run the usual Django ``migrate`` and ``runserver`` commands::
|
#. Apply migrations: ::
|
||||||
|
|
||||||
$ python manage.py migrate
|
$ python manage.py migrate
|
||||||
$ python manage.py runserver
|
|
||||||
|
|
||||||
At this point you can take a break from setup and start getting to know the files in the project.
|
#. See the application being served through Django development server: ::
|
||||||
|
|
||||||
But if you want to go further with setup, read on.
|
$ python manage.py runserver 0.0.0.0:8000
|
||||||
|
|
||||||
(Note: the following sections still need to be revised)
|
.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/
|
||||||
|
.. _`Create a virtualenv`: https://virtualenv.pypa.io/en/stable/userguide/
|
||||||
|
|
||||||
Setting Up Env Vars for Production
|
|
||||||
-----------------------------------
|
|
||||||
|
|
||||||
`Cookiecutter Django` uses the excellent `django-environ`_ package, which includes a ``DATABASE_URL`` environment variable to simplify database configuration in your Django settings.
|
Setup Email Backend
|
||||||
|
-------------------
|
||||||
|
|
||||||
Rename env.example to .env to begin updating the file with your own environment variables. To add your database, define ``DATABASE_URL`` and add it to the .env file, as shown below:
|
MailHog
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
.. parsed-literal::
|
.. note:: In order for the project to support MailHog_ it must have been bootstrapped with ``use_mailhog`` set to ``y``.
|
||||||
|
|
||||||
DATABASE_URL="postgres://*<pg_user_name>*:*<pg_user_password>*\ @127.0.0.1:\ *<pg_port>*/*<pg_database_name>*"
|
MailHog is used to receive emails during development, it is written in Go and has no external dependencies.
|
||||||
|
|
||||||
.. _django-environ: http://django-environ.readthedocs.io
|
For instance, one of the packages we depend upon, ``django-allauth`` sends verification emails to new users signing up as well as to the existing ones who have not yet verified themselves.
|
||||||
|
|
||||||
Setup your email backend
|
#. `Download the latest MailHog release`_ for your OS.
|
||||||
-------------------------
|
|
||||||
|
|
||||||
django-allauth sends an email to verify users (and superusers) after signup and login (if they are still not verified). To send email you need to `configure your email backend`_
|
#. Rename the build to ``MailHog``.
|
||||||
|
|
||||||
.. _configure your email backend: https://docs.djangoproject.com/en/dev/topics/email/#smtp-backend
|
#. Copy the file to the project root.
|
||||||
|
|
||||||
In development you can (optionally) use MailHog_ for email testing. MailHog is built with Go so there are no dependencies. To use MailHog:
|
#. Make it executable: ::
|
||||||
|
|
||||||
1. `Download the latest release`_ for your operating system
|
$ chmod +x MailHog
|
||||||
2. Rename the executable to ``mailhog`` and copy it to the root of your project directory
|
|
||||||
3. Make sure it is executable (e.g. ``chmod +x mailhog``)
|
|
||||||
4. Execute mailhog from the root of your project in a new terminal window (e.g. ``./mailhog``)
|
|
||||||
5. All emails generated from your django app can be seen on http://127.0.0.1:8025/
|
|
||||||
|
|
||||||
.. _Mailhog: https://github.com/mailhog/MailHog/
|
#. Spin up another terminal window and start it there: ::
|
||||||
.. _Download the latest release: https://github.com/mailhog/MailHog/releases
|
|
||||||
|
|
||||||
Alternatively simply output emails to the console via: ``EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'``
|
./MailHog
|
||||||
|
|
||||||
In production basic email configuration is setup to send emails with Mailgun_
|
#. Check out `<http://127.0.0.1:8025/>`_ to see how it goes.
|
||||||
|
|
||||||
|
Now you have your own mail server running locally, ready to receive whatever you send it.
|
||||||
|
|
||||||
|
.. _MailHog: https://github.com/mailhog/MailHog/
|
||||||
|
.. _`properly configured`: https://docs.djangoproject.com/en/dev/topics/email/#smtp-backend
|
||||||
|
|
||||||
|
|
||||||
|
Console
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
.. note:: If you have generated your project with ``use_mailhog`` set to ``n`` this will be a default setup.
|
||||||
|
|
||||||
|
Alternatively, deliver emails over console via ``EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'``.
|
||||||
|
|
||||||
|
In production, we have Mailgun_ configured to have your back!
|
||||||
|
|
||||||
.. _Mailgun: https://www.mailgun.com/
|
.. _Mailgun: https://www.mailgun.com/
|
||||||
|
|
||||||
**Live reloading and Sass CSS compilation**
|
|
||||||
|
|
||||||
If you’d like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with a little bit of `prep work`_.
|
Sass Compilation & Live Reloading
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
.. _prep work: https://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html
|
If you’d like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with a little bit of preparation_.
|
||||||
|
|
||||||
|
.. _`Download the latest MailHog release`: https://github.com/mailhog/MailHog/releases
|
||||||
|
.. _preparation: https://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html
|
||||||
|
|
||||||
|
|
||||||
|
Summary
|
||||||
|
-------
|
||||||
|
|
||||||
|
Congratulations, you have made it! Keep on reading to unleash full potential of Cookiecutter Django.
|
||||||
|
|
|
@ -1,40 +1,87 @@
|
||||||
============================
|
PostgreSQL Backups with Docker
|
||||||
Database Backups with Docker
|
==============================
|
||||||
============================
|
|
||||||
|
|
||||||
The database has to be running to create/restore a backup. These examples show local examples. If you want to use it on a remote server, remove ``-f local.yml`` from each example.
|
.. note:: For brevity it is assumed that you will be running the below commands against local environment, however, this is by no means mandatory so feel free to switch to ``production.yml`` when needed.
|
||||||
|
|
||||||
Running Backups
|
|
||||||
================
|
|
||||||
|
|
||||||
Run the app with `docker-compose -f local.yml up`.
|
Prerequisites
|
||||||
|
-------------
|
||||||
|
|
||||||
|
#. the project was generated with ``use_docker`` set to ``y``;
|
||||||
|
#. the stack is up and running: ``docker-compose -f local.yml up -d postgres``.
|
||||||
|
|
||||||
|
|
||||||
|
Creating a Backup
|
||||||
|
-----------------
|
||||||
|
|
||||||
To create a backup, run::
|
To create a backup, run::
|
||||||
|
|
||||||
docker-compose -f local.yml run postgres backup
|
$ docker-compose -f local.yml exec postgres backup
|
||||||
|
|
||||||
|
Assuming your project's database is named ``my_project`` here is what you will see: ::
|
||||||
|
|
||||||
|
Backing up the 'my_project' database...
|
||||||
|
SUCCESS: 'my_project' database backup 'backup_2018_03_13T09_05_07.sql.gz' has been created and placed in '/backups'.
|
||||||
|
|
||||||
|
Keep in mind that ``/backups`` is the ``postgres`` container directory.
|
||||||
|
|
||||||
|
|
||||||
To list backups, run::
|
Viewing the Existing Backups
|
||||||
|
----------------------------
|
||||||
|
|
||||||
docker-compose -f local.yml run postgres list-backups
|
To list existing backups, ::
|
||||||
|
|
||||||
|
$ docker-compose -f local.yml exec postgres backups
|
||||||
|
|
||||||
|
These are the sample contents of ``/backups``: ::
|
||||||
|
|
||||||
|
These are the backups you have got:
|
||||||
|
total 24K
|
||||||
|
-rw-r--r-- 1 root root 5.2K Mar 13 09:05 backup_2018_03_13T09_05_07.sql.gz
|
||||||
|
-rw-r--r-- 1 root root 5.2K Mar 12 21:13 backup_2018_03_12T21_13_03.sql.gz
|
||||||
|
-rw-r--r-- 1 root root 5.2K Mar 12 21:12 backup_2018_03_12T21_12_58.sql.gz
|
||||||
|
|
||||||
|
|
||||||
To restore a backup, run::
|
Copying Backups Locally
|
||||||
|
-----------------------
|
||||||
|
|
||||||
docker-compose -f local.yml run postgres restore filename.sql
|
If you want to copy backups from your ``postgres`` container locally, ``docker cp`` command_ will help you on that.
|
||||||
|
|
||||||
Where <containerId> is the ID of the Postgres container. To get it, run::
|
For example, given ``9c5c3f055843`` is the container ID copying all the backups over to a local directory is as simple as ::
|
||||||
|
|
||||||
docker ps
|
$ docker cp 9c5c3f055843:/backups ./backups
|
||||||
|
|
||||||
To copy the files from the running Postgres container to the host system::
|
With a single backup file copied to ``.`` that would be ::
|
||||||
|
|
||||||
docker cp <containerId>:/backups /host/path/target
|
$ docker cp 9c5c3f055843:/backups/backup_2018_03_13T09_05_07.sql.gz .
|
||||||
|
|
||||||
Restoring From Backups
|
.. _`command`: https://docs.docker.com/engine/reference/commandline/cp/
|
||||||
======================
|
|
||||||
|
|
||||||
To restore the production database to a local PostgreSQL database::
|
|
||||||
|
|
||||||
createdb NAME_OF_DATABASE
|
Restoring from the Existing Backup
|
||||||
psql NAME_OF_DATABASE < NAME_OF_BACKUP_FILE
|
----------------------------------
|
||||||
|
|
||||||
|
To restore from one of the backups you have already got (take the ``backup_2018_03_13T09_05_07.sql.gz`` for example), ::
|
||||||
|
|
||||||
|
$ docker-compose -f local.yml exec postgres restore backup_2018_03_13T09_05_07.sql.gz
|
||||||
|
|
||||||
|
You will see something like ::
|
||||||
|
|
||||||
|
Restoring the 'my_project' database from the '/backups/backup_2018_03_13T09_05_07.sql.gz' backup...
|
||||||
|
INFO: Dropping the database...
|
||||||
|
INFO: Creating a new database...
|
||||||
|
INFO: Applying the backup to the new database...
|
||||||
|
SET
|
||||||
|
SET
|
||||||
|
SET
|
||||||
|
SET
|
||||||
|
SET
|
||||||
|
set_config
|
||||||
|
------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SET
|
||||||
|
# ...
|
||||||
|
ALTER TABLE
|
||||||
|
SUCCESS: The 'my_project' database has been restored from the '/backups/backup_2018_03_13T09_05_07.sql.gz' backup.
|
||||||
|
|
12
docs/faq.rst
12
docs/faq.rst
|
@ -1,5 +1,5 @@
|
||||||
FAQ
|
FAQ
|
||||||
====
|
===
|
||||||
|
|
||||||
.. index:: FAQ, 12-Factor App
|
.. index:: FAQ, 12-Factor App
|
||||||
|
|
||||||
|
@ -17,11 +17,11 @@ Why aren't you using just one configuration file (12-Factor App)
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
TODO
|
TODO
|
||||||
|
.. TODO
|
||||||
|
|
||||||
Why doesn't this follow the layout from Two Scoops of Django 1.8?
|
Why doesn't this follow the layout from Two Scoops of Django?
|
||||||
----------------------------------------------------------------------
|
-------------------------------------------------------------
|
||||||
|
|
||||||
You may notice that some elements of this project do not exactly match what we describe in chapter 3 of `Two Scoops of Django`_. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored.
|
You may notice that some elements of this project do not exactly match what we describe in chapter 3 of `Two Scoops of Django 1.11`_. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored.
|
||||||
|
|
||||||
|
.. _Two Scoops of Django 1.11: https://www.twoscoopspress.com/collections/django/products/two-scoops-of-django-1-11
|
||||||
.. _`Two Scoops of Django`: http://twoscoopspress.com/products/two-scoops-of-django-1-8
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ Contents:
|
||||||
docker-postgres-backups
|
docker-postgres-backups
|
||||||
faq
|
faq
|
||||||
troubleshooting
|
troubleshooting
|
||||||
my-favorite-cookie
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
Live reloading and Sass CSS compilation
|
Sass Compilation & Live Reloading
|
||||||
=======================================
|
=================================
|
||||||
|
|
||||||
If you'd like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with a little bit of prep work.
|
If you'd like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with a little bit of prep work.
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ If you don't already have it, install `compass` (doesn't hurt if you run this co
|
||||||
|
|
||||||
Now you just need::
|
Now you just need::
|
||||||
|
|
||||||
$ grunt serve
|
$ npm run dev
|
||||||
|
|
||||||
The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled.
|
The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled.
|
||||||
|
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
************************************************
|
|
||||||
Creating your first app with Cookiecutter-Django
|
|
||||||
************************************************
|
|
||||||
|
|
||||||
This tutorial will show you how to build a simple app using the `Cookiecutter Django <https://github.com/pydanny/cookiecutter-django>`_ templating system. We'll be building a cookie polling app to determine the most popular flavor of cookie.
|
|
||||||
|
|
||||||
Developers who have never used Django will learn the basics of creating a Django app; developers who are experienced with Django will learn how to set up a project within the Cookiecutter system. While many Django tutorials use the default SQLite database, Cookiecutter Django uses PostGres only, so we'll have you install and use that.
|
|
||||||
|
|
||||||
|
|
||||||
Dependencies
|
|
||||||
============
|
|
||||||
This tutorial was written on Windows 10 using `git bash <https://git-for-windows.github.io/>`_; alternate instructions for Mac OS and Linux will be provided when needed. Any Linux-style shell should work for the following commands.
|
|
||||||
|
|
||||||
You should have your preferred versions of `Python <https://www.python.org/downloads/>`_
|
|
||||||
and `Django <https://www.djangoproject.com/download/>`_ installed. Use the latest stable versions if you have no preference.
|
|
||||||
|
|
||||||
You should have `Virtualenv <https://virtualenv.pypa.io/en/stable/>`_ and `Cookiecutter <https://github.com/pydanny/cookiecutter-django/>`_ installed:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
$ pip install virtualenv
|
|
||||||
$ pip install cookiecutter
|
|
||||||
|
|
||||||
You should also have `PostgreSQL <https://www.postgresql.org/download/>`_ installed on your machine--just download and run the installer for your OS. The install menu will prompt you for a password, which you'll use when creating the project's database.
|
|
||||||
|
|
||||||
|
|
||||||
Instructions
|
|
||||||
============
|
|
||||||
|
|
||||||
1. **Setup** -- how to set up a virtual environment
|
|
||||||
2. **Cookiecutter** -- use Cookiecutter to initialize a project with your own customized information.
|
|
||||||
3. **Building the App** -- creating the My Favorite Cookie application.
|
|
||||||
|
|
||||||
============
|
|
||||||
1. Setup
|
|
||||||
============
|
|
||||||
|
|
||||||
Virtual Environment
|
|
||||||
"""""""""""""""""""
|
|
||||||
|
|
||||||
Create a virtual environment for your project. Cookiecutter will install a bunch of dependencies for you automatically; using a virtualenv will prevent this from interfering with your other work.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
$ virtualenv c:/.virtualenvs/cookie_polls
|
|
||||||
|
|
||||||
Replace ``c:/.virtualenvs`` with the path to your own ``.virtualenvs`` folder.
|
|
||||||
|
|
||||||
Activate the virtual environment by calling ``source`` on the ``activate`` shell script . On Windows you'll call this from the virtualenv's ``scripts`` folder:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
$ source /path/to/.virtualenvs/cookie_polls/scripts/activate
|
|
||||||
|
|
||||||
On other operating systems, it'll be found in the ``bin`` folder.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
$ source /path/to/.virtualenvs/cookie_polls/bin/activate
|
|
||||||
|
|
||||||
You'll know the virtual environment is active because its name will appear in parentheses before the command prompt. When you're done with this project, you can leave the virtual environment with the ``deactivate`` command.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
(cookie_polls)
|
|
||||||
$ deactivate
|
|
||||||
|
|
||||||
|
|
||||||
Now you're ready to create your project using Cookiecutter.
|
|
||||||
|
|
||||||
|
|
||||||
===============
|
|
||||||
2. Cookiecutter
|
|
||||||
===============
|
|
||||||
|
|
||||||
Django developers may be familiar with the ``startproject`` command, which initializes the directory structure and required files for a bare-bones Django project. While this is fine when you're just learning Django for the first time, it's not great for a real production app. Cookiecutter takes care of a lot of standard tasks for you, including installing software dependencies, setting up testing files, and including and organizing common libraries like Bootstrap and AngularJS. It also generates a software license and a README.
|
|
||||||
|
|
||||||
Change directories into the folder where you want your project to live, and run ``cookiecutter`` followed by the URL of Cookiecutter's Github repo.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
$ cd /my/project/folder
|
|
||||||
(cookie_polls)
|
|
||||||
my/project/folder
|
|
||||||
$ cookiecutter https://github.com/pydanny/cookiecutter-django
|
|
||||||
|
|
||||||
This will prompt you for a bunch of values specific to your project. Press "enter" without typing anything to use the default values, which are shown in [brackets] after the question. You can learn about all the different options `here, <http://cookiecutter-django.readthedocs.io/en/latest/project-generation-options.html>`_ but for now we'll use the defaults for everything but your name, your email, the project's name, and the project's description.
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
project_name [project_name]: My Favorite Cookie
|
|
||||||
project_slug [My_Favorite_Cookie]:
|
|
||||||
author_name [Your Name]: Emily Cain
|
|
||||||
email [Your email]: contact@emcain.net
|
|
||||||
description [A short description of the project.]: Poll your friends to determine the most popular cookie.
|
|
||||||
|
|
||||||
Then hit "enter" to use the default values for everything else.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,69 +1,61 @@
|
||||||
Project Generation Options
|
Project Generation Options
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
project_name [project_name]:
|
project_name [My Awesome Project]:
|
||||||
Your human-readable project name, including any capitalization or spaces.
|
Your project's human-readable name, capitals and spaces allowed.
|
||||||
|
|
||||||
project_slug [project_name]:
|
project_slug [my_awesome_project]:
|
||||||
The slug of your project, without dashes or spaces. Used to name your repo
|
Your project's slug without dashes or spaces. Used to name your repo
|
||||||
and in other places where a Python-importable version of your project name
|
and in other places where a Python-importable version of your project name
|
||||||
is needed.
|
is needed.
|
||||||
|
|
||||||
author_name [Your Name]:
|
description [Behold My Awesome Project!]
|
||||||
You! This goes into places like the LICENSE file.
|
Describes your project and gets used in places like ``README.rst`` and such.
|
||||||
|
|
||||||
email [Your email]:
|
author_name [Daniel Roy Greenfeld]:
|
||||||
Your email address.
|
This is you! The value goes into places like ``LICENSE`` and such.
|
||||||
|
|
||||||
description [A short description of the project.]
|
email [daniel-roy-greenfeld@example.com]:
|
||||||
Used in the generated README.rst and other places.
|
The email address you want to identify yourself in the project.
|
||||||
|
|
||||||
domain_name [example.com]
|
domain_name [example.com]
|
||||||
Whatever domain name you plan to use for your project when it goes live.
|
The domain name you plan to use for your project once it goes live.
|
||||||
|
Note that it can be safely changed later on whenever you need to.
|
||||||
|
|
||||||
version [0.1.0]
|
version [0.1.0]
|
||||||
The starting version number for your project.
|
The version of the project at its inception.
|
||||||
|
|
||||||
|
open_source_license [1]
|
||||||
|
A software license for the project. The choices are:
|
||||||
|
|
||||||
|
1. MIT_
|
||||||
|
2. BSD_
|
||||||
|
3. GPLv3_
|
||||||
|
4. `Apache Software License 2.0`_
|
||||||
|
5. Not open source
|
||||||
|
|
||||||
timezone [UTC]
|
timezone [UTC]
|
||||||
Used in the base settings file for the `TIME_ZONE` value.
|
The value to be used for the ``TIME_ZONE`` setting of the project.
|
||||||
|
|
||||||
use_whitenoise [y]
|
|
||||||
Whether to use WhiteNoise_ for static file serving.
|
|
||||||
|
|
||||||
use_celery [n]
|
|
||||||
Whether to use Celery_. This gives you the ability to use distributed task
|
|
||||||
queues in your project.
|
|
||||||
|
|
||||||
use_mailhog [n]
|
|
||||||
Whether to use MailHog_. MailHog is a tool that simulates email receiving
|
|
||||||
for development purposes. It runs a simple SMTP server which catches
|
|
||||||
any message sent to it. Messages are displayed in a web interface which
|
|
||||||
runs at ``http://localhost:8025/`` You need to download the MailHog
|
|
||||||
executable for your operating system, see the 'Developing Locally' docs
|
|
||||||
for instructions.
|
|
||||||
|
|
||||||
use_sentry_for_error_reporting [n]
|
|
||||||
Whether to use Sentry_ to log errors from your project.
|
|
||||||
|
|
||||||
use_opbeat [n]
|
|
||||||
Whether to use Opbeat_ for preformance monitoring and code optimization.
|
|
||||||
|
|
||||||
use_pycharm [n]
|
|
||||||
Adds support for developing in PyCharm_ with a preconfigured .idea directory.
|
|
||||||
|
|
||||||
windows [n]
|
windows [n]
|
||||||
Whether you'll be developing on Windows.
|
Indicates whether the project should be configured for development on Windows.
|
||||||
|
|
||||||
|
use_pycharm [n]
|
||||||
|
Indicates whether the project should be configured for development with PyCharm_.
|
||||||
|
|
||||||
use_docker [y]
|
use_docker [y]
|
||||||
Whether to use Docker_, separating the app and database into separate
|
Indicates whether the project should be configured to use Docker_ and `Docker Compose`_.
|
||||||
containers.
|
|
||||||
|
|
||||||
use_heroku [n]
|
postgresql_version [1]
|
||||||
Add configuration to deploy the application to a Heroku_ instance.
|
Select a PostgreSQL_ version to use. The choices are:
|
||||||
|
|
||||||
use_compressor [n]
|
1. 10.3
|
||||||
Use `Django Compressor`_ to minify and combine rendered JavaScript and CSS
|
2. 10.2
|
||||||
into cachable static resources.
|
3. 10.1
|
||||||
|
4. 9.6
|
||||||
|
5. 9.5
|
||||||
|
6. 9.4
|
||||||
|
7. 9.3
|
||||||
|
|
||||||
js_task_runner [1]
|
js_task_runner [1]
|
||||||
Select a JavaScript task runner. The choices are:
|
Select a JavaScript task runner. The choices are:
|
||||||
|
@ -73,36 +65,67 @@ js_task_runner [1]
|
||||||
3. None
|
3. None
|
||||||
|
|
||||||
custom_bootstrap_compilation [n]
|
custom_bootstrap_compilation [n]
|
||||||
Scaffold out recompiling Bootstrap as as task, with Gulp_ or Grunt_.
|
Indicates whether the project should support Bootstrap recompilation
|
||||||
Useful for letting you change Bootstrap variables in real time.
|
via the selected JavaScript task runner's task. This can be useful
|
||||||
Consult project README for more details.
|
for real-time Bootstrap variable alteration.
|
||||||
|
|
||||||
open_source_license [1]
|
use_compressor [n]
|
||||||
Select a software license for the project. The choices are:
|
Indicates whether the project should be configured to use `Django Compressor`_.
|
||||||
|
|
||||||
1. MIT_
|
use_celery [n]
|
||||||
2. BSD_
|
Indicates whether the project should be configured to use Celery_.
|
||||||
3. GPLv3_
|
|
||||||
4. `Apache Software License 2.0`_
|
use_mailhog [n]
|
||||||
5. Not open source
|
Indicates whether the project should be configured to use MailHog_.
|
||||||
|
|
||||||
|
use_sentry [n]
|
||||||
|
Indicates whether the project should be configured to use Sentry_.
|
||||||
|
|
||||||
|
use_whitenoise [y]
|
||||||
|
Indicates whether the project should be configured to use WhiteNoise_.
|
||||||
|
|
||||||
|
use_heroku [n]
|
||||||
|
Indicates whether the project should be configured so as to be deployable
|
||||||
|
to Heroku_.
|
||||||
|
|
||||||
|
use_travisci [n]
|
||||||
|
Indicates whether the project should be configured to use `Travis CI`_.
|
||||||
|
|
||||||
|
keep_local_envs_in_vcs [y]
|
||||||
|
Indicates whether the project's ``.envs/.local/`` should be kept in VCS
|
||||||
|
(comes in handy when working in teams where local environment reproducibility
|
||||||
|
is strongly encouraged).
|
||||||
|
|
||||||
|
debug [n]
|
||||||
|
Indicates whether the project should be configured for debugging.
|
||||||
|
This option is relevant for Cookiecutter Django developers only.
|
||||||
|
|
||||||
**NOTE:** *If you choose to use Docker, selecting a JavaScript task runner is
|
|
||||||
not supported out of the box.*
|
|
||||||
|
|
||||||
.. _WhiteNoise: https://github.com/evansd/whitenoise
|
|
||||||
.. _Celery: https://github.com/celery/celery
|
|
||||||
.. _MailHog: https://github.com/mailhog/MailHog
|
|
||||||
.. _Sentry: https://github.com/getsentry/sentry
|
|
||||||
.. _Opbeat: https://github.com/opbeat/opbeat_python
|
|
||||||
.. _PyCharm: https://www.jetbrains.com/pycharm/
|
|
||||||
.. _Docker: https://github.com/docker/docker
|
|
||||||
.. _Heroku: https://github.com/heroku/heroku-buildpack-python
|
|
||||||
.. _Django Compressor: https://github.com/django-compressor/django-compressor
|
|
||||||
.. _Gulp: https://github.com/gulpjs/gulp
|
|
||||||
.. _Grunt: https://github.com/gruntjs/grunt
|
|
||||||
.. _Webpack: https://github.com/webpack/webpack
|
|
||||||
.. _Let's Encrypt: https://github.com/certbot/certbot
|
|
||||||
.. _MIT: https://opensource.org/licenses/MIT
|
.. _MIT: https://opensource.org/licenses/MIT
|
||||||
.. _BSD: https://opensource.org/licenses/BSD-3-Clause
|
.. _BSD: https://opensource.org/licenses/BSD-3-Clause
|
||||||
.. _GPLv3: https://www.gnu.org/licenses/gpl.html
|
.. _GPLv3: https://www.gnu.org/licenses/gpl.html
|
||||||
.. _Apache Software License 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
.. _Apache Software License 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
.. _PyCharm: https://www.jetbrains.com/pycharm/
|
||||||
|
|
||||||
|
.. _Docker: https://github.com/docker/docker
|
||||||
|
.. _Docker Compose: https://docs.docker.com/compose/
|
||||||
|
|
||||||
|
.. _PostgreSQL: https://www.postgresql.org/docs/
|
||||||
|
|
||||||
|
.. _Gulp: https://github.com/gulpjs/gulp
|
||||||
|
.. _Grunt: https://github.com/gruntjs/grunt
|
||||||
|
|
||||||
|
.. _Django Compressor: https://github.com/django-compressor/django-compressor
|
||||||
|
|
||||||
|
.. _Celery: https://github.com/celery/celery
|
||||||
|
|
||||||
|
.. _MailHog: https://github.com/mailhog/MailHog
|
||||||
|
|
||||||
|
.. _Sentry: https://github.com/getsentry/sentry
|
||||||
|
|
||||||
|
.. _WhiteNoise: https://github.com/evansd/whitenoise
|
||||||
|
|
||||||
|
.. _Heroku: https://github.com/heroku/heroku-buildpack-python
|
||||||
|
|
||||||
|
.. _Travis CI: https://travis-ci.org/
|
||||||
|
|
|
@ -47,14 +47,10 @@ DJANGO_AWS_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a
|
||||||
DJANGO_SENTRY_DSN SENTRY_DSN n/a raises error
|
DJANGO_SENTRY_DSN SENTRY_DSN n/a raises error
|
||||||
DJANGO_SENTRY_CLIENT SENTRY_CLIENT n/a raven.contrib.django.raven_compat.DjangoClient
|
DJANGO_SENTRY_CLIENT SENTRY_CLIENT n/a raven.contrib.django.raven_compat.DjangoClient
|
||||||
DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO
|
DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO
|
||||||
DJANGO_MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error
|
MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error
|
||||||
DJANGO_MAILGUN_SERVER_NAME MAILGUN_SERVER_NAME n/a raises error
|
MAILGUN_DOMAIN MAILGUN_SENDER_DOMAIN n/a raises error
|
||||||
MAILGUN_SENDER_DOMAIN MAILGUN_SENDER_DOMAIN n/a raises error
|
|
||||||
NEW_RELIC_APP_NAME NEW_RELIC_APP_NAME n/a raises error
|
NEW_RELIC_APP_NAME NEW_RELIC_APP_NAME n/a raises error
|
||||||
NEW_RELIC_LICENSE_KEY NEW_RELIC_LICENSE_KEY n/a raises error
|
NEW_RELIC_LICENSE_KEY NEW_RELIC_LICENSE_KEY n/a raises error
|
||||||
DJANGO_OPBEAT_APP_ID OPBEAT['APP_ID'] n/a raises error
|
|
||||||
DJANGO_OPBEAT_SECRET_TOKEN OPBEAT['SECRET_TOKEN'] n/a raises error
|
|
||||||
DJANGO_OPBEAT_ORGANIZATION_ID OPBEAT['ORGANIZATION_ID'] n/a raises error
|
|
||||||
======================================= =========================== ============================================== ======================================================================
|
======================================= =========================== ============================================== ======================================================================
|
||||||
|
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
|
@ -3,7 +3,10 @@ Troubleshooting
|
||||||
|
|
||||||
This page contains some advice about errors and problems commonly encountered during the development of Cookiecutter Django applications.
|
This page contains some advice about errors and problems commonly encountered during the development of Cookiecutter Django applications.
|
||||||
|
|
||||||
#. If you get the error ``jinja2.exceptions.TemplateSyntaxError: Encountered unknown tag 'now'.`` , please upgrade your cookiecutter version to >= 1.4 (see issue # 528_ )
|
|
||||||
#. ``project_slug`` must be a valid Python module name or you will have issues on imports.
|
#. ``project_slug`` must be a valid Python module name or you will have issues on imports.
|
||||||
|
|
||||||
.. _528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373
|
#. ``jinja2.exceptions.TemplateSyntaxError: Encountered unknown tag 'now'.``: please upgrade your cookiecutter version to >= 1.4 (see `#528`_)
|
||||||
|
|
||||||
|
#. Internal server error on user registration: make sure you have configured the mail backend (e.g. Mailgun) by adding the API key and sender domain
|
||||||
|
|
||||||
|
.. _#528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373
|
||||||
|
|
|
@ -7,12 +7,12 @@ NOTE:
|
||||||
|
|
||||||
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only
|
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import shutil
|
import shutil
|
||||||
import string
|
import string
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Inspired by
|
# Inspired by
|
||||||
|
@ -22,113 +22,87 @@ try:
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
using_sysrandom = False
|
using_sysrandom = False
|
||||||
|
|
||||||
PROJECT_DIR_PATH = os.path.realpath(os.path.curdir)
|
TERMINATOR = "\x1b[0m"
|
||||||
|
WARNING = "\x1b[1;33m [WARNING]: "
|
||||||
|
INFO = "\x1b[1;33m [INFO]: "
|
||||||
|
HINT = "\x1b[3;33m"
|
||||||
|
SUCCESS = "\x1b[1;32m [SUCCESS]: "
|
||||||
|
|
||||||
|
DEBUG_VALUE = "debug"
|
||||||
|
|
||||||
|
|
||||||
def remove_file(file_path):
|
def remove_open_source_files():
|
||||||
if os.path.exists(file_path):
|
file_names = ["CONTRIBUTORS.txt"]
|
||||||
os.remove(file_path)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_open_source_project_only_files():
|
|
||||||
file_names = [
|
|
||||||
'CONTRIBUTORS.txt',
|
|
||||||
]
|
|
||||||
for file_name in file_names:
|
for file_name in file_names:
|
||||||
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
|
os.remove(file_name)
|
||||||
|
|
||||||
|
|
||||||
def remove_gplv3_files():
|
def remove_gplv3_files():
|
||||||
file_names = [
|
file_names = ["COPYING"]
|
||||||
'COPYING',
|
|
||||||
]
|
|
||||||
for file_name in file_names:
|
for file_name in file_names:
|
||||||
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
|
os.remove(file_name)
|
||||||
|
|
||||||
|
|
||||||
def remove_pycharm_files():
|
def remove_pycharm_files():
|
||||||
idea_dir_path = os.path.join(PROJECT_DIR_PATH, '.idea')
|
idea_dir_path = ".idea"
|
||||||
if os.path.exists(idea_dir_path):
|
if os.path.exists(idea_dir_path):
|
||||||
shutil.rmtree(idea_dir_path)
|
shutil.rmtree(idea_dir_path)
|
||||||
|
|
||||||
docs_dir_path = os.path.join(PROJECT_DIR_PATH, 'docs', 'pycharm')
|
docs_dir_path = os.path.join("docs", "pycharm")
|
||||||
if os.path.exists(docs_dir_path):
|
if os.path.exists(docs_dir_path):
|
||||||
shutil.rmtree(docs_dir_path)
|
shutil.rmtree(docs_dir_path)
|
||||||
|
|
||||||
|
|
||||||
def remove_docker_files():
|
def remove_docker_files():
|
||||||
shutil.rmtree(os.path.join(PROJECT_DIR_PATH, 'compose'))
|
shutil.rmtree("compose")
|
||||||
|
|
||||||
file_names = [
|
file_names = ["local.yml", "production.yml", ".dockerignore"]
|
||||||
'local.yml',
|
|
||||||
'production.yml',
|
|
||||||
'.dockerignore',
|
|
||||||
]
|
|
||||||
for file_name in file_names:
|
for file_name in file_names:
|
||||||
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
|
os.remove(file_name)
|
||||||
|
|
||||||
|
|
||||||
def remove_heroku_files():
|
def remove_heroku_files():
|
||||||
file_names = [
|
file_names = ["Procfile", "runtime.txt", "requirements.txt"]
|
||||||
'Procfile',
|
|
||||||
'runtime.txt',
|
|
||||||
]
|
|
||||||
for file_name in file_names:
|
for file_name in file_names:
|
||||||
remove_file(os.path.join(PROJECT_DIR_PATH, file_name))
|
os.remove(file_name)
|
||||||
|
|
||||||
|
|
||||||
def remove_paas_files():
|
|
||||||
none_paas_files_left = True
|
|
||||||
|
|
||||||
if '{{ cookiecutter.use_heroku }}'.lower() == 'n':
|
|
||||||
remove_heroku_files()
|
|
||||||
none_paas_files_left &= True
|
|
||||||
else:
|
|
||||||
none_paas_files_left &= False
|
|
||||||
|
|
||||||
if none_paas_files_left:
|
|
||||||
remove_file(os.path.join(PROJECT_DIR_PATH, 'requirements.txt'))
|
|
||||||
|
|
||||||
|
|
||||||
def remove_grunt_files():
|
def remove_grunt_files():
|
||||||
file_names = [
|
file_names = ["Gruntfile.js"]
|
||||||
'Gruntfile.js',
|
|
||||||
]
|
|
||||||
for file_name in file_names:
|
for file_name in file_names:
|
||||||
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
|
os.remove(file_name)
|
||||||
|
|
||||||
|
|
||||||
def remove_gulp_files():
|
def remove_gulp_files():
|
||||||
file_names = [
|
file_names = ["gulpfile.js"]
|
||||||
'gulpfile.js',
|
|
||||||
]
|
|
||||||
for file_name in file_names:
|
for file_name in file_names:
|
||||||
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
|
os.remove(file_name)
|
||||||
|
|
||||||
|
|
||||||
def remove_packagejson_file():
|
def remove_packagejson_file():
|
||||||
file_names = [
|
file_names = ["package.json"]
|
||||||
'package.json',
|
|
||||||
]
|
|
||||||
for file_name in file_names:
|
for file_name in file_names:
|
||||||
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
|
os.remove(file_name)
|
||||||
|
|
||||||
|
|
||||||
def remove_celery_app():
|
def remove_celery_app():
|
||||||
shutil.rmtree(os.path.join(PROJECT_DIR_PATH, '{{ cookiecutter.project_slug }}', 'taskapp'))
|
shutil.rmtree(os.path.join("{{ cookiecutter.project_slug }}", "taskapp"))
|
||||||
|
|
||||||
|
|
||||||
|
def remove_dottravisyml_file():
|
||||||
|
os.remove(".travis.yml")
|
||||||
|
|
||||||
|
|
||||||
def append_to_project_gitignore(path):
|
def append_to_project_gitignore(path):
|
||||||
gitignore_file_path = os.path.join(PROJECT_DIR_PATH, '.gitignore')
|
gitignore_file_path = ".gitignore"
|
||||||
with open(gitignore_file_path, 'a') as gitignore_file:
|
with open(gitignore_file_path, "a") as gitignore_file:
|
||||||
gitignore_file.write(path)
|
gitignore_file.write(path)
|
||||||
gitignore_file.write(os.linesep)
|
gitignore_file.write(os.linesep)
|
||||||
|
|
||||||
|
|
||||||
def generate_random_string(length,
|
def generate_random_string(
|
||||||
using_digits=False,
|
length, using_digits=False, using_ascii_letters=False, using_punctuation=False
|
||||||
using_ascii_letters=False,
|
):
|
||||||
using_punctuation=False):
|
|
||||||
"""
|
"""
|
||||||
Example:
|
Example:
|
||||||
opting out for 50 symbol-long, [a-z][A-Z][0-9] string
|
opting out for 50 symbol-long, [a-z][A-Z][0-9] string
|
||||||
|
@ -143,30 +117,26 @@ def generate_random_string(length,
|
||||||
if using_ascii_letters:
|
if using_ascii_letters:
|
||||||
symbols += string.ascii_letters
|
symbols += string.ascii_letters
|
||||||
if using_punctuation:
|
if using_punctuation:
|
||||||
symbols += string.punctuation \
|
symbols += string.punctuation.replace('"', "").replace("'", "").replace(
|
||||||
.replace('"', '') \
|
"\\", ""
|
||||||
.replace("'", '') \
|
)
|
||||||
.replace('\\', '')
|
return "".join([random.choice(symbols) for _ in range(length)])
|
||||||
return ''.join([random.choice(symbols) for _ in range(length)])
|
|
||||||
|
|
||||||
|
|
||||||
def set_flag(file_path,
|
def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs):
|
||||||
flag,
|
|
||||||
value=None,
|
|
||||||
*args,
|
|
||||||
**kwargs):
|
|
||||||
if value is None:
|
if value is None:
|
||||||
random_string = generate_random_string(*args, **kwargs)
|
random_string = generate_random_string(*args, **kwargs)
|
||||||
if random_string is None:
|
if random_string is None:
|
||||||
import sys
|
print(
|
||||||
sys.stdout.write(
|
|
||||||
"We couldn't find a secure pseudo-random number generator on your system. "
|
"We couldn't find a secure pseudo-random number generator on your system. "
|
||||||
"Please, make sure to manually {} later.".format(flag)
|
"Please, make sure to manually {} later.".format(flag)
|
||||||
)
|
)
|
||||||
random_string = flag
|
random_string = flag
|
||||||
|
if formatted is not None:
|
||||||
|
random_string = formatted.format(random_string)
|
||||||
value = random_string
|
value = random_string
|
||||||
|
|
||||||
with open(file_path, 'r+') as f:
|
with open(file_path, "r+") as f:
|
||||||
file_contents = f.read().replace(flag, value)
|
file_contents = f.read().replace(flag, value)
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
f.write(file_contents)
|
f.write(file_contents)
|
||||||
|
@ -178,106 +148,156 @@ def set_flag(file_path,
|
||||||
def set_django_secret_key(file_path):
|
def set_django_secret_key(file_path):
|
||||||
django_secret_key = set_flag(
|
django_secret_key = set_flag(
|
||||||
file_path,
|
file_path,
|
||||||
'!!!SET DJANGO_SECRET_KEY!!!',
|
"!!!SET DJANGO_SECRET_KEY!!!",
|
||||||
length=50,
|
length=64,
|
||||||
using_digits=True,
|
using_digits=True,
|
||||||
using_ascii_letters=True
|
using_ascii_letters=True,
|
||||||
)
|
)
|
||||||
return django_secret_key
|
return django_secret_key
|
||||||
|
|
||||||
|
|
||||||
def set_postgres_user(file_path,
|
def set_django_admin_url(file_path):
|
||||||
value=None):
|
django_admin_url = set_flag(
|
||||||
|
file_path,
|
||||||
|
"!!!SET DJANGO_ADMIN_URL!!!",
|
||||||
|
formatted="^{}/",
|
||||||
|
length=32,
|
||||||
|
using_digits=True,
|
||||||
|
using_ascii_letters=True,
|
||||||
|
)
|
||||||
|
return django_admin_url
|
||||||
|
|
||||||
|
|
||||||
|
def generate_postgres_user(debug=False):
|
||||||
|
return DEBUG_VALUE if debug else generate_random_string(length=32, using_ascii_letters=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_postgres_user(file_path, value):
|
||||||
postgres_user = set_flag(
|
postgres_user = set_flag(
|
||||||
file_path,
|
file_path,
|
||||||
'!!!SET POSTGRES_USER!!!',
|
"!!!SET POSTGRES_USER!!!",
|
||||||
value=value,
|
value=value,
|
||||||
length=8,
|
|
||||||
using_ascii_letters=True
|
|
||||||
)
|
)
|
||||||
return postgres_user
|
return postgres_user
|
||||||
|
|
||||||
|
|
||||||
def set_postgres_password(file_path):
|
def set_postgres_password(file_path, value=None):
|
||||||
postgres_password = set_flag(
|
postgres_password = set_flag(
|
||||||
file_path,
|
file_path,
|
||||||
'!!!SET POSTGRES_PASSWORD!!!',
|
"!!!SET POSTGRES_PASSWORD!!!",
|
||||||
length=42,
|
value=value,
|
||||||
|
length=64,
|
||||||
using_digits=True,
|
using_digits=True,
|
||||||
using_ascii_letters=True
|
using_ascii_letters=True,
|
||||||
)
|
)
|
||||||
return postgres_password
|
return postgres_password
|
||||||
|
|
||||||
|
|
||||||
def initialize_dotenv(postgres_user):
|
def append_to_gitignore_file(s):
|
||||||
# Initializing `env.example` first.
|
with open(".gitignore", "a") as gitignore_file:
|
||||||
envexample_file_path = os.path.join(PROJECT_DIR_PATH, 'env.example')
|
gitignore_file.write(s)
|
||||||
set_django_secret_key(envexample_file_path)
|
gitignore_file.write(os.linesep)
|
||||||
set_postgres_user(envexample_file_path, value=postgres_user)
|
|
||||||
set_postgres_password(envexample_file_path)
|
|
||||||
# Renaming `env.example` to `.env`.
|
|
||||||
dotenv_file_path = os.path.join(PROJECT_DIR_PATH, '.env')
|
|
||||||
shutil.move(envexample_file_path, dotenv_file_path)
|
|
||||||
|
|
||||||
|
|
||||||
def initialize_localyml(postgres_user):
|
def set_flags_in_envs(postgres_user, debug=False):
|
||||||
set_postgres_user(os.path.join(PROJECT_DIR_PATH, 'local.yml'), value=postgres_user)
|
local_postgres_envs_path = os.path.join(".envs", ".local", ".postgres")
|
||||||
|
set_postgres_user(local_postgres_envs_path, value=postgres_user)
|
||||||
|
set_postgres_password(local_postgres_envs_path, value=DEBUG_VALUE if debug else None)
|
||||||
|
|
||||||
|
production_django_envs_path = os.path.join(".envs", ".production", ".django")
|
||||||
|
set_django_secret_key(production_django_envs_path)
|
||||||
|
set_django_admin_url(production_django_envs_path)
|
||||||
|
|
||||||
|
production_postgres_envs_path = os.path.join(".envs", ".production", ".postgres")
|
||||||
|
set_postgres_user(production_postgres_envs_path, value=postgres_user)
|
||||||
|
set_postgres_password(production_postgres_envs_path, value=DEBUG_VALUE if debug else None)
|
||||||
|
|
||||||
|
|
||||||
def initialize_local_settings():
|
def set_flags_in_settings_files():
|
||||||
set_django_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'local.py'))
|
set_django_secret_key(os.path.join("config", "settings", "local.py"))
|
||||||
|
set_django_secret_key(os.path.join("config", "settings", "test.py"))
|
||||||
|
|
||||||
|
|
||||||
def initialize_test_settings():
|
def remove_envs_and_associated_files():
|
||||||
set_django_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'test.py'))
|
shutil.rmtree(".envs")
|
||||||
|
os.remove("merge_production_dotenvs_in_dotenv.py")
|
||||||
|
|
||||||
|
|
||||||
|
def remove_celery_compose_dirs():
|
||||||
|
shutil.rmtree(os.path.join("compose", "local", "django", "celery"))
|
||||||
|
shutil.rmtree(os.path.join("compose", "production", "django", "celery"))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
postgres_user = generate_random_string(length=16, using_ascii_letters=True)
|
postgres_user = generate_postgres_user(debug="{{ cookiecutter.debug }}".lower() == "y")
|
||||||
initialize_dotenv(postgres_user)
|
set_flags_in_envs(postgres_user, debug="{{ cookiecutter.debug }}".lower() == "y")
|
||||||
initialize_localyml(postgres_user)
|
set_flags_in_settings_files()
|
||||||
initialize_local_settings()
|
|
||||||
initialize_test_settings()
|
|
||||||
|
|
||||||
if '{{ cookiecutter.open_source_license }}' == 'Not open source':
|
if "{{ cookiecutter.open_source_license }}" == "Not open source":
|
||||||
remove_open_source_project_only_files()
|
remove_open_source_files()
|
||||||
elif '{{ cookiecutter.open_source_license}}' != 'GPLv3':
|
if "{{ cookiecutter.open_source_license}}" != "GPLv3":
|
||||||
remove_gplv3_files()
|
remove_gplv3_files()
|
||||||
|
|
||||||
if '{{ cookiecutter.use_pycharm }}'.lower() == 'n':
|
if "{{ cookiecutter.use_pycharm }}".lower() == "n":
|
||||||
remove_pycharm_files()
|
remove_pycharm_files()
|
||||||
|
|
||||||
if '{{ cookiecutter.use_docker }}'.lower() == 'n':
|
if "{{ cookiecutter.use_docker }}".lower() == "n":
|
||||||
remove_docker_files()
|
remove_docker_files()
|
||||||
|
|
||||||
remove_paas_files()
|
if "{{ cookiecutter.use_heroku }}".lower() == "n":
|
||||||
|
remove_heroku_files()
|
||||||
|
|
||||||
if '{{ cookiecutter.js_task_runner}}'.lower() == 'gulp':
|
if (
|
||||||
|
"{{ cookiecutter.use_docker }}".lower() == "n"
|
||||||
|
and "{{ cookiecutter.use_heroku }}".lower() == "n"
|
||||||
|
):
|
||||||
|
if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y":
|
||||||
|
print(
|
||||||
|
INFO + ".env(s) are only utilized when Docker Compose and/or "
|
||||||
|
"Heroku support is enabled so keeping them does not "
|
||||||
|
"make sense given your current setup." + TERMINATOR
|
||||||
|
)
|
||||||
|
remove_envs_and_associated_files()
|
||||||
|
else:
|
||||||
|
append_to_gitignore_file(".env")
|
||||||
|
append_to_gitignore_file(".envs/*")
|
||||||
|
if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y":
|
||||||
|
append_to_gitignore_file("!.envs/.local/")
|
||||||
|
|
||||||
|
if "{{ cookiecutter.js_task_runner}}".lower() == "gulp":
|
||||||
remove_grunt_files()
|
remove_grunt_files()
|
||||||
elif '{{ cookiecutter.js_task_runner}}'.lower() == 'grunt':
|
elif "{{ cookiecutter.js_task_runner}}".lower() == "grunt":
|
||||||
remove_gulp_files()
|
remove_gulp_files()
|
||||||
else:
|
else:
|
||||||
remove_gulp_files()
|
remove_gulp_files()
|
||||||
remove_grunt_files()
|
remove_grunt_files()
|
||||||
remove_packagejson_file()
|
remove_packagejson_file()
|
||||||
|
if (
|
||||||
if '{{ cookiecutter.js_task_runner }}'.lower() in ['grunt', 'gulp'] \
|
"{{ cookiecutter.js_task_runner }}".lower() in ["grunt", "gulp"]
|
||||||
and '{{ cookiecutter.use_docker }}'.lower() == 'y':
|
and "{{ cookiecutter.use_docker }}".lower() == "y"
|
||||||
TERMINATOR = "\x1b[0m"
|
):
|
||||||
INFO = "\x1b[1;33m [INFO]: "
|
print(
|
||||||
sys.stdout.write(
|
WARNING
|
||||||
INFO +
|
+ "Docker and {} JS task runner ".format(
|
||||||
"Docker and {} JS task runner ".format('{{ cookiecutter.js_task_runner }}'.lower().capitalize()) +
|
"{{ cookiecutter.js_task_runner }}".lower().capitalize()
|
||||||
"working together not supported yet. "
|
)
|
||||||
"You can continue using the generated project like you normally would, "
|
+ "working together not supported yet. "
|
||||||
"however you would need to add a JS task runner service "
|
"You can continue using the generated project like you "
|
||||||
"to your Docker Compose configuration manually." +
|
"normally would, however you would need to add a JS "
|
||||||
TERMINATOR
|
"task runner service to your Docker Compose configuration "
|
||||||
|
"manually." + TERMINATOR
|
||||||
)
|
)
|
||||||
|
|
||||||
if '{{ cookiecutter.use_celery }}'.lower() == 'n':
|
if "{{ cookiecutter.use_celery }}".lower() == "n":
|
||||||
remove_celery_app()
|
remove_celery_app()
|
||||||
|
if "{{ cookiecutter.use_docker }}".lower() == "y":
|
||||||
|
remove_celery_compose_dirs()
|
||||||
|
|
||||||
|
if "{{ cookiecutter.use_travisci }}".lower() == "n":
|
||||||
|
remove_dottravisyml_file()
|
||||||
|
|
||||||
|
print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -6,54 +6,51 @@ NOTE:
|
||||||
|
|
||||||
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only
|
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
project_slug = '{{ cookiecutter.project_slug }}'
|
import sys
|
||||||
if hasattr(project_slug, 'isidentifier'):
|
|
||||||
assert project_slug.isidentifier(), "'{}' project slug is not a valid Python identifier.".format(project_slug)
|
|
||||||
|
|
||||||
using_docker = '{{ cookiecutter.use_docker }}'.lower()
|
TERMINATOR = "\x1b[0m"
|
||||||
if using_docker == 'n':
|
WARNING = "\x1b[1;33m [WARNING]: "
|
||||||
TERMINATOR = "\x1b[0m"
|
INFO = "\x1b[1;33m [INFO]: "
|
||||||
WARNING = "\x1b[1;33m [WARNING]: "
|
HINT = "\x1b[3;33m"
|
||||||
INFO = "\x1b[1;33m [INFO]: "
|
SUCCESS = "\x1b[1;32m [SUCCESS]: "
|
||||||
HINT = "\x1b[3;33m"
|
|
||||||
SUCCESS = "\x1b[1;32m [SUCCESS]: "
|
|
||||||
|
|
||||||
import sys
|
project_slug = "{{ cookiecutter.project_slug }}"
|
||||||
|
if hasattr(project_slug, "isidentifier"):
|
||||||
|
assert project_slug.isidentifier(), "'{}' project slug is not a valid Python identifier.".format(
|
||||||
|
project_slug
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name."
|
||||||
|
|
||||||
|
if "{{ cookiecutter.use_docker }}".lower() == "n":
|
||||||
python_major_version = sys.version_info[0]
|
python_major_version = sys.version_info[0]
|
||||||
if python_major_version == 2:
|
if python_major_version == 2:
|
||||||
sys.stdout.write(
|
print(
|
||||||
WARNING +
|
WARNING + "Cookiecutter Django does not support Python 2. "
|
||||||
"Cookiecutter Django does not support Python 2. "
|
|
||||||
"Stability is guaranteed with Python 3.6+ only, "
|
"Stability is guaranteed with Python 3.6+ only, "
|
||||||
"are you sure you want to proceed (y/n)? " +
|
"are you sure you want to proceed (y/n)? " + TERMINATOR
|
||||||
TERMINATOR
|
|
||||||
)
|
)
|
||||||
yes_options, no_options = frozenset(['y']), frozenset(['n'])
|
yes_options, no_options = frozenset(["y"]), frozenset(["n"])
|
||||||
while True:
|
while True:
|
||||||
choice = raw_input().lower()
|
choice = raw_input().lower()
|
||||||
if choice in yes_options:
|
if choice in yes_options:
|
||||||
break
|
break
|
||||||
|
|
||||||
elif choice in no_options:
|
elif choice in no_options:
|
||||||
sys.stdout.write(
|
print(INFO + "Generation process stopped as requested." + TERMINATOR)
|
||||||
INFO +
|
|
||||||
"Generation process stopped as requested." +
|
|
||||||
TERMINATOR
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
sys.stdout.write(
|
print(
|
||||||
HINT +
|
HINT
|
||||||
"Please respond with {} or {}: ".format(
|
+ "Please respond with {} or {}: ".format(
|
||||||
', '.join(["'{}'".format(o) for o in yes_options if not o == '']),
|
", ".join(
|
||||||
', '.join(["'{}'".format(o) for o in no_options if not o == ''])
|
["'{}'".format(o) for o in yes_options if not o == ""]
|
||||||
) +
|
),
|
||||||
TERMINATOR
|
", ".join(
|
||||||
|
["'{}'".format(o) for o in no_options if not o == ""]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
+ TERMINATOR
|
||||||
)
|
)
|
||||||
|
|
||||||
sys.stdout.write(
|
|
||||||
SUCCESS +
|
|
||||||
"Project initialized, keep up the good work!" +
|
|
||||||
TERMINATOR
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[tool:pytest]
|
[pytest]
|
||||||
python_paths = .
|
python_paths = .
|
||||||
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/*
|
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/*
|
|
@ -1,11 +1,13 @@
|
||||||
cookiecutter==1.6.0
|
cookiecutter==1.6.0
|
||||||
flake8==3.5.0 # pyup: != 2.6.0
|
|
||||||
sh==1.12.14
|
sh==1.12.14
|
||||||
binaryornot==0.4.4
|
binaryornot==0.4.4
|
||||||
|
|
||||||
|
# Code quality
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
flake8==3.5.0
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
pytest==3.3.2
|
# ------------------------------------------------------------------------------
|
||||||
pycodestyle==2.3.1
|
tox==3.0.0
|
||||||
pyflakes==1.6.0
|
pytest==3.5.1
|
||||||
tox==2.9.1
|
|
||||||
pytest-cookies==0.3.0
|
pytest-cookies==0.3.0
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
# These requirements prevented an upgrade to Django 1.11.
|
|
||||||
django-autoslug==1.9.3
|
|
47
setup.py
47
setup.py
|
@ -10,43 +10,42 @@ except ImportError:
|
||||||
|
|
||||||
# Our version ALWAYS matches the version of Django we support
|
# Our version ALWAYS matches the version of Django we support
|
||||||
# If Django has a new release, we branch, tag, then update this setting after the tag.
|
# If Django has a new release, we branch, tag, then update this setting after the tag.
|
||||||
version = '1.11.9'
|
version = "2.0.2"
|
||||||
|
|
||||||
if sys.argv[-1] == 'tag':
|
if sys.argv[-1] == "tag":
|
||||||
os.system('git tag -a %s -m "version %s"' % (version, version))
|
os.system('git tag -a %s -m "version %s"' % (version, version))
|
||||||
os.system('git push --tags')
|
os.system("git push --tags")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
with open('README.rst') as readme_file:
|
with open("README.rst") as readme_file:
|
||||||
long_description = readme_file.read()
|
long_description = readme_file.read()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='cookiecutter-django',
|
name="cookiecutter-django",
|
||||||
version=version,
|
version=version,
|
||||||
description='A Cookiecutter template for creating production-ready Django projects quickly.',
|
description="A Cookiecutter template for creating production-ready Django projects quickly.",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
author='Daniel Roy Greenfeld',
|
author="Daniel Roy Greenfeld",
|
||||||
author_email='pydanny@gmail.com',
|
author_email="pydanny@gmail.com",
|
||||||
url='https://github.com/pydanny/cookiecutter-django',
|
url="https://github.com/pydanny/cookiecutter-django",
|
||||||
packages=[],
|
packages=[],
|
||||||
license='BSD',
|
license="BSD",
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 4 - Beta',
|
"Development Status :: 4 - Beta",
|
||||||
'Environment :: Console',
|
"Environment :: Console",
|
||||||
'Framework :: Django :: 1.11',
|
"Framework :: Django :: 2.0",
|
||||||
'Intended Audience :: Developers',
|
"Intended Audience :: Developers",
|
||||||
'Natural Language :: English',
|
"Natural Language :: English",
|
||||||
'License :: OSI Approved :: BSD License',
|
"License :: OSI Approved :: BSD License",
|
||||||
'Programming Language :: Python',
|
"Programming Language :: Python",
|
||||||
'Programming Language :: Python :: 3',
|
"Programming Language :: Python :: 3",
|
||||||
'Programming Language :: Python :: 3.6',
|
"Programming Language :: Python :: 3.6",
|
||||||
'Programming Language :: Python :: Implementation :: CPython',
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
'Programming Language :: Python :: Implementation :: PyPy',
|
"Topic :: Software Development",
|
||||||
'Topic :: Software Development',
|
|
||||||
],
|
],
|
||||||
keywords=(
|
keywords=(
|
||||||
'cookiecutter, Python, projects, project templates, django, '
|
"cookiecutter, Python, projects, project templates, django, "
|
||||||
'skeleton, scaffolding, project directory, setup.py'
|
"skeleton, scaffolding, project directory, setup.py"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,21 +5,21 @@ import sh
|
||||||
import pytest
|
import pytest
|
||||||
from binaryornot.check import is_binary
|
from binaryornot.check import is_binary
|
||||||
|
|
||||||
PATTERN = '{{(\s?cookiecutter)[.](.*?)}}'
|
PATTERN = "{{(\s?cookiecutter)[.](.*?)}}"
|
||||||
RE_OBJ = re.compile(PATTERN)
|
RE_OBJ = re.compile(PATTERN)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def context():
|
def context():
|
||||||
return {
|
return {
|
||||||
'project_name': 'My Test Project',
|
"project_name": "My Test Project",
|
||||||
'project_slug': 'my_test_project',
|
"project_slug": "my_test_project",
|
||||||
'author_name': 'Test Author',
|
"author_name": "Test Author",
|
||||||
'email': 'test@example.com',
|
"email": "test@example.com",
|
||||||
'description': 'A short description of the project.',
|
"description": "A short description of the project.",
|
||||||
'domain_name': 'example.com',
|
"domain_name": "example.com",
|
||||||
'version': '0.1.0',
|
"version": "0.1.0",
|
||||||
'timezone': 'UTC',
|
"timezone": "UTC",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,9 +40,10 @@ def check_paths(paths):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if is_binary(path):
|
if is_binary(path):
|
||||||
continue
|
continue
|
||||||
for line in open(path, 'r'):
|
|
||||||
|
for line in open(path, "r"):
|
||||||
match = RE_OBJ.search(line)
|
match = RE_OBJ.search(line)
|
||||||
msg = 'cookiecutter variable not replaced in {}'
|
msg = "cookiecutter variable not replaced in {}"
|
||||||
assert match is None, msg.format(path)
|
assert match is None, msg.format(path)
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ def test_default_configuration(cookies, context):
|
||||||
result = cookies.bake(extra_context=context)
|
result = cookies.bake(extra_context=context)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.exception is None
|
assert result.exception is None
|
||||||
assert result.project.basename == context['project_slug']
|
assert result.project.basename == context["project_slug"]
|
||||||
assert result.project.isdir()
|
assert result.project.isdir()
|
||||||
|
|
||||||
paths = build_files_list(str(result.project))
|
paths = build_files_list(str(result.project))
|
||||||
|
@ -58,9 +59,9 @@ def test_default_configuration(cookies, context):
|
||||||
check_paths(paths)
|
check_paths(paths)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=['use_mailhog', 'use_celery', 'windows'])
|
@pytest.fixture(params=["use_mailhog", "use_celery", "windows"])
|
||||||
def feature_context(request, context):
|
def feature_context(request, context):
|
||||||
context.update({request.param: 'y'})
|
context.update({request.param: "y"})
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ def test_enabled_features(cookies, feature_context):
|
||||||
result = cookies.bake(extra_context=feature_context)
|
result = cookies.bake(extra_context=feature_context)
|
||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert result.exception is None
|
assert result.exception is None
|
||||||
assert result.project.basename == feature_context['project_slug']
|
assert result.project.basename == feature_context["project_slug"]
|
||||||
assert result.project.isdir()
|
assert result.project.isdir()
|
||||||
|
|
||||||
paths = build_files_list(str(result.project))
|
paths = build_files_list(str(result.project))
|
||||||
|
|
|
@ -12,10 +12,13 @@ cd .cache/docker
|
||||||
|
|
||||||
# create the project using the default settings in cookiecutter.json
|
# create the project using the default settings in cookiecutter.json
|
||||||
cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y js_task_runner=None
|
cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y js_task_runner=None
|
||||||
cd project_name
|
cd my_awesome_project
|
||||||
|
|
||||||
# run the project's tests
|
# run the project's tests
|
||||||
docker-compose -f local.yml run django python manage.py test
|
docker-compose -f local.yml run django python manage.py test
|
||||||
|
|
||||||
# return non-zero status code if there are migrations that have not been created
|
# return non-zero status code if there are migrations that have not been created
|
||||||
docker-compose -f local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; }
|
docker-compose -f local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; }
|
||||||
|
|
||||||
|
# Test support for translations
|
||||||
|
docker-compose -f local.yml run django python manage.py makemessages
|
||||||
|
|
9
tox.ini
9
tox.ini
|
@ -3,10 +3,5 @@ skipsdist = true
|
||||||
envlist = py36
|
envlist = py36
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
passenv = LC_ALL, LANG, HOME
|
deps = -rrequirements.txt
|
||||||
deps =
|
commands = pytest {posargs:./tests}
|
||||||
binaryornot
|
|
||||||
flake8==2.5.5
|
|
||||||
pytest-cookies
|
|
||||||
sh
|
|
||||||
commands = py.test {posargs:tests}
|
|
||||||
|
|
7
{{cookiecutter.project_slug}}/.envs/.local/.django
Normal file
7
{{cookiecutter.project_slug}}/.envs/.local/.django
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# General
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
USE_DOCKER=yes
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
REDIS_URL=redis://redis:6379/0
|
5
{{cookiecutter.project_slug}}/.envs/.local/.postgres
Normal file
5
{{cookiecutter.project_slug}}/.envs/.local/.postgres
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# PostgreSQL
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
POSTGRES_DB={{ cookiecutter.project_slug }}
|
||||||
|
POSTGRES_USER=!!!SET POSTGRES_USER!!!
|
||||||
|
POSTGRES_PASSWORD=!!!SET POSTGRES_PASSWORD!!!
|
3
{{cookiecutter.project_slug}}/.envs/.production/.caddy
Normal file
3
{{cookiecutter.project_slug}}/.envs/.production/.caddy
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Caddy
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
DOMAIN_NAME={{ cookiecutter.domain_name }}
|
45
{{cookiecutter.project_slug}}/.envs/.production/.django
Normal file
45
{{cookiecutter.project_slug}}/.envs/.production/.django
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
# General
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# DJANGO_READ_DOT_ENV_FILE=True
|
||||||
|
DJANGO_SETTINGS_MODULE=config.settings.production
|
||||||
|
DJANGO_SECRET_KEY=!!!SET DJANGO_SECRET_KEY!!!
|
||||||
|
DJANGO_ADMIN_URL=!!!SET DJANGO_ADMIN_URL!!!
|
||||||
|
DJANGO_ALLOWED_HOSTS=.{{ cookiecutter.domain_name }}
|
||||||
|
|
||||||
|
# Security
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# TIP: better off using DNS, however, redirect is OK too
|
||||||
|
DJANGO_SECURE_SSL_REDIRECT=False
|
||||||
|
|
||||||
|
# Email
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
MAILGUN_API_KEY=
|
||||||
|
DJANGO_SERVER_EMAIL=
|
||||||
|
MAILGUN_DOMAIN=
|
||||||
|
|
||||||
|
# AWS
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
DJANGO_AWS_ACCESS_KEY_ID=
|
||||||
|
DJANGO_AWS_SECRET_ACCESS_KEY=
|
||||||
|
DJANGO_AWS_STORAGE_BUCKET_NAME=
|
||||||
|
|
||||||
|
# django-allauth
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
DJANGO_ACCOUNT_ALLOW_REGISTRATION=True
|
||||||
|
{% if cookiecutter.use_compressor == 'y' %}
|
||||||
|
# django-compressor
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
COMPRESS_ENABLED=
|
||||||
|
{% endif %}
|
||||||
|
# Gunicorn
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
WEB_CONCURRENCY=4
|
||||||
|
{% if cookiecutter.use_sentry == 'y' %}
|
||||||
|
# Sentry
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
DJANGO_SENTRY_DSN=
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
REDIS_URL=redis://redis:6379/0
|
|
@ -0,0 +1,5 @@
|
||||||
|
# PostgreSQL
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
POSTGRES_DB={{ cookiecutter.project_slug }}
|
||||||
|
POSTGRES_USER=!!!SET POSTGRES_USER!!!
|
||||||
|
POSTGRES_PASSWORD=!!!SET POSTGRES_PASSWORD!!!
|
37
{{cookiecutter.project_slug}}/.gitignore
vendored
37
{{cookiecutter.project_slug}}/.gitignore
vendored
|
@ -53,42 +53,25 @@ coverage.xml
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
staticfiles/
|
staticfiles/
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
.python-version
|
.python-version
|
||||||
|
|
||||||
|
{% if cookiecutter.use_celery == 'y' -%}
|
||||||
# celery beat schedule file
|
# celery beat schedule file
|
||||||
celerybeat-schedule
|
celerybeat-schedule
|
||||||
|
{%- endif %}
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
|
||||||
.venv
|
.venv
|
||||||
env/
|
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
# Rope project settings
|
||||||
.ropeproject
|
.ropeproject
|
||||||
|
|
||||||
|
@ -341,7 +324,7 @@ Session.vim
|
||||||
|
|
||||||
# Auto-generated tag files
|
# Auto-generated tag files
|
||||||
tags
|
tags
|
||||||
|
{% if cookiecutter.use_docker == 'n' %}
|
||||||
|
|
||||||
### VirtualEnv template
|
### VirtualEnv template
|
||||||
# Virtualenv
|
# Virtualenv
|
||||||
|
@ -354,16 +337,10 @@ tags
|
||||||
[Ss]cripts
|
[Ss]cripts
|
||||||
pyvenv.cfg
|
pyvenv.cfg
|
||||||
pip-selfcheck.json
|
pip-selfcheck.json
|
||||||
|
|
||||||
|
|
||||||
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' -%}
|
|
||||||
mailhog
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
### Project template
|
||||||
|
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %}
|
||||||
|
MailHog
|
||||||
|
{%- endif %}
|
||||||
{{ cookiecutter.project_slug }}/media/
|
{{ cookiecutter.project_slug }}/media/
|
||||||
|
|
||||||
{% if cookiecutter.use_docker == 'y' -%}
|
|
||||||
# Added to maintain local compose files which are ignored by something above.
|
|
||||||
# See issue https://github.com/pydanny/cookiecutter-django/issues/1321
|
|
||||||
!/compose/local/
|
|
||||||
{% endif %}
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="merge_production_dotenvs_in_dotenv" type="PythonConfigurationType" factoryName="Python" singleton="true">
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<module name="{{ cookiecutter.project_slug }}" />
|
||||||
|
<EXTENSION ID="PythonCoverageRunConfigurationExtension" enabled="false" sample_coverage="true" runner="coverage.py" />
|
||||||
|
<option name="SCRIPT_NAME" value="merge_production_dotenvs_in_dotenv.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
|
@ -92,7 +92,7 @@ module.exports = function (grunt) {
|
||||||
|
|
||||||
processors: [
|
processors: [
|
||||||
require('pixrem')(), // add fallbacks for rem units
|
require('pixrem')(), // add fallbacks for rem units
|
||||||
require('autoprefixer-core')({browsers: [
|
require('autoprefixer')({browsers: [
|
||||||
'Android 2.3',
|
'Android 2.3',
|
||||||
'Android >= 4',
|
'Android >= 4',
|
||||||
'Chrome >= 20',
|
'Chrome >= 20',
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
release: manage.py migrate
|
||||||
web: gunicorn config.wsgi:application
|
web: gunicorn config.wsgi:application
|
||||||
{% if cookiecutter.use_celery == "y" -%}
|
{% if cookiecutter.use_celery == "y" -%}
|
||||||
worker: celery worker --app={{cookiecutter.project_slug}}.taskapp --loglevel=info
|
worker: celery worker --app={{cookiecutter.project_slug}}.taskapp --loglevel=info
|
||||||
|
|
|
@ -99,7 +99,7 @@ The email server will exit when you exit the Grunt task on the CLI with Ctrl+C.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
.. _mailhog: https://github.com/mailhog/MailHog
|
.. _mailhog: https://github.com/mailhog/MailHog
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == "y" %}
|
{% if cookiecutter.use_sentry == "y" %}
|
||||||
|
|
||||||
Sentry
|
Sentry
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
|
@ -1,8 +1,21 @@
|
||||||
FROM python:3.6
|
FROM python:3.6-alpine
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
|
||||||
# Requirements have to be pulled and installed here, otherwise caching won't work
|
RUN apk update \
|
||||||
|
# psycopg2 dependencies
|
||||||
|
&& apk add --virtual build-deps gcc python3-dev musl-dev \
|
||||||
|
&& apk add postgresql-dev \
|
||||||
|
# Pillow dependencies
|
||||||
|
&& apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
|
||||||
|
# CFFI dependencies
|
||||||
|
&& apk add libffi-dev openssl-dev py-cffi \
|
||||||
|
# Translations dependencies
|
||||||
|
&& apk add gettext \
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/django-admin/#dbshell
|
||||||
|
&& apk add postgresql-client
|
||||||
|
|
||||||
|
# Requirements are installed here to ensure they will be cached.
|
||||||
COPY ./requirements /requirements
|
COPY ./requirements /requirements
|
||||||
RUN pip install -r /requirements/local.txt
|
RUN pip install -r /requirements/local.txt
|
||||||
|
|
||||||
|
@ -13,7 +26,7 @@ RUN chmod +x /entrypoint.sh
|
||||||
COPY ./compose/local/django/start.sh /start.sh
|
COPY ./compose/local/django/start.sh /start.sh
|
||||||
RUN sed -i 's/\r//' /start.sh
|
RUN sed -i 's/\r//' /start.sh
|
||||||
RUN chmod +x /start.sh
|
RUN chmod +x /start.sh
|
||||||
|
{% if cookiecutter.use_celery == "y" %}
|
||||||
COPY ./compose/local/django/celery/worker/start.sh /start-celeryworker.sh
|
COPY ./compose/local/django/celery/worker/start.sh /start-celeryworker.sh
|
||||||
RUN sed -i 's/\r//' /start-celeryworker.sh
|
RUN sed -i 's/\r//' /start-celeryworker.sh
|
||||||
RUN chmod +x /start-celeryworker.sh
|
RUN chmod +x /start-celeryworker.sh
|
||||||
|
@ -21,7 +34,7 @@ RUN chmod +x /start-celeryworker.sh
|
||||||
COPY ./compose/local/django/celery/beat/start.sh /start-celerybeat.sh
|
COPY ./compose/local/django/celery/beat/start.sh /start-celerybeat.sh
|
||||||
RUN sed -i 's/\r//' /start-celerybeat.sh
|
RUN sed -i 's/\r//' /start-celerybeat.sh
|
||||||
RUN chmod +x /start-celerybeat.sh
|
RUN chmod +x /start-celerybeat.sh
|
||||||
|
{% endif %}
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENTRYPOINT ["/entrypoint.sh"]
|
ENTRYPOINT ["/entrypoint.sh"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
FROM python:3.6
|
FROM python:3.6-alpine
|
||||||
|
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
|
||||||
RUN groupadd -r django \
|
RUN apk update \
|
||||||
&& useradd -r -g django django
|
# psycopg2 dependencies
|
||||||
|
&& apk add --virtual build-deps gcc python3-dev musl-dev \
|
||||||
|
&& apk add postgresql-dev \
|
||||||
|
# Pillow dependencies
|
||||||
|
&& apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
|
||||||
|
# CFFI dependencies
|
||||||
|
&& apk add libffi-dev openssl-dev py-cffi
|
||||||
|
|
||||||
# Requirements have to be pulled and installed here, otherwise caching won't work
|
RUN addgroup -S django \
|
||||||
|
&& adduser -S -G django django
|
||||||
|
|
||||||
|
# Requirements are installed here to ensure they will be cached.
|
||||||
COPY ./requirements /requirements
|
COPY ./requirements /requirements
|
||||||
RUN pip install --no-cache-dir -r /requirements/production.txt \
|
RUN pip install --no-cache-dir -r /requirements/production.txt \
|
||||||
&& rm -rf /requirements
|
&& rm -rf /requirements
|
||||||
|
@ -19,7 +28,7 @@ COPY ./compose/production/django/entrypoint.sh /entrypoint.sh
|
||||||
RUN sed -i 's/\r//' /entrypoint.sh
|
RUN sed -i 's/\r//' /entrypoint.sh
|
||||||
RUN chmod +x /entrypoint.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
RUN chown django /entrypoint.sh
|
RUN chown django /entrypoint.sh
|
||||||
|
{% if cookiecutter.use_celery == "y" %}
|
||||||
COPY ./compose/production/django/celery/worker/start.sh /start-celeryworker.sh
|
COPY ./compose/production/django/celery/worker/start.sh /start-celeryworker.sh
|
||||||
RUN sed -i 's/\r//' /start-celeryworker.sh
|
RUN sed -i 's/\r//' /start-celeryworker.sh
|
||||||
RUN chmod +x /start-celeryworker.sh
|
RUN chmod +x /start-celeryworker.sh
|
||||||
|
@ -27,7 +36,7 @@ RUN chmod +x /start-celeryworker.sh
|
||||||
COPY ./compose/production/django/celery/beat/start.sh /start-celerybeat.sh
|
COPY ./compose/production/django/celery/beat/start.sh /start-celerybeat.sh
|
||||||
RUN sed -i 's/\r//' /start-celerybeat.sh
|
RUN sed -i 's/\r//' /start-celerybeat.sh
|
||||||
RUN chmod +x /start-celerybeat.sh
|
RUN chmod +x /start-celerybeat.sh
|
||||||
|
{% endif %}
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
RUN chown -R django /app
|
RUN chown -R django /app
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
|
@ -1,46 +1,46 @@
|
||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
set -o nounset
|
||||||
# todo: turn on after #1295
|
|
||||||
# set -o nounset
|
|
||||||
|
|
||||||
|
|
||||||
cmd="$@"
|
cmd="$@"
|
||||||
|
|
||||||
# This entrypoint is used to play nicely with the current cookiecutter configuration.
|
# N.B. If only .env files supported variable expansion...
|
||||||
# Since docker-compose relies heavily on environment variables itself for configuration, we'd have to define multiple
|
export CELERY_BROKER_URL="${REDIS_URL}"
|
||||||
# environment variables just to support cookiecutter out of the box. That makes no sense, so this little entrypoint
|
|
||||||
# does all this for us.
|
|
||||||
export REDIS_URL=redis://redis:6379
|
|
||||||
|
|
||||||
# the official postgres image uses 'postgres' as default user if not set explictly.
|
if [ -z "${POSTGRES_USER}" ]; then
|
||||||
if [ -z "$POSTGRES_USER" ]; then
|
base_postgres_image_default_user='postgres'
|
||||||
export POSTGRES_USER=postgres
|
export POSTGRES_USER="${base_postgres_image_default_user}"
|
||||||
fi
|
fi
|
||||||
|
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}"
|
||||||
|
|
||||||
export DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_USER
|
postgres_ready() {
|
||||||
{% if cookiecutter.use_celery == 'y' %}
|
|
||||||
export CELERY_BROKER_URL=$REDIS_URL/0
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
function postgres_ready(){
|
|
||||||
python << END
|
python << END
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn = psycopg2.connect(dbname="$POSTGRES_USER", user="$POSTGRES_USER", password="$POSTGRES_PASSWORD", host="postgres")
|
psycopg2.connect(
|
||||||
|
dbname="${POSTGRES_DB}",
|
||||||
|
user="${POSTGRES_USER}",
|
||||||
|
password="${POSTGRES_PASSWORD}",
|
||||||
|
host="postgres"
|
||||||
|
)
|
||||||
except psycopg2.OperationalError:
|
except psycopg2.OperationalError:
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
END
|
END
|
||||||
}
|
}
|
||||||
|
|
||||||
until postgres_ready; do
|
until postgres_ready; do
|
||||||
>&2 echo "Postgres is unavailable - sleeping"
|
>&2 echo 'PostgreSQL is unavailable (sleeping)...'
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
>&2 echo "Postgres is up - continuing..."
|
>&2 echo 'PostgreSQL is up - continuing...'
|
||||||
|
|
||||||
exec $cmd
|
exec $cmd
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#!/usr/bin/env bash
|
#!/bin/sh
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
@ -6,4 +6,4 @@ set -o nounset
|
||||||
|
|
||||||
|
|
||||||
python /app/manage.py collectstatic --noinput
|
python /app/manage.py collectstatic --noinput
|
||||||
/usr/local/bin/gunicorn config.wsgi -w 4 -b 0.0.0.0:5000 --chdir=/app
|
/usr/local/bin/gunicorn config.wsgi -b 0.0.0.0:5000 --chdir=/app
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
FROM postgres:{{ cookiecutter.postgresql_version }}
|
FROM postgres:{{ cookiecutter.postgresql_version }}
|
||||||
|
|
||||||
COPY ./compose/production/postgres/backup.sh /usr/local/bin/backup
|
COPY ./compose/production/postgres/maintenance /usr/local/bin/maintenance
|
||||||
RUN chmod +x /usr/local/bin/backup
|
RUN chmod +x /usr/local/bin/maintenance/*
|
||||||
|
RUN mv /usr/local/bin/maintenance/* /usr/local/bin \
|
||||||
COPY ./compose/production/postgres/restore.sh /usr/local/bin/restore
|
&& rmdir /usr/local/bin/maintenance
|
||||||
RUN chmod +x /usr/local/bin/restore
|
|
||||||
|
|
||||||
COPY ./compose/production/postgres/list-backups.sh /usr/local/bin/list-backups
|
|
||||||
RUN chmod +x /usr/local/bin/list-backups
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o pipefail
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
|
|
||||||
# we might run into trouble when using the default `postgres` user, e.g. when dropping the postgres
|
|
||||||
# database in restore.sh. Check that something else is used here
|
|
||||||
if [ "$POSTGRES_USER" == "postgres" ]
|
|
||||||
then
|
|
||||||
echo "creating a backup as the postgres user is not supported, make sure to set the POSTGRES_USER environment variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# export the postgres password so that subsequent commands don't ask for it
|
|
||||||
export PGPASSWORD=$POSTGRES_PASSWORD
|
|
||||||
|
|
||||||
echo "creating backup"
|
|
||||||
echo "---------------"
|
|
||||||
|
|
||||||
FILENAME=backup_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz
|
|
||||||
pg_dump -h postgres -U $POSTGRES_USER | gzip > /backups/$FILENAME
|
|
||||||
|
|
||||||
echo "successfully created backup $FILENAME"
|
|
|
@ -1,10 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o pipefail
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
|
|
||||||
echo "listing available backups"
|
|
||||||
echo "-------------------------"
|
|
||||||
ls /backups/
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
BACKUP_DIR_PATH='/backups'
|
||||||
|
BACKUP_FILE_PREFIX='backup'
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
countdown() {
|
||||||
|
declare desc="A simple countdown. Source: https://superuser.com/a/611582"
|
||||||
|
local seconds="${1}"
|
||||||
|
local d=$(($(date +%s) + "${seconds}"))
|
||||||
|
while [ "$d" -ge `date +%s` ]; do
|
||||||
|
echo -ne "$(date -u --date @$(($d - `date +%s`)) +%H:%M:%S)\r";
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
message_newline() {
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
message_debug()
|
||||||
|
{
|
||||||
|
echo -e "DEBUG: ${@}"
|
||||||
|
}
|
||||||
|
|
||||||
|
message_welcome()
|
||||||
|
{
|
||||||
|
echo -e "\e[1m${@}\e[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
message_warning()
|
||||||
|
{
|
||||||
|
echo -e "\e[33mWARNING\e[0m: ${@}"
|
||||||
|
}
|
||||||
|
|
||||||
|
message_error()
|
||||||
|
{
|
||||||
|
echo -e "\e[31mERROR\e[0m: ${@}"
|
||||||
|
}
|
||||||
|
|
||||||
|
message_info()
|
||||||
|
{
|
||||||
|
echo -e "\e[37mINFO\e[0m: ${@}"
|
||||||
|
}
|
||||||
|
|
||||||
|
message_suggestion()
|
||||||
|
{
|
||||||
|
echo -e "\e[33mSUGGESTION\e[0m: ${@}"
|
||||||
|
}
|
||||||
|
|
||||||
|
message_success()
|
||||||
|
{
|
||||||
|
echo -e "\e[32mSUCCESS\e[0m: ${@}"
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
yes_no() {
|
||||||
|
declare desc="Prompt for confirmation. \$\"\{1\}\": confirmation message."
|
||||||
|
local arg1="${1}"
|
||||||
|
|
||||||
|
local response=
|
||||||
|
read -r -p "${arg1} (y/[n])? " response
|
||||||
|
if [[ "${response}" =~ ^[Yy]$ ]]
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
### Create a database backup.
|
||||||
|
###
|
||||||
|
### Usage:
|
||||||
|
### $ docker-compose -f <environment>.yml (exec |run --rm) postgres backup
|
||||||
|
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o pipefail
|
||||||
|
set -o nounset
|
||||||
|
|
||||||
|
|
||||||
|
working_dir="$(dirname ${0})"
|
||||||
|
source "${working_dir}/_sourced/constants.sh"
|
||||||
|
source "${working_dir}/_sourced/messages.sh"
|
||||||
|
|
||||||
|
|
||||||
|
message_welcome "Backing up the '${POSTGRES_DB}' database..."
|
||||||
|
|
||||||
|
|
||||||
|
if [[ "${POSTGRES_USER}" == "postgres" ]]; then
|
||||||
|
message_error "Backing up as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PGHOST="postgres"
|
||||||
|
export PGUSER="${POSTGRES_USER}"
|
||||||
|
export PGPASSWORD="${POSTGRES_PASSWORD}"
|
||||||
|
export PGDATABASE="${POSTGRES_DB}"
|
||||||
|
|
||||||
|
backup_filename="${BACKUP_FILE_PREFIX}_$(date +'%Y_%m_%dT%H_%M_%S').sql.gz"
|
||||||
|
pg_dump | gzip > "${BACKUP_DIR_PATH}/${backup_filename}"
|
||||||
|
|
||||||
|
|
||||||
|
message_success "'${POSTGRES_DB}' database backup '${backup_filename}' has been created and placed in '${BACKUP_DIR_PATH}'."
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
### View backups.
|
||||||
|
###
|
||||||
|
### Usage:
|
||||||
|
### $ docker-compose -f <environment>.yml (exec |run --rm) postgres backups
|
||||||
|
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o pipefail
|
||||||
|
set -o nounset
|
||||||
|
|
||||||
|
|
||||||
|
working_dir="$(dirname ${0})"
|
||||||
|
source "${working_dir}/_sourced/constants.sh"
|
||||||
|
source "${working_dir}/_sourced/messages.sh"
|
||||||
|
|
||||||
|
|
||||||
|
message_welcome "These are the backups you have got:"
|
||||||
|
|
||||||
|
ls -lht "${BACKUP_DIR_PATH}"
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
### Restore database from a backup.
|
||||||
|
###
|
||||||
|
### Parameters:
|
||||||
|
### <1> filename of an existing backup.
|
||||||
|
###
|
||||||
|
### Usage:
|
||||||
|
### $ docker-compose -f <environment>.yml (exec |run --rm) postgres restore <1>
|
||||||
|
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o pipefail
|
||||||
|
set -o nounset
|
||||||
|
|
||||||
|
|
||||||
|
working_dir="$(dirname ${0})"
|
||||||
|
source "${working_dir}/_sourced/constants.sh"
|
||||||
|
source "${working_dir}/_sourced/messages.sh"
|
||||||
|
|
||||||
|
|
||||||
|
if [[ -z ${1+x} ]]; then
|
||||||
|
message_error "Backup filename is not specified yet it is a required parameter. Make sure you provide one and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
backup_filename="${BACKUP_DIR_PATH}/${1}"
|
||||||
|
if [[ ! -f "${backup_filename}" ]]; then
|
||||||
|
message_error "No backup with the specified filename found. Check out the 'backups' maintenance script output to see if there is one and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
message_welcome "Restoring the '${POSTGRES_DB}' database from the '${backup_filename}' backup..."
|
||||||
|
|
||||||
|
if [[ "${POSTGRES_USER}" == "postgres" ]]; then
|
||||||
|
message_error "Restoring as 'postgres' user is not supported. Assign 'POSTGRES_USER' env with another one and try again."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export PGHOST="postgres"
|
||||||
|
export PGUSER="${POSTGRES_USER}"
|
||||||
|
export PGPASSWORD="${POSTGRES_PASSWORD}"
|
||||||
|
export PGDATABASE="${POSTGRES_DB}"
|
||||||
|
|
||||||
|
message_info "Dropping the database..."
|
||||||
|
dropdb "${PGDATABASE}"
|
||||||
|
|
||||||
|
message_info "Creating a new database..."
|
||||||
|
createdb --owner="${POSTGRES_USER}"
|
||||||
|
|
||||||
|
message_info "Applying the backup to the new database..."
|
||||||
|
gunzip -c "${backup_filename}" | psql "${POSTGRES_DB}"
|
||||||
|
|
||||||
|
message_success "The '${POSTGRES_DB}' database has been restored from the '${backup_filename}' backup."
|
|
@ -1,58 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -o errexit
|
|
||||||
set -o pipefail
|
|
||||||
set -o nounset
|
|
||||||
|
|
||||||
|
|
||||||
# we might run into trouble when using the default `postgres` user, e.g. when dropping the postgres
|
|
||||||
# database in restore.sh. Check that something else is used here
|
|
||||||
if [ "$POSTGRES_USER" == "postgres" ]
|
|
||||||
then
|
|
||||||
echo "restoring as the postgres user is not supported, make sure to set the POSTGRES_USER environment variable"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# export the postgres password so that subsequent commands don't ask for it
|
|
||||||
export PGPASSWORD=$POSTGRES_PASSWORD
|
|
||||||
|
|
||||||
# check that we have an argument for a filename candidate
|
|
||||||
if [[ $# -eq 0 ]] ; then
|
|
||||||
echo 'usage:'
|
|
||||||
echo ' docker-compose -f production.yml run postgres restore <backup-file>'
|
|
||||||
echo ''
|
|
||||||
echo 'to get a list of available backups, run:'
|
|
||||||
echo ' docker-compose -f production.yml run postgres list-backups'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# set the backupfile variable
|
|
||||||
BACKUPFILE=/backups/$1
|
|
||||||
|
|
||||||
# check that the file exists
|
|
||||||
if ! [ -f $BACKUPFILE ]; then
|
|
||||||
echo "backup file not found"
|
|
||||||
echo 'to get a list of available backups, run:'
|
|
||||||
echo ' docker-compose -f production.yml run postgres list-backups'
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "beginning restore from $1"
|
|
||||||
echo "-------------------------"
|
|
||||||
|
|
||||||
# delete the db
|
|
||||||
# deleting the db can fail. Spit out a comment if this happens but continue since the db
|
|
||||||
# is created in the next step
|
|
||||||
echo "deleting old database $POSTGRES_USER"
|
|
||||||
if dropdb -h postgres -U $POSTGRES_USER $POSTGRES_USER
|
|
||||||
then echo "deleted $POSTGRES_USER database"
|
|
||||||
else echo "database $POSTGRES_USER does not exist, continue"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# create a new database
|
|
||||||
echo "creating new database $POSTGRES_USER"
|
|
||||||
createdb -h postgres -U $POSTGRES_USER $POSTGRES_USER -O $POSTGRES_USER
|
|
||||||
|
|
||||||
# restore the database
|
|
||||||
echo "restoring database $POSTGRES_USER"
|
|
||||||
gunzip -c $BACKUPFILE | psql -h postgres -U $POSTGRES_USER
|
|
|
@ -1,232 +1,119 @@
|
||||||
"""
|
"""
|
||||||
Base settings for {{cookiecutter.project_name}} project.
|
Base settings to build other settings files upon.
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/dev/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/dev/ref/settings/
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import environ
|
import environ
|
||||||
|
|
||||||
ROOT_DIR = environ.Path(__file__) - 3 # ({{ cookiecutter.project_slug }}/config/settings/base.py - 3 = {{ cookiecutter.project_slug }}/)
|
ROOT_DIR = environ.Path(__file__) - 3 # ({{ cookiecutter.project_slug }}/config/settings/base.py - 3 = {{ cookiecutter.project_slug }}/)
|
||||||
APPS_DIR = ROOT_DIR.path('{{ cookiecutter.project_slug }}')
|
APPS_DIR = ROOT_DIR.path('{{ cookiecutter.project_slug }}')
|
||||||
|
|
||||||
# Load operating system environment variables and then prepare to use them
|
|
||||||
env = environ.Env()
|
env = environ.Env()
|
||||||
|
|
||||||
# .env file, should load only in development environment
|
|
||||||
READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=False)
|
READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=False)
|
||||||
|
|
||||||
if READ_DOT_ENV_FILE:
|
if READ_DOT_ENV_FILE:
|
||||||
# Operating System Environment variables have precedence over variables defined in the .env file,
|
# OS environment variables take precedence over variables from .env
|
||||||
# that is to say variables from the .env files will only be used if not defined
|
env.read_env(str(ROOT_DIR.path('.env')))
|
||||||
# as environment variables.
|
|
||||||
env_file = str(ROOT_DIR.path('.env'))
|
|
||||||
print('Loading : {}'.format(env_file))
|
|
||||||
env.read_env(env_file)
|
|
||||||
print('The .env file has been loaded. See base.py for more information')
|
|
||||||
|
|
||||||
# APP CONFIGURATION
|
# GENERAL
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||||
|
DEBUG = env.bool('DJANGO_DEBUG', False)
|
||||||
|
# Local time zone. Choices are
|
||||||
|
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
||||||
|
# though not all of them may be available with every OS.
|
||||||
|
# In Windows, this must be set to your system time zone.
|
||||||
|
TIME_ZONE = '{{ cookiecutter.timezone }}'
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
||||||
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#site-id
|
||||||
|
SITE_ID = 1
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
|
||||||
|
USE_I18N = True
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
|
||||||
|
USE_L10N = True
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
# DATABASES
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#databases
|
||||||
|
{% if cookiecutter.use_docker == 'y' -%}
|
||||||
|
DATABASES = {
|
||||||
|
'default': env.db('DATABASE_URL'),
|
||||||
|
}
|
||||||
|
{%- else %}
|
||||||
|
DATABASES = {
|
||||||
|
'default': env.db('DATABASE_URL', default='postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}'),
|
||||||
|
}
|
||||||
|
{%- endif %}
|
||||||
|
DATABASES['default']['ATOMIC_REQUESTS'] = True
|
||||||
|
|
||||||
|
# URLS
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
|
||||||
|
ROOT_URLCONF = 'config.urls'
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
|
||||||
|
WSGI_APPLICATION = 'config.wsgi.application'
|
||||||
|
|
||||||
|
# APPS
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
DJANGO_APPS = [
|
DJANGO_APPS = [
|
||||||
# Default Django apps:
|
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.sites',
|
'django.contrib.sites',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
# 'django.contrib.humanize', # Handy template tags
|
||||||
# Useful template tags:
|
|
||||||
# 'django.contrib.humanize',
|
|
||||||
|
|
||||||
# Admin
|
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
]
|
]
|
||||||
THIRD_PARTY_APPS = [
|
THIRD_PARTY_APPS = [
|
||||||
'crispy_forms', # Form layouts
|
'crispy_forms',
|
||||||
'allauth', # registration
|
'allauth',
|
||||||
'allauth.account', # registration
|
'allauth.account',
|
||||||
'allauth.socialaccount', # registration
|
'allauth.socialaccount',
|
||||||
|
'rest_framework',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Apps specific for this project go here.
|
|
||||||
LOCAL_APPS = [
|
LOCAL_APPS = [
|
||||||
# custom users app
|
|
||||||
'{{ cookiecutter.project_slug }}.users.apps.UsersConfig',
|
'{{ cookiecutter.project_slug }}.users.apps.UsersConfig',
|
||||||
# Your stuff: custom apps go here
|
# Your stuff: custom apps go here
|
||||||
]
|
]
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
|
||||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||||
|
|
||||||
# MIDDLEWARE CONFIGURATION
|
# MIGRATIONS
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
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',
|
|
||||||
]
|
|
||||||
|
|
||||||
# MIGRATIONS CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules
|
||||||
MIGRATION_MODULES = {
|
MIGRATION_MODULES = {
|
||||||
'sites': '{{ cookiecutter.project_slug }}.contrib.sites.migrations'
|
'sites': '{{ cookiecutter.project_slug }}.contrib.sites.migrations'
|
||||||
}
|
}
|
||||||
|
|
||||||
# DEBUG
|
# AUTHENTICATION
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
# https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends
|
||||||
DEBUG = env.bool('DJANGO_DEBUG', False)
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
# FIXTURE CONFIGURATION
|
'allauth.account.auth_backends.AuthenticationBackend',
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS
|
|
||||||
FIXTURE_DIRS = (
|
|
||||||
str(APPS_DIR.path('fixtures')),
|
|
||||||
)
|
|
||||||
|
|
||||||
# EMAIL CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
|
|
||||||
|
|
||||||
# MANAGER CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins
|
|
||||||
ADMINS = [
|
|
||||||
("""{{cookiecutter.author_name}}""", '{{cookiecutter.email}}'),
|
|
||||||
]
|
]
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model
|
||||||
|
AUTH_USER_MODEL = 'users.User'
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
|
||||||
|
LOGIN_REDIRECT_URL = 'users:redirect'
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#login-url
|
||||||
|
LOGIN_URL = 'account_login'
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers
|
# PASSWORDS
|
||||||
MANAGERS = ADMINS
|
|
||||||
|
|
||||||
# DATABASE CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
|
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
|
||||||
# Uses django-environ to accept uri format
|
|
||||||
# See: https://django-environ.readthedocs.io/en/latest/#supported-types
|
|
||||||
DATABASES = {
|
|
||||||
'default': env.db('DATABASE_URL', default='postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}'),
|
|
||||||
}
|
|
||||||
DATABASES['default']['ATOMIC_REQUESTS'] = True
|
|
||||||
|
|
||||||
|
|
||||||
# GENERAL CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Local time zone for this installation. Choices can be found here:
|
|
||||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
|
|
||||||
# although not all choices may be available on all operating systems.
|
|
||||||
# In a Windows environment this must be set to your system time zone.
|
|
||||||
TIME_ZONE = '{{ cookiecutter.timezone }}'
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
|
|
||||||
SITE_ID = 1
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n
|
|
||||||
USE_L10N = True
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
# TEMPLATE CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#templates
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
|
|
||||||
'DIRS': [
|
|
||||||
str(APPS_DIR.path('templates')),
|
|
||||||
],
|
|
||||||
'OPTIONS': {
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
|
|
||||||
'debug': DEBUG,
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
|
|
||||||
'loaders': [
|
|
||||||
'django.template.loaders.filesystem.Loader',
|
|
||||||
'django.template.loaders.app_directories.Loader',
|
|
||||||
],
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.template.context_processors.i18n',
|
|
||||||
'django.template.context_processors.media',
|
|
||||||
'django.template.context_processors.static',
|
|
||||||
'django.template.context_processors.tz',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
# Your stuff: custom template context processors go here
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
# See: http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
|
|
||||||
CRISPY_TEMPLATE_PACK = 'bootstrap4'
|
|
||||||
|
|
||||||
# STATIC FILE CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
|
|
||||||
STATIC_ROOT = str(ROOT_DIR('staticfiles'))
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
|
|
||||||
STATIC_URL = '/static/'
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
|
|
||||||
STATICFILES_DIRS = [
|
|
||||||
str(APPS_DIR.path('static')),
|
|
||||||
]
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
|
|
||||||
STATICFILES_FINDERS = [
|
|
||||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
|
||||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
|
||||||
]
|
|
||||||
|
|
||||||
# MEDIA CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
|
|
||||||
MEDIA_ROOT = str(APPS_DIR('media'))
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
|
|
||||||
MEDIA_URL = '/media/'
|
|
||||||
|
|
||||||
# URL Configuration
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
ROOT_URLCONF = 'config.urls'
|
|
||||||
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
|
|
||||||
WSGI_APPLICATION = 'config.wsgi.application'
|
|
||||||
|
|
||||||
# PASSWORD STORAGE SETTINGS
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# See https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django
|
|
||||||
PASSWORD_HASHERS = [
|
PASSWORD_HASHERS = [
|
||||||
|
# https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django
|
||||||
'django.contrib.auth.hashers.Argon2PasswordHasher',
|
'django.contrib.auth.hashers.Argon2PasswordHasher',
|
||||||
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||||
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
|
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
|
||||||
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
|
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
|
||||||
'django.contrib.auth.hashers.BCryptPasswordHasher',
|
'django.contrib.auth.hashers.BCryptPasswordHasher',
|
||||||
]
|
]
|
||||||
|
|
||||||
# PASSWORD VALIDATION
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
@ -242,50 +129,142 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# AUTHENTICATION CONFIGURATION
|
# MIDDLEWARE
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
AUTHENTICATION_BACKENDS = [
|
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
MIDDLEWARE = [
|
||||||
'allauth.account.auth_backends.AuthenticationBackend',
|
'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',
|
||||||
]
|
]
|
||||||
|
|
||||||
# Some really nice defaults
|
# STATIC
|
||||||
ACCOUNT_AUTHENTICATION_METHOD = 'username'
|
# ------------------------------------------------------------------------------
|
||||||
ACCOUNT_EMAIL_REQUIRED = True
|
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
|
||||||
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
|
STATIC_ROOT = str(ROOT_DIR('staticfiles'))
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
str(APPS_DIR.path('static')),
|
||||||
|
]
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
|
||||||
|
STATICFILES_FINDERS = [
|
||||||
|
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||||
|
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
|
||||||
|
]
|
||||||
|
|
||||||
ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_ACCOUNT_ALLOW_REGISTRATION', True)
|
# MEDIA
|
||||||
ACCOUNT_ADAPTER = '{{cookiecutter.project_slug}}.users.adapters.AccountAdapter'
|
# ------------------------------------------------------------------------------
|
||||||
SOCIALACCOUNT_ADAPTER = '{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter'
|
# https://docs.djangoproject.com/en/dev/ref/settings/#media-root
|
||||||
|
MEDIA_ROOT = str(APPS_DIR('media'))
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#media-url
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
# Custom user app defaults
|
# TEMPLATES
|
||||||
# Select the correct user model
|
# ------------------------------------------------------------------------------
|
||||||
AUTH_USER_MODEL = 'users.User'
|
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
|
||||||
LOGIN_REDIRECT_URL = 'users:redirect'
|
TEMPLATES = [
|
||||||
LOGIN_URL = 'account_login'
|
{
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
|
||||||
|
'DIRS': [
|
||||||
|
str(APPS_DIR.path('templates')),
|
||||||
|
],
|
||||||
|
'OPTIONS': {
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
|
||||||
|
'debug': DEBUG,
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
|
||||||
|
'loaders': [
|
||||||
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
'django.template.loaders.app_directories.Loader',
|
||||||
|
],
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.template.context_processors.i18n',
|
||||||
|
'django.template.context_processors.media',
|
||||||
|
'django.template.context_processors.static',
|
||||||
|
'django.template.context_processors.tz',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
|
||||||
|
CRISPY_TEMPLATE_PACK = 'bootstrap4'
|
||||||
|
|
||||||
# SLUGLIFIER
|
# FIXTURES
|
||||||
AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify'
|
# ------------------------------------------------------------------------------
|
||||||
{% if cookiecutter.use_celery == 'y' %}
|
# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs
|
||||||
########## CELERY
|
FIXTURE_DIRS = (
|
||||||
|
str(APPS_DIR.path('fixtures')),
|
||||||
|
)
|
||||||
|
|
||||||
|
# EMAIL
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||||
|
EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
|
||||||
|
|
||||||
|
# ADMIN
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Django Admin URL regex.
|
||||||
|
ADMIN_URL = r'^admin/'
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#admins
|
||||||
|
ADMINS = [
|
||||||
|
("""{{cookiecutter.author_name}}""", '{{cookiecutter.email}}'),
|
||||||
|
]
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#managers
|
||||||
|
MANAGERS = ADMINS
|
||||||
|
|
||||||
|
{% if cookiecutter.use_celery == 'y' -%}
|
||||||
|
# Celery
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
INSTALLED_APPS += ['{{cookiecutter.project_slug}}.taskapp.celery.CeleryConfig']
|
INSTALLED_APPS += ['{{cookiecutter.project_slug}}.taskapp.celery.CeleryConfig']
|
||||||
|
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-broker_url
|
||||||
CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='django://')
|
CELERY_BROKER_URL = env('CELERY_BROKER_URL', default='django://')
|
||||||
|
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_backend
|
||||||
if CELERY_BROKER_URL == 'django://':
|
if CELERY_BROKER_URL == 'django://':
|
||||||
CELERY_RESULT_BACKEND = 'redis://'
|
CELERY_RESULT_BACKEND = 'redis://'
|
||||||
else:
|
else:
|
||||||
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
|
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
|
||||||
########## END CELERY
|
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-accept_content
|
||||||
{% endif %}
|
CELERY_ACCEPT_CONTENT = ['json']
|
||||||
|
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_serializer
|
||||||
|
CELERY_TASK_SERIALIZER = 'json'
|
||||||
|
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_serializer
|
||||||
|
CELERY_RESULT_SERIALIZER = 'json'
|
||||||
|
|
||||||
{%- if cookiecutter.use_compressor == 'y'-%}
|
{%- endif %}
|
||||||
|
# django-allauth
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_ACCOUNT_ALLOW_REGISTRATION', True)
|
||||||
|
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||||
|
ACCOUNT_AUTHENTICATION_METHOD = 'username'
|
||||||
|
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||||
|
ACCOUNT_EMAIL_REQUIRED = True
|
||||||
|
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||||
|
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
|
||||||
|
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||||
|
ACCOUNT_ADAPTER = '{{cookiecutter.project_slug}}.users.adapters.AccountAdapter'
|
||||||
|
# https://django-allauth.readthedocs.io/en/latest/configuration.html
|
||||||
|
SOCIALACCOUNT_ADAPTER = '{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter'
|
||||||
|
|
||||||
|
{% if cookiecutter.use_compressor == 'y' -%}
|
||||||
# django-compressor
|
# django-compressor
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://django-compressor.readthedocs.io/en/latest/quickstart/#installation
|
||||||
INSTALLED_APPS += ['compressor']
|
INSTALLED_APPS += ['compressor']
|
||||||
STATICFILES_FINDERS += ['compressor.finders.CompressorFinder']
|
STATICFILES_FINDERS += ['compressor.finders.CompressorFinder']
|
||||||
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
# Your stuff...
|
||||||
# Location of root django.contrib.admin URL, use {% raw %}{% url 'admin:index' %}{% endraw %}
|
|
||||||
ADMIN_URL = r'^admin/'
|
|
||||||
|
|
||||||
# Your common stuff: Below this line define 3rd party library settings
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,47 +1,22 @@
|
||||||
"""
|
|
||||||
Local settings for {{cookiecutter.project_name}} project.
|
|
||||||
|
|
||||||
- Run in Debug mode
|
|
||||||
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' %}
|
|
||||||
- Use mailhog for emails via Docker
|
|
||||||
{% elif cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %}
|
|
||||||
- Use mailhog for emails
|
|
||||||
{% else %}
|
|
||||||
- Use console backend for emails
|
|
||||||
{% endif %}
|
|
||||||
- Add Django Debug Toolbar
|
|
||||||
- Add django-extensions as app
|
|
||||||
"""
|
|
||||||
|
|
||||||
from .base import * # noqa
|
from .base import * # noqa
|
||||||
|
from .base import env
|
||||||
|
|
||||||
# DEBUG
|
# GENERAL
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||||
DEBUG = env.bool('DJANGO_DEBUG', default=True)
|
DEBUG = env.bool('DJANGO_DEBUG', default=True)
|
||||||
TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||||
|
|
||||||
# SECRET CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
|
||||||
# Note: This key only used for development and testing.
|
|
||||||
SECRET_KEY = env('DJANGO_SECRET_KEY', default='!!!SET DJANGO_SECRET_KEY!!!')
|
SECRET_KEY = env('DJANGO_SECRET_KEY', default='!!!SET DJANGO_SECRET_KEY!!!')
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
"localhost",
|
||||||
|
"0.0.0.0",
|
||||||
|
"127.0.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
# Mail settings
|
# CACHES
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
EMAIL_PORT = 1025
|
|
||||||
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' %}
|
|
||||||
EMAIL_HOST = env('EMAIL_HOST', default='mailhog')
|
|
||||||
{% elif cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %}
|
|
||||||
EMAIL_HOST = 'localhost'
|
|
||||||
{% else %}
|
|
||||||
EMAIL_HOST = 'localhost'
|
|
||||||
EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND',
|
|
||||||
default='django.core.mail.backends.console.EmailBackend')
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# CACHING
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#caches
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||||
|
@ -49,40 +24,62 @@ CACHES = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TEMPLATES
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
|
||||||
|
TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # noqa F405
|
||||||
|
|
||||||
|
# EMAIL
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' -%}
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
|
||||||
|
EMAIL_HOST = env('EMAIL_HOST', default='mailhog')
|
||||||
|
{%- elif cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' -%}
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
|
||||||
|
EMAIL_HOST = 'localhost'
|
||||||
|
{%- else -%}
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||||
|
EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend')
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
|
||||||
|
EMAIL_HOST = 'localhost'
|
||||||
|
{%- endif %}
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
|
||||||
|
EMAIL_PORT = 1025
|
||||||
|
|
||||||
# django-debug-toolbar
|
# django-debug-toolbar
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware', ]
|
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites
|
||||||
INSTALLED_APPS += ['debug_toolbar', ]
|
INSTALLED_APPS += ['debug_toolbar'] # noqa F405
|
||||||
|
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware
|
||||||
INTERNAL_IPS = ['127.0.0.1', '10.0.2.2', ]
|
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware'] # noqa F405
|
||||||
{% if cookiecutter.use_docker == 'y' %}
|
# https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config
|
||||||
{# [cookiecutter-django] This is a workaround to flake8 "imported but unused" errors #}
|
|
||||||
import socket
|
|
||||||
import os
|
|
||||||
# tricks to have debug toolbar when developing with docker
|
|
||||||
if os.environ.get('USE_DOCKER') == 'yes':
|
|
||||||
ip = socket.gethostbyname(socket.gethostname())
|
|
||||||
INTERNAL_IPS += [ip[:-1] + '1']
|
|
||||||
{% endif %}
|
|
||||||
DEBUG_TOOLBAR_CONFIG = {
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
'DISABLE_PANELS': [
|
'DISABLE_PANELS': [
|
||||||
'debug_toolbar.panels.redirects.RedirectsPanel',
|
'debug_toolbar.panels.redirects.RedirectsPanel',
|
||||||
],
|
],
|
||||||
'SHOW_TEMPLATE_CONTEXT': True,
|
'SHOW_TEMPLATE_CONTEXT': True,
|
||||||
}
|
}
|
||||||
|
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips
|
||||||
|
INTERNAL_IPS = ['127.0.0.1', '10.0.2.2']
|
||||||
|
{% if cookiecutter.use_docker == 'y' -%}
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
if os.environ.get('USE_DOCKER') == 'yes':
|
||||||
|
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
|
||||||
|
INTERNAL_IPS += [ip[:-1] + '1' for ip in ips]
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
# django-extensions
|
# django-extensions
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
INSTALLED_APPS += ['django_extensions', ]
|
# https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration
|
||||||
|
INSTALLED_APPS += ['django_extensions'] # noqa F405
|
||||||
|
{% if cookiecutter.use_celery == 'y' -%}
|
||||||
|
|
||||||
# TESTING
|
# Celery
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
|
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_always_eager
|
||||||
{% if cookiecutter.use_celery == 'y' %}
|
|
||||||
########## CELERY
|
|
||||||
# In development, all tasks will be executed locally by blocking until the task returns
|
|
||||||
CELERY_ALWAYS_EAGER = True
|
CELERY_ALWAYS_EAGER = True
|
||||||
########## END CELERY
|
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
# Your local stuff: Below this line define 3rd party library settings
|
# Your stuff...
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,203 +1,190 @@
|
||||||
"""
|
|
||||||
Production settings for {{cookiecutter.project_name}} project.
|
|
||||||
|
|
||||||
{% if cookiecutter.use_whitenoise == 'y' -%}
|
|
||||||
- Use WhiteNoise for serving static files{% endif %}
|
|
||||||
- Use Amazon's S3 for storing {% if cookiecutter.use_whitenoise == 'n' -%}static files and {% endif %}uploaded media
|
|
||||||
- Use mailgun to send emails
|
|
||||||
- Use Redis for cache
|
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == 'y' %}
|
|
||||||
- Use sentry for error logging
|
|
||||||
{% endif %}
|
|
||||||
{% if cookiecutter.use_opbeat == 'y' %}
|
|
||||||
- Use opbeat for error reporting
|
|
||||||
{% endif %}
|
|
||||||
"""
|
|
||||||
|
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == 'y' %}
|
|
||||||
import logging
|
import logging
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
from .base import * # noqa
|
from .base import * # noqa
|
||||||
|
from .base import env
|
||||||
|
|
||||||
# SECRET CONFIGURATION
|
# GENERAL
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||||
# Raises ImproperlyConfigured exception if DJANGO_SECRET_KEY not in os.environ
|
|
||||||
SECRET_KEY = env('DJANGO_SECRET_KEY')
|
SECRET_KEY = env('DJANGO_SECRET_KEY')
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
||||||
|
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['{{ cookiecutter.domain_name }}'])
|
||||||
|
|
||||||
|
# DATABASES
|
||||||
# This ensures that Django will be able to detect a secure connection
|
|
||||||
# properly on Heroku.
|
|
||||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
|
||||||
|
|
||||||
{%- if cookiecutter.use_sentry_for_error_reporting == 'y' %}
|
|
||||||
# raven sentry client
|
|
||||||
# See https://docs.sentry.io/clients/python/integrations/django/
|
|
||||||
INSTALLED_APPS += ['raven.contrib.django.raven_compat', ]
|
|
||||||
{% endif %}
|
|
||||||
{%- if cookiecutter.use_whitenoise == 'y' %}
|
|
||||||
# Use Whitenoise to serve static files
|
|
||||||
# See: https://whitenoise.readthedocs.io/
|
|
||||||
WHITENOISE_MIDDLEWARE = ['whitenoise.middleware.WhiteNoiseMiddleware', ]
|
|
||||||
MIDDLEWARE = WHITENOISE_MIDDLEWARE + MIDDLEWARE
|
|
||||||
{% endif %}
|
|
||||||
{%- if cookiecutter.use_sentry_for_error_reporting == 'y' -%}
|
|
||||||
RAVEN_MIDDLEWARE = ['raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware']
|
|
||||||
MIDDLEWARE = RAVEN_MIDDLEWARE + MIDDLEWARE
|
|
||||||
{% endif %}
|
|
||||||
{%- if cookiecutter.use_opbeat == 'y' -%}
|
|
||||||
# opbeat integration
|
|
||||||
# See https://opbeat.com/languages/django/
|
|
||||||
INSTALLED_APPS += ['opbeat.contrib.django', ]
|
|
||||||
OPBEAT = {
|
|
||||||
'ORGANIZATION_ID': env('DJANGO_OPBEAT_ORGANIZATION_ID'),
|
|
||||||
'APP_ID': env('DJANGO_OPBEAT_APP_ID'),
|
|
||||||
'SECRET_TOKEN': env('DJANGO_OPBEAT_SECRET_TOKEN')
|
|
||||||
}
|
|
||||||
MIDDLEWARE = ['opbeat.contrib.django.middleware.OpbeatAPMMiddleware', ] + MIDDLEWARE
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# SECURITY CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# See https://docs.djangoproject.com/en/dev/ref/middleware/#module-django.middleware.security
|
DATABASES['default'] = env.db('DATABASE_URL') # noqa F405
|
||||||
# and https://docs.djangoproject.com/en/dev/howto/deployment/checklist/#run-manage-py-check-deploy
|
DATABASES['default']['ATOMIC_REQUESTS'] = True # noqa F405
|
||||||
|
DATABASES['default']['CONN_MAX_AGE'] = env.int('CONN_MAX_AGE', default=60) # noqa F405
|
||||||
|
|
||||||
# set this to 60 seconds and then to 518400 when you can prove it works
|
# CACHES
|
||||||
SECURE_HSTS_SECONDS = 60
|
|
||||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool(
|
|
||||||
'DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS', default=True)
|
|
||||||
SECURE_CONTENT_TYPE_NOSNIFF = env.bool(
|
|
||||||
'DJANGO_SECURE_CONTENT_TYPE_NOSNIFF', default=True)
|
|
||||||
SECURE_BROWSER_XSS_FILTER = True
|
|
||||||
SESSION_COOKIE_SECURE = True
|
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
|
||||||
SECURE_SSL_REDIRECT = env.bool('DJANGO_SECURE_SSL_REDIRECT', default=True)
|
|
||||||
CSRF_COOKIE_SECURE = True
|
|
||||||
CSRF_COOKIE_HTTPONLY = True
|
|
||||||
X_FRAME_OPTIONS = 'DENY'
|
|
||||||
|
|
||||||
# SITE CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Hosts/domain names that are valid for this site
|
|
||||||
# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
|
|
||||||
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['{{cookiecutter.domain_name}}', ])
|
|
||||||
# END SITE CONFIGURATION
|
|
||||||
|
|
||||||
INSTALLED_APPS += ['gunicorn', ]
|
|
||||||
|
|
||||||
|
|
||||||
# STORAGE CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Uploaded Media Files
|
|
||||||
# ------------------------
|
|
||||||
# See: http://django-storages.readthedocs.io/en/latest/index.html
|
|
||||||
INSTALLED_APPS += ['storages', ]
|
|
||||||
|
|
||||||
AWS_ACCESS_KEY_ID = env('DJANGO_AWS_ACCESS_KEY_ID')
|
|
||||||
AWS_SECRET_ACCESS_KEY = env('DJANGO_AWS_SECRET_ACCESS_KEY')
|
|
||||||
AWS_STORAGE_BUCKET_NAME = env('DJANGO_AWS_STORAGE_BUCKET_NAME')
|
|
||||||
AWS_AUTO_CREATE_BUCKET = True
|
|
||||||
AWS_QUERYSTRING_AUTH = False
|
|
||||||
|
|
||||||
# AWS cache settings, don't change unless you know what you're doing:
|
|
||||||
AWS_EXPIRY = 60 * 60 * 24 * 7
|
|
||||||
|
|
||||||
# TODO See: https://github.com/jschneier/django-storages/issues/47
|
|
||||||
# Revert the following and use str after the above-mentioned bug is fixed in
|
|
||||||
# either django-storage-redux or boto
|
|
||||||
control = 'max-age=%d, s-maxage=%d, must-revalidate' % (AWS_EXPIRY, AWS_EXPIRY)
|
|
||||||
AWS_HEADERS = {
|
|
||||||
'Cache-Control': bytes(control, encoding='latin-1')
|
|
||||||
}
|
|
||||||
|
|
||||||
# URL that handles the media served from MEDIA_ROOT, used for managing
|
|
||||||
# stored files.
|
|
||||||
{% if cookiecutter.use_whitenoise == 'y' -%}
|
|
||||||
MEDIA_URL = 'https://s3.amazonaws.com/%s/' % AWS_STORAGE_BUCKET_NAME
|
|
||||||
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
|
||||||
{% else %}
|
|
||||||
# See:http://stackoverflow.com/questions/10390244/
|
|
||||||
from storages.backends.s3boto3 import S3Boto3Storage
|
|
||||||
StaticRootS3BotoStorage = lambda: S3Boto3Storage(location='static') # noqa
|
|
||||||
MediaRootS3BotoStorage = lambda: S3Boto3Storage(location='media', file_overwrite=False) # noqa
|
|
||||||
DEFAULT_FILE_STORAGE = 'config.settings.production.MediaRootS3BotoStorage'
|
|
||||||
|
|
||||||
MEDIA_URL = 'https://s3.amazonaws.com/%s/media/' % AWS_STORAGE_BUCKET_NAME
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
# Static Assets
|
|
||||||
# ------------------------
|
|
||||||
{% if cookiecutter.use_whitenoise == 'y' -%}
|
|
||||||
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
|
||||||
{% else %}
|
|
||||||
STATIC_URL = 'https://s3.amazonaws.com/%s/static/' % AWS_STORAGE_BUCKET_NAME
|
|
||||||
STATICFILES_STORAGE = 'config.settings.production.StaticRootS3BotoStorage'
|
|
||||||
# See: https://github.com/antonagestam/collectfast
|
|
||||||
# For Django 1.7+, 'collectfast' should come before
|
|
||||||
# 'django.contrib.staticfiles'
|
|
||||||
AWS_PRELOAD_METADATA = True
|
|
||||||
INSTALLED_APPS = ['collectfast', ] + INSTALLED_APPS
|
|
||||||
{%- endif %}
|
|
||||||
{% if cookiecutter.use_compressor == 'y'-%}
|
|
||||||
# COMPRESSOR
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
COMPRESS_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
|
||||||
COMPRESS_URL = STATIC_URL
|
|
||||||
COMPRESS_ENABLED = env.bool('COMPRESS_ENABLED', default=True)
|
|
||||||
{%- endif %}
|
|
||||||
# EMAIL
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
DEFAULT_FROM_EMAIL = env('DJANGO_DEFAULT_FROM_EMAIL',
|
|
||||||
default='{{cookiecutter.project_name}} <noreply@{{cookiecutter.domain_name}}>')
|
|
||||||
EMAIL_SUBJECT_PREFIX = env('DJANGO_EMAIL_SUBJECT_PREFIX', default='[{{cookiecutter.project_name}}]')
|
|
||||||
SERVER_EMAIL = env('DJANGO_SERVER_EMAIL', default=DEFAULT_FROM_EMAIL)
|
|
||||||
|
|
||||||
# Anymail with Mailgun
|
|
||||||
INSTALLED_APPS += ['anymail', ]
|
|
||||||
ANYMAIL = {
|
|
||||||
'MAILGUN_API_KEY': env('DJANGO_MAILGUN_API_KEY'),
|
|
||||||
'MAILGUN_SENDER_DOMAIN': env('MAILGUN_SENDER_DOMAIN')
|
|
||||||
}
|
|
||||||
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend'
|
|
||||||
|
|
||||||
# TEMPLATE CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# See:
|
|
||||||
# https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.cached.Loader
|
|
||||||
TEMPLATES[0]['OPTIONS']['loaders'] = [
|
|
||||||
('django.template.loaders.cached.Loader', [
|
|
||||||
'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ]),
|
|
||||||
]
|
|
||||||
{% set _DEFAULT_CONN_MAX_AGE=60 %}
|
|
||||||
# DATABASE CONFIGURATION
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# Use the Heroku-style specification
|
|
||||||
# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
|
|
||||||
DATABASES['default'] = env.db('DATABASE_URL')
|
|
||||||
DATABASES['default']['CONN_MAX_AGE'] = env.int('CONN_MAX_AGE', default={{ _DEFAULT_CONN_MAX_AGE }})
|
|
||||||
|
|
||||||
|
|
||||||
# CACHING
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
REDIS_LOCATION = '{0}/{1}'.format(env('REDIS_URL', default='redis://127.0.0.1:6379'), 0)
|
|
||||||
|
|
||||||
# Heroku URL does not pass the DB number, so we parse it in
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
'default': {
|
||||||
'BACKEND': 'django_redis.cache.RedisCache',
|
'BACKEND': 'django_redis.cache.RedisCache',
|
||||||
'LOCATION': REDIS_LOCATION,
|
'LOCATION': env('REDIS_URL'),
|
||||||
'OPTIONS': {
|
'OPTIONS': {
|
||||||
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
|
||||||
'IGNORE_EXCEPTIONS': True, # mimics memcache behavior.
|
# Mimicing memcache behavior.
|
||||||
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
|
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
|
||||||
|
'IGNORE_EXCEPTIONS': True,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == 'y' %}
|
# SECURITY
|
||||||
# Sentry Configuration
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header
|
||||||
|
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect
|
||||||
|
SECURE_SSL_REDIRECT = env.bool('DJANGO_SECURE_SSL_REDIRECT', default=True)
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly
|
||||||
|
SESSION_COOKIE_HTTPONLY = True
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly
|
||||||
|
CSRF_COOKIE_HTTPONLY = True
|
||||||
|
# https://docs.djangoproject.com/en/dev/topics/security/#ssl-https
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds
|
||||||
|
# TODO: set this to 60 seconds first and then to 518400 once you prove the former works
|
||||||
|
SECURE_HSTS_SECONDS = 60
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains
|
||||||
|
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool('DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS', default=True)
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload
|
||||||
|
SECURE_HSTS_PRELOAD = env.bool('DJANGO_SECURE_HSTS_PRELOAD', default=True)
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff
|
||||||
|
SECURE_CONTENT_TYPE_NOSNIFF = env.bool('DJANGO_SECURE_CONTENT_TYPE_NOSNIFF', default=True)
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter
|
||||||
|
SECURE_BROWSER_XSS_FILTER = True
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options
|
||||||
|
X_FRAME_OPTIONS = 'DENY'
|
||||||
|
|
||||||
|
# STORAGES
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://django-storages.readthedocs.io/en/latest/#installation
|
||||||
|
INSTALLED_APPS += ['storages'] # noqa F405
|
||||||
|
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||||
|
AWS_ACCESS_KEY_ID = env('DJANGO_AWS_ACCESS_KEY_ID')
|
||||||
|
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||||
|
AWS_SECRET_ACCESS_KEY = env('DJANGO_AWS_SECRET_ACCESS_KEY')
|
||||||
|
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||||
|
AWS_STORAGE_BUCKET_NAME = env('DJANGO_AWS_STORAGE_BUCKET_NAME')
|
||||||
|
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||||
|
AWS_AUTO_CREATE_BUCKET = True
|
||||||
|
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||||
|
AWS_QUERYSTRING_AUTH = False
|
||||||
|
# DO NOT change these unless you know what you're doing.
|
||||||
|
_AWS_EXPIRY = 60 * 60 * 24 * 7
|
||||||
|
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
|
||||||
|
AWS_S3_OBJECT_PARAMETERS = {
|
||||||
|
'CacheControl': f'max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate',
|
||||||
|
}
|
||||||
|
|
||||||
|
# STATIC
|
||||||
|
# ------------------------
|
||||||
|
{% if cookiecutter.use_whitenoise == 'y' -%}
|
||||||
|
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
||||||
|
{%- else %}
|
||||||
|
STATICFILES_STORAGE = 'config.settings.production.StaticRootS3BotoStorage'
|
||||||
|
STATIC_URL = 'https://s3.amazonaws.com/%s/static/' % AWS_STORAGE_BUCKET_NAME
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
# MEDIA
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
{% if cookiecutter.use_whitenoise == 'y' -%}
|
||||||
|
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||||
|
MEDIA_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/'
|
||||||
|
{%- else %}
|
||||||
|
# region http://stackoverflow.com/questions/10390244/
|
||||||
|
from storages.backends.s3boto3 import S3Boto3Storage
|
||||||
|
StaticRootS3BotoStorage = lambda: S3Boto3Storage(location='static') # noqa
|
||||||
|
MediaRootS3BotoStorage = lambda: S3Boto3Storage(location='media', file_overwrite=False) # noqa
|
||||||
|
# endregion
|
||||||
|
DEFAULT_FILE_STORAGE = 'config.settings.production.MediaRootS3BotoStorage'
|
||||||
|
MEDIA_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/media/'
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
|
# TEMPLATES
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
|
||||||
|
TEMPLATES[0]['OPTIONS']['loaders'] = [ # noqa F405
|
||||||
|
(
|
||||||
|
'django.template.loaders.cached.Loader',
|
||||||
|
[
|
||||||
|
'django.template.loaders.filesystem.Loader',
|
||||||
|
'django.template.loaders.app_directories.Loader',
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# EMAIL
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email
|
||||||
|
DEFAULT_FROM_EMAIL = env(
|
||||||
|
'DJANGO_DEFAULT_FROM_EMAIL',
|
||||||
|
default='{{cookiecutter.project_name}} <noreply@{{cookiecutter.domain_name}}>'
|
||||||
|
)
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#server-email
|
||||||
|
SERVER_EMAIL = env('DJANGO_SERVER_EMAIL', default=DEFAULT_FROM_EMAIL)
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix
|
||||||
|
EMAIL_SUBJECT_PREFIX = env('DJANGO_EMAIL_SUBJECT_PREFIX', default='[{{cookiecutter.project_name}}]')
|
||||||
|
|
||||||
|
# ADMIN
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Django Admin URL regex.
|
||||||
|
ADMIN_URL = env('DJANGO_ADMIN_URL')
|
||||||
|
|
||||||
|
# Anymail (Mailgun)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://anymail.readthedocs.io/en/stable/installation/#installing-anymail
|
||||||
|
INSTALLED_APPS += ['anymail'] # noqa F405
|
||||||
|
EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend'
|
||||||
|
# https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference
|
||||||
|
ANYMAIL = {
|
||||||
|
'MAILGUN_API_KEY': env('MAILGUN_API_KEY'),
|
||||||
|
'MAILGUN_SENDER_DOMAIN': env('MAILGUN_DOMAIN')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gunicorn
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
INSTALLED_APPS += ['gunicorn'] # noqa F405
|
||||||
|
|
||||||
|
{% if cookiecutter.use_whitenoise == 'y' -%}
|
||||||
|
# WhiteNoise
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# http://whitenoise.evans.io/en/latest/django.html#enable-whitenoise
|
||||||
|
MIDDLEWARE = ['whitenoise.middleware.WhiteNoiseMiddleware'] + MIDDLEWARE # noqa F405
|
||||||
|
|
||||||
|
{%- endif %}
|
||||||
|
{% if cookiecutter.use_compressor == 'y' -%}
|
||||||
|
# django-compressor
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED
|
||||||
|
COMPRESS_ENABLED = env.bool('COMPRESS_ENABLED', default=True)
|
||||||
|
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE
|
||||||
|
COMPRESS_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||||
|
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL
|
||||||
|
COMPRESS_URL = STATIC_URL
|
||||||
|
|
||||||
|
{%- endif %}
|
||||||
|
{% if cookiecutter.use_whitenoise == 'n' -%}
|
||||||
|
# Collectfast
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://github.com/antonagestam/collectfast#installation
|
||||||
|
INSTALLED_APPS = ['collectfast'] + INSTALLED_APPS # noqa F405
|
||||||
|
AWS_PRELOAD_METADATA = True
|
||||||
|
|
||||||
|
{%- endif %}
|
||||||
|
{% if cookiecutter.use_sentry == 'y' -%}
|
||||||
|
# raven
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.sentry.io/clients/python/integrations/django/
|
||||||
|
INSTALLED_APPS += ['raven.contrib.django.raven_compat'] # noqa F405
|
||||||
|
MIDDLEWARE = ['raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware'] + MIDDLEWARE
|
||||||
|
|
||||||
|
# Sentry
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
SENTRY_DSN = env('DJANGO_SENTRY_DSN')
|
SENTRY_DSN = env('DJANGO_SENTRY_DSN')
|
||||||
SENTRY_CLIENT = env('DJANGO_SENTRY_CLIENT', default='raven.contrib.django.raven_compat.DjangoClient')
|
SENTRY_CLIENT = env('DJANGO_SENTRY_CLIENT', default='raven.contrib.django.raven_compat.DjangoClient')
|
||||||
LOGGING = {
|
LOGGING = {
|
||||||
|
@ -205,7 +192,7 @@ LOGGING = {
|
||||||
'disable_existing_loggers': True,
|
'disable_existing_loggers': True,
|
||||||
'root': {
|
'root': {
|
||||||
'level': 'WARNING',
|
'level': 'WARNING',
|
||||||
'handlers': ['sentry', ],
|
'handlers': ['sentry'],
|
||||||
},
|
},
|
||||||
'formatters': {
|
'formatters': {
|
||||||
'verbose': {
|
'verbose': {
|
||||||
|
@ -227,33 +214,33 @@ LOGGING = {
|
||||||
'loggers': {
|
'loggers': {
|
||||||
'django.db.backends': {
|
'django.db.backends': {
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
'handlers': ['console', ],
|
'handlers': ['console'],
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'raven': {
|
'raven': {
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'handlers': ['console', ],
|
'handlers': ['console'],
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'sentry.errors': {
|
'sentry.errors': {
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'handlers': ['console', ],
|
'handlers': ['console'],
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'django.security.DisallowedHost': {
|
'django.security.DisallowedHost': {
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
'handlers': ['console', 'sentry', ],
|
'handlers': ['console', 'sentry'],
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SENTRY_CELERY_LOGLEVEL = env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO)
|
SENTRY_CELERY_LOGLEVEL = env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO)
|
||||||
RAVEN_CONFIG = {
|
RAVEN_CONFIG = {
|
||||||
'CELERY_LOGLEVEL': env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO),
|
'dsn': SENTRY_DSN
|
||||||
'DSN': SENTRY_DSN
|
|
||||||
}
|
}
|
||||||
{% elif cookiecutter.use_sentry_for_error_reporting == 'n' %}
|
{%- else %}
|
||||||
# LOGGING CONFIGURATION
|
# LOGGING
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
|
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
|
||||||
# A sample logging configuration. The only tangible logging
|
# A sample logging configuration. The only tangible logging
|
||||||
|
@ -278,7 +265,7 @@ LOGGING = {
|
||||||
'handlers': {
|
'handlers': {
|
||||||
'mail_admins': {
|
'mail_admins': {
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
'filters': ['require_debug_false', ],
|
'filters': ['require_debug_false'],
|
||||||
'class': 'django.utils.log.AdminEmailHandler'
|
'class': 'django.utils.log.AdminEmailHandler'
|
||||||
},
|
},
|
||||||
'console': {
|
'console': {
|
||||||
|
@ -289,20 +276,18 @@ LOGGING = {
|
||||||
},
|
},
|
||||||
'loggers': {
|
'loggers': {
|
||||||
'django.request': {
|
'django.request': {
|
||||||
'handlers': ['mail_admins', ],
|
'handlers': ['mail_admins'],
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
'propagate': True
|
'propagate': True
|
||||||
},
|
},
|
||||||
'django.security.DisallowedHost': {
|
'django.security.DisallowedHost': {
|
||||||
'level': 'ERROR',
|
'level': 'ERROR',
|
||||||
'handlers': ['console', 'mail_admins', ],
|
'handlers': ['console', 'mail_admins'],
|
||||||
'propagate': True
|
'propagate': True
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{% endif %}
|
|
||||||
# Custom Admin URL, use {% raw %}{% url 'admin:index' %}{% endraw %}
|
|
||||||
ADMIN_URL = env('DJANGO_ADMIN_URL')
|
|
||||||
|
|
||||||
# Your production stuff: Below this line define 3rd party library settings
|
{%- endif %}
|
||||||
|
# Your stuff...
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
|
@ -1,61 +1,55 @@
|
||||||
"""
|
"""
|
||||||
Test settings for {{cookiecutter.project_name}} project.
|
With these settings, tests run faster.
|
||||||
|
|
||||||
- Used to run tests fast on the continuous integration server and locally
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .base import * # noqa
|
from .base import * # noqa
|
||||||
|
from .base import env
|
||||||
|
|
||||||
|
# GENERAL
|
||||||
# DEBUG
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Turn debug off so tests run faster
|
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
TEMPLATES[0]['OPTIONS']['debug'] = False
|
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
||||||
|
SECRET_KEY = env("DJANGO_SECRET_KEY", default="!!!SET DJANGO_SECRET_KEY!!!")
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#test-runner
|
||||||
|
TEST_RUNNER = "django.test.runner.DiscoverRunner"
|
||||||
|
|
||||||
# SECRET CONFIGURATION
|
# CACHES
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
|
# https://docs.djangoproject.com/en/dev/ref/settings/#caches
|
||||||
# Note: This key only used for development and testing.
|
|
||||||
SECRET_KEY = env('DJANGO_SECRET_KEY', default='!!!SET DJANGO_SECRET_KEY!!!')
|
|
||||||
|
|
||||||
# Mail settings
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
EMAIL_HOST = 'localhost'
|
|
||||||
EMAIL_PORT = 1025
|
|
||||||
|
|
||||||
# In-memory email backend stores messages in django.core.mail.outbox
|
|
||||||
# for unit testing purposes
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
|
|
||||||
|
|
||||||
# CACHING
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Speed advantages of in-memory caching without having to run Memcached
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
"default": {
|
||||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
"BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": ""
|
||||||
'LOCATION': ''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# TESTING
|
# PASSWORDS
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
TEST_RUNNER = 'django.test.runner.DiscoverRunner'
|
# https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers
|
||||||
|
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||||
|
|
||||||
|
# TEMPLATES
|
||||||
# PASSWORD HASHING
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Use fast password hasher so tests run faster
|
# https://docs.djangoproject.com/en/dev/ref/settings/#templates
|
||||||
PASSWORD_HASHERS = [
|
TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG # noqa F405
|
||||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405
|
||||||
|
(
|
||||||
|
"django.template.loaders.cached.Loader",
|
||||||
|
[
|
||||||
|
"django.template.loaders.filesystem.Loader",
|
||||||
|
"django.template.loaders.app_directories.Loader",
|
||||||
|
],
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# TEMPLATE LOADERS
|
# EMAIL
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
|
||||||
|
EMAIL_HOST = "localhost"
|
||||||
|
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
|
||||||
|
EMAIL_PORT = 1025
|
||||||
|
|
||||||
|
# Your stuff...
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Keep templates in memory so tests run faster
|
|
||||||
TEMPLATES[0]['OPTIONS']['loaders'] = [
|
|
||||||
['django.template.loaders.cached.Loader', [
|
|
||||||
'django.template.loaders.filesystem.Loader',
|
|
||||||
'django.template.loaders.app_directories.Loader',
|
|
||||||
], ],
|
|
||||||
]
|
|
||||||
|
|
|
@ -6,32 +6,47 @@ from django.views.generic import TemplateView
|
||||||
from django.views import defaults as default_views
|
from django.views import defaults as default_views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^$', TemplateView.as_view(template_name='pages/home.html'), name='home'),
|
url(r"^$", TemplateView.as_view(template_name="pages/home.html"), name="home"),
|
||||||
url(r'^about/$', TemplateView.as_view(template_name='pages/about.html'), name='about'),
|
url(
|
||||||
|
r"^about/$",
|
||||||
|
TemplateView.as_view(template_name="pages/about.html"),
|
||||||
|
name="about",
|
||||||
|
),
|
||||||
# Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %}
|
# Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %}
|
||||||
url(settings.ADMIN_URL, admin.site.urls),
|
url(settings.ADMIN_URL, admin.site.urls),
|
||||||
|
|
||||||
# User management
|
# User management
|
||||||
url(r'^users/', include('{{ cookiecutter.project_slug }}.users.urls', namespace='users')),
|
url(
|
||||||
url(r'^accounts/', include('allauth.urls')),
|
r"^users/",
|
||||||
|
include("{{ cookiecutter.project_slug }}.users.urls", namespace="users"),
|
||||||
|
),
|
||||||
|
url(r"^accounts/", include("allauth.urls")),
|
||||||
# Your stuff: custom urls includes go here
|
# Your stuff: custom urls includes go here
|
||||||
|
] + static(
|
||||||
|
settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
)
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
# This allows the error pages to be debugged during development, just visit
|
# This allows the error pages to be debugged during development, just visit
|
||||||
# these url in browser to see how these error pages look like.
|
# these url in browser to see how these error pages look like.
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^400/$', default_views.bad_request, kwargs={'exception': Exception('Bad Request!')}),
|
url(
|
||||||
url(r'^403/$', default_views.permission_denied, kwargs={'exception': Exception('Permission Denied')}),
|
r"^400/$",
|
||||||
url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception('Page not Found')}),
|
default_views.bad_request,
|
||||||
url(r'^500/$', default_views.server_error),
|
kwargs={"exception": Exception("Bad Request!")},
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^403/$",
|
||||||
|
default_views.permission_denied,
|
||||||
|
kwargs={"exception": Exception("Permission Denied")},
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^404/$",
|
||||||
|
default_views.page_not_found,
|
||||||
|
kwargs={"exception": Exception("Page not Found")},
|
||||||
|
),
|
||||||
|
url(r"^500/$", default_views.server_error),
|
||||||
]
|
]
|
||||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
urlpatterns = [
|
|
||||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
urlpatterns = [url(r"^__debug__/", include(debug_toolbar.urls))] + urlpatterns
|
||||||
] + urlpatterns
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ app_path = os.path.abspath(os.path.join(
|
||||||
os.path.dirname(os.path.abspath(__file__)), os.pardir))
|
os.path.dirname(os.path.abspath(__file__)), os.pardir))
|
||||||
sys.path.append(os.path.join(app_path, '{{ cookiecutter.project_slug }}'))
|
sys.path.append(os.path.join(app_path, '{{ cookiecutter.project_slug }}'))
|
||||||
|
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == 'y' -%}
|
{% if cookiecutter.use_sentry == 'y' -%}
|
||||||
if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production':
|
if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production':
|
||||||
from raven.contrib.django.raven_compat.middleware.wsgi import Sentry
|
from raven.contrib.django.raven_compat.middleware.wsgi import Sentry
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
@ -39,7 +39,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
|
||||||
# file. This includes Django's development server, if the WSGI_APPLICATION
|
# file. This includes Django's development server, if the WSGI_APPLICATION
|
||||||
# setting points here.
|
# setting points here.
|
||||||
application = get_wsgi_application()
|
application = get_wsgi_application()
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == 'y' -%}
|
{% if cookiecutter.use_sentry == 'y' -%}
|
||||||
if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production':
|
if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production':
|
||||||
application = Sentry(application)
|
application = Sentry(application)
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
|
@ -27,19 +27,19 @@ import sys
|
||||||
extensions = []
|
extensions = []
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
# The suffix of source filenames.
|
# The suffix of source filenames.
|
||||||
source_suffix = '.rst'
|
source_suffix = ".rst"
|
||||||
|
|
||||||
# The encoding of source files.
|
# The encoding of source files.
|
||||||
# source_encoding = 'utf-8-sig'
|
# source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
# The master toctree document.
|
# The master toctree document.
|
||||||
master_doc = 'index'
|
master_doc = "index"
|
||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = '{{ cookiecutter.project_name }}'
|
project = "{{ cookiecutter.project_name }}"
|
||||||
copyright = """{% now 'utc', '%Y' %}, {{ cookiecutter.author_name }}"""
|
copyright = """{% now 'utc', '%Y' %}, {{ cookiecutter.author_name }}"""
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
@ -47,9 +47,9 @@ copyright = """{% now 'utc', '%Y' %}, {{ cookiecutter.author_name }}"""
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '0.1'
|
version = "0.1"
|
||||||
# The full version, including alpha/beta/rc tags.
|
# The full version, including alpha/beta/rc tags.
|
||||||
release = '0.1'
|
release = "0.1"
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
@ -63,7 +63,7 @@ release = '0.1'
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
# List of patterns, relative to source directory, that match files and
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
exclude_patterns = ['_build']
|
exclude_patterns = ["_build"]
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||||
# default_role = None
|
# default_role = None
|
||||||
|
@ -80,7 +80,7 @@ exclude_patterns = ['_build']
|
||||||
# show_authors = False
|
# show_authors = False
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
pygments_style = 'sphinx'
|
pygments_style = "sphinx"
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
# A list of ignored prefixes for module index sorting.
|
||||||
# modindex_common_prefix = []
|
# modindex_common_prefix = []
|
||||||
|
@ -90,7 +90,7 @@ pygments_style = 'sphinx'
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
html_theme = 'default'
|
html_theme = "default"
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
|
@ -119,7 +119,7 @@ html_theme = 'default'
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
html_static_path = ['_static']
|
html_static_path = ["_static"]
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
# using the given strftime format.
|
# using the given strftime format.
|
||||||
|
@ -163,7 +163,7 @@ html_static_path = ['_static']
|
||||||
# html_file_suffix = None
|
# html_file_suffix = None
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = '{{ cookiecutter.project_slug }}doc'
|
htmlhelp_basename = "{{ cookiecutter.project_slug }}doc"
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
@ -171,10 +171,8 @@ htmlhelp_basename = '{{ cookiecutter.project_slug }}doc'
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
# 'papersize': 'letterpaper',
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
# 'pointsize': '10pt',
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
# 'preamble': '',
|
# 'preamble': '',
|
||||||
}
|
}
|
||||||
|
@ -182,10 +180,13 @@ latex_elements = {
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index',
|
(
|
||||||
'{{ cookiecutter.project_slug }}.tex',
|
"index",
|
||||||
'{{ cookiecutter.project_name }} Documentation',
|
"{{ cookiecutter.project_slug }}.tex",
|
||||||
"""{{ cookiecutter.author_name }}""", 'manual'),
|
"{{ cookiecutter.project_name }} Documentation",
|
||||||
|
"""{{ cookiecutter.author_name }}""",
|
||||||
|
"manual",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
@ -214,8 +215,13 @@ latex_documents = [
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('index', '{{ cookiecutter.project_slug }}', '{{ cookiecutter.project_name }} Documentation',
|
(
|
||||||
["""{{ cookiecutter.author_name }}"""], 1)
|
"index",
|
||||||
|
"{{ cookiecutter.project_slug }}",
|
||||||
|
"{{ cookiecutter.project_name }} Documentation",
|
||||||
|
["""{{ cookiecutter.author_name }}"""],
|
||||||
|
1,
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
|
@ -228,9 +234,15 @@ man_pages = [
|
||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', '{{ cookiecutter.project_slug }}', '{{ cookiecutter.project_name }} Documentation',
|
(
|
||||||
"""{{ cookiecutter.author_name }}""", '{{ cookiecutter.project_name }}',
|
"index",
|
||||||
"""{{ cookiecutter.description }}""", 'Miscellaneous'),
|
"{{ cookiecutter.project_slug }}",
|
||||||
|
"{{ cookiecutter.project_name }} Documentation",
|
||||||
|
"""{{ cookiecutter.author_name }}""",
|
||||||
|
"{{ cookiecutter.project_name }}",
|
||||||
|
"""{{ cookiecutter.description }}""",
|
||||||
|
"Miscellaneous",
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
|
|
||||||
# PostgreSQL
|
|
||||||
POSTGRES_PASSWORD=!!!SET POSTGRES_PASSWORD!!!
|
|
||||||
POSTGRES_USER=!!!SET POSTGRES_USER!!!
|
|
||||||
CONN_MAX_AGE=
|
|
||||||
|
|
||||||
# Domain name, used by caddy
|
|
||||||
DOMAIN_NAME={{ cookiecutter.domain_name }}
|
|
||||||
|
|
||||||
# General settings
|
|
||||||
# DJANGO_READ_DOT_ENV_FILE=True
|
|
||||||
DJANGO_ADMIN_URL=
|
|
||||||
DJANGO_SETTINGS_MODULE=config.settings.production
|
|
||||||
DJANGO_SECRET_KEY=!!!SET DJANGO_SECRET_KEY!!!
|
|
||||||
DJANGO_ALLOWED_HOSTS=.{{ cookiecutter.domain_name }}
|
|
||||||
|
|
||||||
# AWS Settings
|
|
||||||
DJANGO_AWS_ACCESS_KEY_ID=
|
|
||||||
DJANGO_AWS_SECRET_ACCESS_KEY=
|
|
||||||
DJANGO_AWS_STORAGE_BUCKET_NAME=
|
|
||||||
|
|
||||||
# Used with email
|
|
||||||
DJANGO_MAILGUN_API_KEY=
|
|
||||||
DJANGO_SERVER_EMAIL=
|
|
||||||
MAILGUN_SENDER_DOMAIN=
|
|
||||||
|
|
||||||
# Security! Better to use DNS for this task, but you can use redirect
|
|
||||||
DJANGO_SECURE_SSL_REDIRECT=False
|
|
||||||
|
|
||||||
# django-allauth
|
|
||||||
DJANGO_ACCOUNT_ALLOW_REGISTRATION=True
|
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == 'y' -%}
|
|
||||||
# Sentry
|
|
||||||
DJANGO_SENTRY_DSN=
|
|
||||||
{% endif %}
|
|
||||||
{% if cookiecutter.use_opbeat == 'y' -%}
|
|
||||||
DJANGO_OPBEAT_ORGANIZATION_ID=
|
|
||||||
DJANGO_OPBEAT_APP_ID=
|
|
||||||
DJANGO_OPBEAT_SECRET_TOKEN=
|
|
||||||
{% endif %}
|
|
||||||
{% if cookiecutter.use_compressor == 'y' -%}
|
|
||||||
COMPRESS_ENABLED=
|
|
||||||
{% endif %}
|
|
|
@ -9,14 +9,17 @@ services:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./compose/local/django/Dockerfile
|
dockerfile: ./compose/local/django/Dockerfile
|
||||||
|
image: {{ cookiecutter.project_slug }}_local_django
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres{% if cookiecutter.use_mailhog == 'y' %}
|
- postgres
|
||||||
- mailhog{% endif %}
|
{%- if cookiecutter.use_mailhog == 'y' %}
|
||||||
|
- mailhog
|
||||||
|
{%- endif %}
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
environment:
|
env_file:
|
||||||
- POSTGRES_USER=!!!SET POSTGRES_USER!!!
|
- ./.envs/.local/.django
|
||||||
- USE_DOCKER=yes
|
- ./.envs/.local/.postgres
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
command: /start.sh
|
command: /start.sh
|
||||||
|
@ -25,38 +28,53 @@ services:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./compose/production/postgres/Dockerfile
|
dockerfile: ./compose/production/postgres/Dockerfile
|
||||||
|
image: {{ cookiecutter.project_slug }}_production_postgres
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data_local:/var/lib/postgresql/data
|
- postgres_data_local:/var/lib/postgresql/data
|
||||||
- postgres_backup_local:/backups
|
- postgres_backup_local:/backups
|
||||||
environment:
|
env_file:
|
||||||
- POSTGRES_USER=!!!SET POSTGRES_USER!!!
|
- ./.envs/.local/.postgres
|
||||||
{% if cookiecutter.use_mailhog == 'y' %}
|
{%- if cookiecutter.use_mailhog == 'y' %}
|
||||||
|
|
||||||
mailhog:
|
mailhog:
|
||||||
image: mailhog/mailhog:v1.0.0
|
image: mailhog/mailhog:v1.0.0
|
||||||
ports:
|
ports:
|
||||||
- "8025:8025"
|
- "8025:8025"
|
||||||
{% endif %}
|
|
||||||
{% if cookiecutter.use_celery == 'y' %}
|
{%- endif %}
|
||||||
|
{%- if cookiecutter.use_celery == 'y' %}
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:3.0
|
image: redis:3.2
|
||||||
|
|
||||||
celeryworker:
|
celeryworker:
|
||||||
# https://github.com/docker/compose/issues/3220
|
|
||||||
<<: *django
|
<<: *django
|
||||||
|
image: {{ cookiecutter.project_slug }}_local_celeryworker
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- postgres{% if cookiecutter.use_mailhog == 'y' %}
|
- postgres
|
||||||
- mailhog{% endif %}
|
{% if cookiecutter.use_mailhog == 'y' -%}
|
||||||
|
- mailhog
|
||||||
|
{%- endif %}
|
||||||
|
env_file:
|
||||||
|
- ./.envs/.local/.django
|
||||||
|
- ./.envs/.local/.postgres
|
||||||
ports: []
|
ports: []
|
||||||
command: /start-celeryworker.sh
|
command: /start-celeryworker.sh
|
||||||
|
|
||||||
celerybeat:
|
celerybeat:
|
||||||
# https://github.com/docker/compose/issues/3220
|
|
||||||
<<: *django
|
<<: *django
|
||||||
|
image: {{ cookiecutter.project_slug }}_local_celerybeat
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- postgres{% if cookiecutter.use_mailhog == 'y' %}
|
- postgres
|
||||||
- mailhog{% endif %}
|
{% if cookiecutter.use_mailhog == 'y' -%}
|
||||||
|
- mailhog
|
||||||
|
{%- endif %}
|
||||||
|
env_file:
|
||||||
|
- ./.envs/.local/.django
|
||||||
|
- ./.envs/.local/.postgres
|
||||||
ports: []
|
ports: []
|
||||||
command: /start-celerybeat.sh
|
command: /start-celerybeat.sh
|
||||||
{% endif %}
|
|
||||||
|
{%- endif %}
|
||||||
|
|
6
{{cookiecutter.project_slug}}/locale/README.rst
Normal file
6
{{cookiecutter.project_slug}}/locale/README.rst
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
Translations
|
||||||
|
============
|
||||||
|
|
||||||
|
Translations will be placed in this folder when running::
|
||||||
|
|
||||||
|
python manage.py makemessages
|
|
@ -2,8 +2,8 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
|
@ -19,11 +19,12 @@ if __name__ == '__main__':
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
"forget to activate a virtual environment?"
|
"forget to activate a virtual environment?"
|
||||||
)
|
)
|
||||||
|
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# This allows easy placement of apps within the interior
|
# This allows easy placement of apps within the interior
|
||||||
# {{ cookiecutter.project_slug }} directory.
|
# {{ cookiecutter.project_slug }} directory.
|
||||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
sys.path.append(os.path.join(current_path, '{{ cookiecutter.project_slug }}'))
|
sys.path.append(os.path.join(current_path, "{{ cookiecutter.project_slug }}"))
|
||||||
|
|
||||||
execute_from_command_line(sys.argv)
|
execute_from_command_line(sys.argv)
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import os
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT_DIR_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
PRODUCTION_DOTENVS_DIR_PATH = os.path.join(ROOT_DIR_PATH, ".envs", ".production")
|
||||||
|
PRODUCTION_DOTENV_FILE_PATHS = [
|
||||||
|
os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".django"),
|
||||||
|
os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".postgres"),
|
||||||
|
os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".caddy"),
|
||||||
|
]
|
||||||
|
DOTENV_FILE_PATH = os.path.join(ROOT_DIR_PATH, ".env")
|
||||||
|
|
||||||
|
|
||||||
|
def merge(
|
||||||
|
output_file_path: str, merged_file_paths: Sequence[str], append_linesep: bool = True
|
||||||
|
) -> None:
|
||||||
|
with open(output_file_path, "w") as output_file:
|
||||||
|
for merged_file_path in merged_file_paths:
|
||||||
|
with open(merged_file_path, "r") as merged_file:
|
||||||
|
merged_file_content = merged_file.read()
|
||||||
|
output_file.write(merged_file_content)
|
||||||
|
if append_linesep:
|
||||||
|
output_file.write(os.linesep)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
merge(DOTENV_FILE_PATH, PRODUCTION_DOTENV_FILE_PATHS)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("merged_file_count", range(3))
|
||||||
|
@pytest.mark.parametrize("append_linesep", [True, False])
|
||||||
|
def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool):
|
||||||
|
tmp_dir_path = str(tmpdir_factory.getbasetemp())
|
||||||
|
|
||||||
|
output_file_path = os.path.join(tmp_dir_path, ".env")
|
||||||
|
|
||||||
|
expected_output_file_content = ""
|
||||||
|
merged_file_paths = []
|
||||||
|
for i in range(merged_file_count):
|
||||||
|
merged_file_ord = i + 1
|
||||||
|
|
||||||
|
merged_filename = ".service{}".format(merged_file_ord)
|
||||||
|
merged_file_path = os.path.join(tmp_dir_path, merged_filename)
|
||||||
|
|
||||||
|
merged_file_content = merged_filename * merged_file_ord
|
||||||
|
|
||||||
|
with open(merged_file_path, "w+") as file:
|
||||||
|
file.write(merged_file_content)
|
||||||
|
|
||||||
|
expected_output_file_content += merged_file_content
|
||||||
|
if append_linesep:
|
||||||
|
expected_output_file_content += os.linesep
|
||||||
|
|
||||||
|
merged_file_paths.append(merged_file_path)
|
||||||
|
|
||||||
|
merge(output_file_path, merged_file_paths, append_linesep)
|
||||||
|
|
||||||
|
with open(output_file_path, "r") as output_file:
|
||||||
|
actual_output_file_content = output_file.read()
|
||||||
|
|
||||||
|
assert actual_output_file_content == expected_output_file_content
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -4,22 +4,22 @@
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
{% if cookiecutter.js_task_runner == 'Grunt' %}
|
{% if cookiecutter.js_task_runner == 'Grunt' %}
|
||||||
"autoprefixer-core": "~5.2.1",
|
"autoprefixer": "~8.1.0",
|
||||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||||
"bootstrap": "^4.0.0",
|
"bootstrap": "^4.0.0",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"connect-livereload": "~0.3.2",
|
"connect-livereload": "~0.6.0",
|
||||||
"cssnano": "~2.1.0",
|
"cssnano": "~3.10.0",
|
||||||
"grunt": "~0.4.5",
|
"grunt": "~1.0.2",
|
||||||
"grunt-bg-shell": "~2.3.1",
|
"grunt-bg-shell": "~2.3.1",
|
||||||
"grunt-contrib-watch": "~0.6.1",
|
"grunt-contrib-watch": "~1.0.0",
|
||||||
"grunt-postcss": "~0.5.5",
|
"grunt-postcss": "~0.9.0",
|
||||||
"grunt-sass": "~1.0.0",
|
"grunt-sass": "~2.1.0",
|
||||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||||
"jquery": "^3.2.1-slim",
|
"jquery": "^3.2.1-slim",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"load-grunt-tasks": "~3.2.0",
|
"load-grunt-tasks": "~3.2.0",
|
||||||
"pixrem": "~1.3.1",
|
"pixrem": "~4.0.1",
|
||||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||||
"popper.js": "^1.12.3",
|
"popper.js": "^1.12.3",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -31,26 +31,34 @@
|
||||||
"browser-sync": "^2.14.0",
|
"browser-sync": "^2.14.0",
|
||||||
"del": "^2.2.2",
|
"del": "^2.2.2",
|
||||||
"gulp": "^3.9.1",
|
"gulp": "^3.9.1",
|
||||||
"gulp-autoprefixer": "^3.1.1",
|
"gulp-autoprefixer": "^5.0.0",
|
||||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||||
"gulp-concat": "^2.6.1",
|
"gulp-concat": "^2.6.1",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"gulp-cssnano": "^2.1.2",
|
"gulp-cssnano": "^2.1.2",
|
||||||
"gulp-imagemin": "^3.0.3",
|
"gulp-imagemin": "^4.1.0",
|
||||||
"gulp-pixrem": "^1.0.0",
|
"gulp-pixrem": "^1.0.0",
|
||||||
"gulp-plumber": "^1.1.0",
|
"gulp-plumber": "^1.1.0",
|
||||||
"gulp-rename": "^1.2.2",
|
"gulp-rename": "^1.2.2",
|
||||||
"gulp-sass": "^2.3.2",
|
"gulp-sass": "^3.1.0",
|
||||||
"gulp-uglify": "^2.0.0",
|
"gulp-uglify": "^3.0.0",
|
||||||
"gulp-util": "^3.0.7",
|
"gulp-util": "^3.0.7",
|
||||||
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
{% if cookiecutter.custom_bootstrap_compilation == 'y' %}
|
||||||
"jquery": "^3.2.1-slim",
|
"jquery": "^3.2.1-slim",
|
||||||
"popper.js": "^1.12.3",
|
"popper.js": "^1.12.3",
|
||||||
{% endif %}
|
{% endif %}
|
||||||
"run-sequence": "^1.2.2"
|
"run-sequence": "^2.1.1"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
{% if cookiecutter.js_task_runner == 'Grunt' %}
|
||||||
|
"dev": "grunt serve"
|
||||||
|
{% elif cookiecutter.js_task_runner == 'Gulp' %}
|
||||||
|
"dev": "gulp"
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,48 +10,65 @@ services:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./compose/production/django/Dockerfile
|
dockerfile: ./compose/production/django/Dockerfile
|
||||||
|
image: {{ cookiecutter.project_slug }}_production_django
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
env_file: .env
|
env_file:
|
||||||
|
- ./.envs/.production/.django
|
||||||
|
- ./.envs/.production/.postgres
|
||||||
command: /gunicorn.sh
|
command: /gunicorn.sh
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./compose/production/postgres/Dockerfile
|
dockerfile: ./compose/production/postgres/Dockerfile
|
||||||
|
image: {{ cookiecutter.project_slug }}_production_postgres
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- postgres_data:/var/lib/postgresql/data
|
||||||
- postgres_backup:/backups
|
- postgres_backup:/backups
|
||||||
env_file: .env
|
env_file:
|
||||||
|
- ./.envs/.production/.postgres
|
||||||
|
|
||||||
caddy:
|
caddy:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./compose/production/caddy/Dockerfile
|
dockerfile: ./compose/production/caddy/Dockerfile
|
||||||
|
image: {{ cookiecutter.project_slug }}_production_caddy
|
||||||
depends_on:
|
depends_on:
|
||||||
- django
|
- django
|
||||||
volumes:
|
volumes:
|
||||||
- caddy:/root/.caddy
|
- caddy:/root/.caddy
|
||||||
env_file: .env
|
env_file:
|
||||||
|
- ./.envs/.production/.caddy
|
||||||
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"
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:3.0
|
image: redis:3.2
|
||||||
{% if cookiecutter.use_celery == 'y' %}
|
{%- if cookiecutter.use_celery == 'y' %}
|
||||||
|
|
||||||
celeryworker:
|
celeryworker:
|
||||||
<<: *django
|
<<: *django
|
||||||
|
image: {{ cookiecutter.project_slug }}_production_celeryworker
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
env_file:
|
||||||
|
- ./.envs/.production/.django
|
||||||
|
- ./.envs/.production/.postgres
|
||||||
command: /start-celeryworker.sh
|
command: /start-celeryworker.sh
|
||||||
|
|
||||||
celerybeat:
|
celerybeat:
|
||||||
<<: *django
|
<<: *django
|
||||||
|
image: {{ cookiecutter.project_slug }}_production_celerybeat
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
env_file:
|
||||||
|
- ./.envs/.production/.django
|
||||||
|
- ./.envs/.production/.postgres
|
||||||
command: /start-celerybeat.sh
|
command: /start-celerybeat.sh
|
||||||
{% endif %}
|
|
||||||
|
{%- endif %}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[pytest]
|
[pytest]
|
||||||
DJANGO_SETTINGS_MODULE=config.settings.test
|
DJANGO_SETTINGS_MODULE=config.settings.test
|
||||||
{% if cookiecutter.js_task_runner != 'None' %}
|
{%- if cookiecutter.js_task_runner != 'None' %}
|
||||||
norecursedirs = node_modules
|
norecursedirs = node_modules
|
||||||
{% endif %}
|
{%- endif %}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# This file is here because many Platforms as a Service look for
|
# This file is expected by Heroku.
|
||||||
# requirements.txt in the root directory of a project.
|
|
||||||
-r requirements/production.txt
|
-r requirements/production.txt
|
||||||
|
|
|
@ -1,61 +1,30 @@
|
||||||
# Wheel 0.25+ needed to install certain packages on CPython 3.5+
|
pytz==2018.4 # https://github.com/stub42/pytz
|
||||||
# like Pillow and psycopg2
|
awesome-slugify==1.6.5 # https://github.com/dimka665/awesome-slugify
|
||||||
# See http://bitly.com/wheel-building-fails-CPython-35
|
Pillow==5.1.0 # https://github.com/python-pillow/Pillow
|
||||||
# Verified bug on Python 3.5.1
|
{%- if cookiecutter.use_compressor == "y" %}
|
||||||
wheel==0.30.0
|
rcssmin==1.0.6{% if cookiecutter.windows == 'y' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin
|
||||||
|
{%- endif %}
|
||||||
|
argon2-cffi==18.1.0 # https://github.com/hynek/argon2_cffi
|
||||||
# Conservative Django
|
{%- if cookiecutter.use_whitenoise == 'y' %}
|
||||||
django==1.11.10 # pyup: <2.0
|
whitenoise==3.3.1 # https://github.com/evansd/whitenoise
|
||||||
|
{%- endif %}
|
||||||
# Configuration
|
redis>=2.10.5 # https://github.com/antirez/redis
|
||||||
django-environ==0.4.4
|
{%- if cookiecutter.use_celery == "y" %}
|
||||||
{% if cookiecutter.use_whitenoise == 'y' -%}
|
celery==3.1.25 # pyup: <4.0 # https://github.com/celery/celery
|
||||||
whitenoise==3.3.1
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
# Django
|
||||||
# Forms
|
# ------------------------------------------------------------------------------
|
||||||
django-crispy-forms==1.7.0
|
django==2.0.5 # pyup: < 2.1 # https://www.djangoproject.com/
|
||||||
|
django-environ==0.4.4 # https://github.com/joke2k/django-environ
|
||||||
# Models
|
django-model-utils==3.1.1 # https://github.com/jazzband/django-model-utils
|
||||||
django-model-utils==3.1.1
|
django-allauth==0.36.0 # https://github.com/pennersr/django-allauth
|
||||||
|
django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms
|
||||||
# Images
|
{%- if cookiecutter.use_compressor == "y" %}
|
||||||
Pillow==5.0.0
|
django-compressor==2.2 # https://github.com/django-compressor/django-compressor
|
||||||
|
|
||||||
# Password storage
|
|
||||||
argon2-cffi==18.1.0
|
|
||||||
|
|
||||||
# For user registration, either via email or social
|
|
||||||
# Well-built with regular release cycles!
|
|
||||||
django-allauth==0.35.0
|
|
||||||
|
|
||||||
{% if cookiecutter.windows == 'y' -%}
|
|
||||||
# On Windows, you must download/install psycopg2 manually
|
|
||||||
# from http://www.lfd.uci.edu/~gohlke/pythonlibs/#psycopg
|
|
||||||
{% else %}
|
|
||||||
# Python-PostgreSQL Database Adapter
|
|
||||||
psycopg2==2.7.4
|
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
django-redis==4.9.0 # https://github.com/niwinz/django-redis
|
||||||
|
|
||||||
# Unicode slugification
|
# Django REST Framework
|
||||||
awesome-slugify==1.6.5
|
djangorestframework==3.8.2 # https://github.com/encode/django-rest-framework
|
||||||
|
coreapi==2.3.3 # https://github.com/core-api/python-client
|
||||||
# Time zones support
|
|
||||||
pytz==2017.3
|
|
||||||
|
|
||||||
# Redis support
|
|
||||||
django-redis==4.8.0
|
|
||||||
redis>=2.10.5
|
|
||||||
|
|
||||||
{% if cookiecutter.use_celery == "y" %}
|
|
||||||
celery==3.1.25
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if cookiecutter.use_compressor == "y" %}
|
|
||||||
rcssmin==1.0.6 {% if cookiecutter.windows == 'y' %}--install-option="--without-c-extensions"{% endif %}
|
|
||||||
django-compressor==2.2
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# Your custom requirements go here
|
|
||||||
|
|
|
@ -1,19 +1,30 @@
|
||||||
# Local development dependencies go here
|
-r ./base.txt
|
||||||
-r base.txt
|
|
||||||
|
|
||||||
coverage==4.5
|
Werkzeug==0.14.1 # https://github.com/pallets/werkzeug
|
||||||
django-coverage-plugin==1.5.0
|
ipdb==0.11 # https://github.com/gotcha/ipdb
|
||||||
|
Sphinx==1.7.4 # https://github.com/sphinx-doc/sphinx
|
||||||
|
{%- if cookiecutter.use_docker == 'y' %}
|
||||||
|
psycopg2==2.7.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||||
|
{%- else %}
|
||||||
|
psycopg2-binary==2.7.4 # https://github.com/psycopg/psycopg2
|
||||||
|
{%- endif %}
|
||||||
|
|
||||||
Sphinx==1.6.7
|
# Testing
|
||||||
django-extensions==1.9.9
|
# ------------------------------------------------------------------------------
|
||||||
Werkzeug==0.14.1
|
pytest==3.5.1 # https://github.com/pytest-dev/pytest
|
||||||
django-test-plus==1.0.22
|
pytest-sugar==0.9.1 # https://github.com/Frozenball/pytest-sugar
|
||||||
factory-boy==2.10.0
|
|
||||||
|
|
||||||
django-debug-toolbar==1.9.1
|
# Code quality
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
flake8==3.5.0 # https://github.com/PyCQA/flake8
|
||||||
|
coverage==4.5.1 # https://github.com/nedbat/coveragepy
|
||||||
|
|
||||||
# improved REPL
|
# Django
|
||||||
ipdb==0.10.3
|
# ------------------------------------------------------------------------------
|
||||||
|
factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy
|
||||||
|
django-test-plus==1.0.22 # https://github.com/revsys/django-test-plus
|
||||||
|
|
||||||
pytest-django==3.1.2
|
django-debug-toolbar==1.9.1 # https://github.com/jazzband/django-debug-toolbar
|
||||||
pytest-sugar==0.9.1
|
django-extensions==2.0.7 # https://github.com/django-extensions/django-extensions
|
||||||
|
django-coverage-plugin==1.5.0 # https://github.com/nedbat/django_coverage_plugin
|
||||||
|
pytest-django==3.2.1 # https://github.com/pytest-dev/pytest-django
|
||||||
|
|
|
@ -1,39 +1,17 @@
|
||||||
# Pro-tip: Try not to put anything here. Avoid dependencies in
|
# PRECAUTION: avoid production dependencies that aren't in development
|
||||||
# production that aren't in development.
|
|
||||||
-r base.txt
|
|
||||||
|
|
||||||
{% if cookiecutter.windows == 'y' -%}
|
-r ./base.txt
|
||||||
# Python-PostgreSQL Database Adapter
|
|
||||||
# Assuming Windows is used locally, and *nix -- in production.
|
gunicorn==19.8.1 # https://github.com/benoitc/gunicorn
|
||||||
# ------------------------------------------------------------
|
psycopg2==2.7.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||||
psycopg2==2.7.4
|
{%- if cookiecutter.use_whitenoise == 'n' %}
|
||||||
|
Collectfast==0.6.2 # https://github.com/antonagestam/collectfast
|
||||||
|
{%- endif %}
|
||||||
|
{%- if cookiecutter.use_sentry == "y" %}
|
||||||
|
raven==6.7.0 # https://github.com/getsentry/raven-python
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
# WSGI Handler
|
# Django
|
||||||
# ------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
gevent==1.2.2
|
django-storages[boto3]==1.6.6 # https://github.com/jschneier/django-storages
|
||||||
gunicorn==19.7.1
|
django-anymail==2.2 # https://github.com/anymail/django-anymail
|
||||||
|
|
||||||
# Static and Media Storage
|
|
||||||
# ------------------------------------------------
|
|
||||||
boto3==1.5.25
|
|
||||||
django-storages==1.6.5
|
|
||||||
{% if cookiecutter.use_whitenoise != 'y' -%}
|
|
||||||
Collectfast==0.6.0
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
# Email backends for Mailgun, Postmark, SendGrid and more
|
|
||||||
# -------------------------------------------------------
|
|
||||||
django-anymail==1.4
|
|
||||||
|
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == "y" -%}
|
|
||||||
# Raven is the Sentry client
|
|
||||||
# --------------------------
|
|
||||||
raven==6.5.0
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
{% if cookiecutter.use_opbeat == "y" -%}
|
|
||||||
# Opbeat agent for performance monitoring
|
|
||||||
# -----------------------------------------
|
|
||||||
opbeat==3.6.1
|
|
||||||
{%- endif %}
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Test dependencies go here.
|
|
||||||
-r base.txt
|
|
||||||
|
|
||||||
{% if cookiecutter.windows == 'y' -%}
|
|
||||||
# Python-PostgreSQL Database Adapter
|
|
||||||
# If using Win for dev, this assumes Unix in test/prod
|
|
||||||
psycopg2==2.7.4
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
coverage==4.5
|
|
||||||
flake8==3.5.0 # pyup: != 2.6.0
|
|
||||||
django-test-plus==1.0.22
|
|
||||||
factory-boy==2.10.0
|
|
||||||
django-coverage-plugin==1.5.0
|
|
||||||
|
|
||||||
# pytest
|
|
||||||
pytest-django==3.1.2
|
|
||||||
pytest-sugar==0.9.1
|
|
|
@ -1 +1 @@
|
||||||
python-3.6.4
|
python-3.6.5
|
||||||
|
|
|
@ -4,4 +4,4 @@ exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
|
||||||
|
|
||||||
[pycodestyle]
|
[pycodestyle]
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
exclude=.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
|
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
|
||||||
|
|
|
@ -35,7 +35,6 @@ if [ -z "$VIRTUAL_ENV" ]; then
|
||||||
else
|
else
|
||||||
|
|
||||||
pip install -r $PROJECT_DIR/requirements/local.txt
|
pip install -r $PROJECT_DIR/requirements/local.txt
|
||||||
pip install -r $PROJECT_DIR/requirements/test.txt
|
|
||||||
{% if cookiecutter.use_heroku == "y" -%}
|
{% if cookiecutter.use_heroku == "y" -%}
|
||||||
pip install -r $PROJECT_DIR/requirements.txt
|
pip install -r $PROJECT_DIR/requirements.txt
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
|
@ -1,2 +1,7 @@
|
||||||
__version__ = '{{ cookiecutter.version }}'
|
__version__ = "{{ cookiecutter.version }}"
|
||||||
__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
|
__version_info__ = tuple(
|
||||||
|
[
|
||||||
|
int(num) if num.isdigit() else num
|
||||||
|
for num in __version__.replace("-", ".", 1).split(".")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
|
@ -9,23 +9,34 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Site',
|
name="Site",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('domain', models.CharField(
|
"id",
|
||||||
max_length=100, verbose_name='domain name', validators=[_simple_domain_name_validator]
|
models.AutoField(
|
||||||
)),
|
verbose_name="ID",
|
||||||
('name', models.CharField(max_length=50, verbose_name='display name')),
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"domain",
|
||||||
|
models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
verbose_name="domain name",
|
||||||
|
validators=[_simple_domain_name_validator],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=50, verbose_name="display name")),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('domain',),
|
"ordering": ("domain",),
|
||||||
'db_table': 'django_site',
|
"db_table": "django_site",
|
||||||
'verbose_name': 'site',
|
"verbose_name": "site",
|
||||||
'verbose_name_plural': 'sites',
|
"verbose_name_plural": "sites",
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
bases=(models.Model,),
|
||||||
managers=[
|
managers=[("objects", django.contrib.sites.models.SiteManager())],
|
||||||
('objects', django.contrib.sites.models.SiteManager()),
|
)
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,17 +4,17 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("sites", "0001_initial")]
|
||||||
('sites', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='site',
|
model_name="site",
|
||||||
name='domain',
|
name="domain",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
max_length=100, unique=True, validators=[django.contrib.sites.models._simple_domain_name_validator],
|
max_length=100,
|
||||||
verbose_name='domain name'
|
unique=True,
|
||||||
|
validators=[django.contrib.sites.models._simple_domain_name_validator],
|
||||||
|
verbose_name="domain name",
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,34 +9,26 @@ from django.db import migrations
|
||||||
|
|
||||||
def update_site_forward(apps, schema_editor):
|
def update_site_forward(apps, schema_editor):
|
||||||
"""Set site domain and name."""
|
"""Set site domain and name."""
|
||||||
Site = apps.get_model('sites', 'Site')
|
Site = apps.get_model("sites", "Site")
|
||||||
Site.objects.update_or_create(
|
Site.objects.update_or_create(
|
||||||
id=settings.SITE_ID,
|
id=settings.SITE_ID,
|
||||||
defaults={
|
defaults={
|
||||||
'domain': '{{cookiecutter.domain_name}}',
|
"domain": "{{cookiecutter.domain_name}}",
|
||||||
'name': '{{cookiecutter.project_name}}'
|
"name": "{{cookiecutter.project_name}}",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def update_site_backward(apps, schema_editor):
|
def update_site_backward(apps, schema_editor):
|
||||||
"""Revert site domain and name to default."""
|
"""Revert site domain and name to default."""
|
||||||
Site = apps.get_model('sites', 'Site')
|
Site = apps.get_model("sites", "Site")
|
||||||
Site.objects.update_or_create(
|
Site.objects.update_or_create(
|
||||||
id=settings.SITE_ID,
|
id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"}
|
||||||
defaults={
|
|
||||||
'domain': 'example.com',
|
|
||||||
'name': 'example.com'
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("sites", "0002_alter_domain_unique")]
|
||||||
('sites', '0002_alter_domain_unique'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [migrations.RunPython(update_site_forward, update_site_backward)]
|
||||||
migrations.RunPython(update_site_forward, update_site_backward),
|
|
||||||
]
|
|
||||||
|
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
@ -24,7 +24,7 @@ class CeleryConfig(AppConfig):
|
||||||
installed_apps = [app_config.name for app_config in apps.get_app_configs()]
|
installed_apps = [app_config.name for app_config in apps.get_app_configs()]
|
||||||
app.autodiscover_tasks(lambda: installed_apps, force=True)
|
app.autodiscover_tasks(lambda: installed_apps, force=True)
|
||||||
|
|
||||||
{% if cookiecutter.use_sentry_for_error_reporting == 'y' -%}
|
{% if cookiecutter.use_sentry == 'y' -%}
|
||||||
if hasattr(settings, 'RAVEN_CONFIG'):
|
if hasattr(settings, 'RAVEN_CONFIG'):
|
||||||
# Celery signal registration
|
# Celery signal registration
|
||||||
{% if cookiecutter.use_pycharm == 'y' -%}
|
{% if cookiecutter.use_pycharm == 'y' -%}
|
||||||
|
@ -41,41 +41,15 @@ class CeleryConfig(AppConfig):
|
||||||
# @formatter:on
|
# @formatter:on
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
raven_client = RavenClient(dsn=settings.RAVEN_CONFIG['DSN'])
|
raven_client = RavenClient(dsn=settings.RAVEN_CONFIG['dsn'])
|
||||||
raven_register_logger_signal(raven_client)
|
raven_register_logger_signal(raven_client)
|
||||||
raven_register_signal(raven_client)
|
raven_register_signal(raven_client)
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
{% if cookiecutter.use_opbeat == 'y' -%}
|
|
||||||
if hasattr(settings, 'OPBEAT'):
|
|
||||||
{% if cookiecutter.use_pycharm == 'y' -%}
|
|
||||||
# Since opbeat is required in production only,
|
|
||||||
# imports might (most surely will) be wiped out
|
|
||||||
# during PyCharm code clean up started
|
|
||||||
# in other environments.
|
|
||||||
# @formatter:off
|
|
||||||
{%- endif %}
|
|
||||||
from opbeat.contrib.django.models import client as opbeat_client
|
|
||||||
from opbeat.contrib.django.models import logger as opbeat_logger
|
|
||||||
from opbeat.contrib.django.models import register_handlers as opbeat_register_handlers
|
|
||||||
from opbeat.contrib.celery import register_signal as opbeat_register_signal
|
|
||||||
{% if cookiecutter.use_pycharm == 'y' -%}
|
|
||||||
# @formatter:on
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
try:
|
|
||||||
opbeat_register_signal(opbeat_client)
|
|
||||||
except Exception as e:
|
|
||||||
opbeat_logger.exception('Failed installing celery hook: %s' % e)
|
|
||||||
|
|
||||||
if 'opbeat.contrib.django' in settings.INSTALLED_APPS:
|
|
||||||
opbeat_register_handlers()
|
|
||||||
{%- endif %}
|
|
||||||
|
|
||||||
|
|
||||||
@app.task(bind=True)
|
@app.task(bind=True)
|
||||||
def debug_task(self):
|
def debug_task(self):
|
||||||
print('Request: {0!r}'.format(self.request)) # pragma: no cover
|
print(f'Request: {self.request!r}') # pragma: no cover
|
||||||
{% else %}
|
{% else %}
|
||||||
# Use this as a starting point for your project with celery.
|
# Use this as a starting point for your project with celery.
|
||||||
# If you are not using celery, you can remove this app
|
# If you are not using celery, you can remove this app
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
|
|
||||||
|
<link rel="icon" href="{% static 'images/favicons/favicon.ico' %}">
|
||||||
|
|
||||||
{% block css %}
|
{% block css %}
|
||||||
{% endraw %}{% if cookiecutter.custom_bootstrap_compilation == "n" %}{% raw %}
|
{% endraw %}{% if cookiecutter.custom_bootstrap_compilation == "n" %}{% raw %}
|
||||||
<!-- Latest compiled and minified Bootstrap 4 beta CSS -->
|
<!-- Latest compiled and minified Bootstrap 4 beta CSS -->
|
||||||
|
@ -22,7 +24,7 @@
|
||||||
<!-- Your stuff: Third-party CSS libraries go here -->
|
<!-- Your stuff: Third-party CSS libraries go here -->
|
||||||
{% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress css %}{% endraw %}{% endif %}{% raw %}
|
{% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress css %}{% endraw %}{% endif %}{% raw %}
|
||||||
<!-- This file stores project-specific CSS -->
|
<!-- This file stores project-specific CSS -->
|
||||||
{% endraw %}{% if cookiecutter.js_task_runner == "Gulp" %}{% raw %}
|
{% endraw %}{% if cookiecutter.js_task_runner == "Gulp" and cookiecutter.use_compressor == "n" %}{% raw %}
|
||||||
<link href="{% static 'css/project.min.css' %}" rel="stylesheet">
|
<link href="{% static 'css/project.min.css' %}" rel="stylesheet">
|
||||||
{% endraw %}{% else %}{% raw %}
|
{% endraw %}{% else %}{% raw %}
|
||||||
<link href="{% static 'css/project.css' %}" rel="stylesheet">
|
<link href="{% static 'css/project.css' %}" rel="stylesheet">
|
||||||
|
|
|
@ -4,10 +4,12 @@ from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||||
|
|
||||||
|
|
||||||
class AccountAdapter(DefaultAccountAdapter):
|
class AccountAdapter(DefaultAccountAdapter):
|
||||||
|
|
||||||
def is_open_for_signup(self, request):
|
def is_open_for_signup(self, request):
|
||||||
return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True)
|
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
|
||||||
|
|
||||||
|
|
||||||
class SocialAccountAdapter(DefaultSocialAccountAdapter):
|
class SocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||||
|
|
||||||
def is_open_for_signup(self, request, sociallogin):
|
def is_open_for_signup(self, request, sociallogin):
|
||||||
return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True)
|
return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True)
|
||||||
|
|
|
@ -6,15 +6,16 @@ from .models import User
|
||||||
|
|
||||||
|
|
||||||
class MyUserChangeForm(UserChangeForm):
|
class MyUserChangeForm(UserChangeForm):
|
||||||
|
|
||||||
class Meta(UserChangeForm.Meta):
|
class Meta(UserChangeForm.Meta):
|
||||||
model = User
|
model = User
|
||||||
|
|
||||||
|
|
||||||
class MyUserCreationForm(UserCreationForm):
|
class MyUserCreationForm(UserCreationForm):
|
||||||
|
|
||||||
error_message = UserCreationForm.error_messages.update({
|
error_message = UserCreationForm.error_messages.update(
|
||||||
'duplicate_username': 'This username has already been taken.'
|
{"duplicate_username": "This username has already been taken."}
|
||||||
})
|
)
|
||||||
|
|
||||||
class Meta(UserCreationForm.Meta):
|
class Meta(UserCreationForm.Meta):
|
||||||
model = User
|
model = User
|
||||||
|
@ -25,15 +26,14 @@ class MyUserCreationForm(UserCreationForm):
|
||||||
User.objects.get(username=username)
|
User.objects.get(username=username)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
return username
|
return username
|
||||||
raise forms.ValidationError(self.error_messages['duplicate_username'])
|
|
||||||
|
raise forms.ValidationError(self.error_messages["duplicate_username"])
|
||||||
|
|
||||||
|
|
||||||
@admin.register(User)
|
@admin.register(User)
|
||||||
class MyUserAdmin(AuthUserAdmin):
|
class MyUserAdmin(AuthUserAdmin):
|
||||||
form = MyUserChangeForm
|
form = MyUserChangeForm
|
||||||
add_form = MyUserCreationForm
|
add_form = MyUserCreationForm
|
||||||
fieldsets = (
|
fieldsets = (("User Profile", {"fields": ("name",)}),) + AuthUserAdmin.fieldsets
|
||||||
('User Profile', {'fields': ('name',)}),
|
list_display = ("username", "name", "is_superuser")
|
||||||
) + AuthUserAdmin.fieldsets
|
search_fields = ["name"]
|
||||||
list_display = ('username', 'name', 'is_superuser')
|
|
||||||
search_fields = ['name']
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class UsersConfig(AppConfig):
|
class UsersConfig(AppConfig):
|
||||||
name = '{{cookiecutter.project_slug}}.users'
|
name = "{{cookiecutter.project_slug}}.users"
|
||||||
verbose_name = "Users"
|
verbose_name = "Users"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
@ -10,4 +10,7 @@ class UsersConfig(AppConfig):
|
||||||
Users system checks
|
Users system checks
|
||||||
Users signal registration
|
Users signal registration
|
||||||
"""
|
"""
|
||||||
pass
|
try:
|
||||||
|
import users.signals # noqa F401
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
|
@ -8,36 +8,125 @@ class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("auth", "0008_alter_user_username_max_length")]
|
||||||
('auth', '0008_alter_user_username_max_length'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name="User",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
(
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
"id",
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
models.AutoField(
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
auto_created=True,
|
||||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
primary_key=True,
|
||||||
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
|
serialize=False,
|
||||||
('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')),
|
verbose_name="ID",
|
||||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
),
|
||||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
),
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
("password", models.CharField(max_length=128, verbose_name="password")),
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
(
|
||||||
('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')),
|
"last_login",
|
||||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
models.DateTimeField(
|
||||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
|
blank=True, null=True, verbose_name="last login"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_superuser",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
||||||
|
verbose_name="superuser status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"username",
|
||||||
|
models.CharField(
|
||||||
|
error_messages={
|
||||||
|
"unique": "A user with that username already exists."
|
||||||
|
},
|
||||||
|
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
||||||
|
max_length=150,
|
||||||
|
unique=True,
|
||||||
|
validators=[
|
||||||
|
django.contrib.auth.validators.UnicodeUsernameValidator()
|
||||||
|
],
|
||||||
|
verbose_name="username",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"first_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=30, verbose_name="first name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"last_name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=150, verbose_name="last name"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
models.EmailField(
|
||||||
|
blank=True, max_length=254, verbose_name="email address"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_staff",
|
||||||
|
models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Designates whether the user can log into this admin site.",
|
||||||
|
verbose_name="staff status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_active",
|
||||||
|
models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
||||||
|
verbose_name="active",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"date_joined",
|
||||||
|
models.DateTimeField(
|
||||||
|
default=django.utils.timezone.now, verbose_name="date joined"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
models.CharField(
|
||||||
|
blank=True, max_length=255, verbose_name="Name of User"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"groups",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Group",
|
||||||
|
verbose_name="groups",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user_permissions",
|
||||||
|
models.ManyToManyField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Specific permissions for this user.",
|
||||||
|
related_name="user_set",
|
||||||
|
related_query_name="user",
|
||||||
|
to="auth.Permission",
|
||||||
|
verbose_name="user permissions",
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name_plural': 'users',
|
"verbose_name_plural": "users",
|
||||||
'verbose_name': 'user',
|
"verbose_name": "user",
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
managers=[
|
managers=[("objects", django.contrib.auth.models.UserManager())],
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
)
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
|
|
||||||
# First Name and Last Name do not cover name patterns
|
# First Name and Last Name do not cover name patterns
|
||||||
# around the globe.
|
# around the globe.
|
||||||
name = models.CharField(_('Name of User'), blank=True, max_length=255)
|
name = models.CharField(_("Name of User"), blank=True, max_length=255)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('users:detail', kwargs={'username': self.username})
|
return reverse("users:detail", kwargs={"username": self.username})
|
||||||
|
|
|
@ -2,10 +2,10 @@ import factory
|
||||||
|
|
||||||
|
|
||||||
class UserFactory(factory.django.DjangoModelFactory):
|
class UserFactory(factory.django.DjangoModelFactory):
|
||||||
username = factory.Sequence(lambda n: 'user-{0}'.format(n))
|
username = factory.Sequence(lambda n: f"user-{n}")
|
||||||
email = factory.Sequence(lambda n: 'user-{0}@example.com'.format(n))
|
email = factory.Sequence(lambda n: f"user-{n}@example.com")
|
||||||
password = factory.PostGenerationMethodCall('set_password', 'password')
|
password = factory.PostGenerationMethodCall("set_password", "password")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = 'users.User'
|
model = "users.User"
|
||||||
django_get_or_create = ('username', )
|
django_get_or_create = ("username",)
|
||||||
|
|
|
@ -6,30 +6,34 @@ from ..admin import MyUserCreationForm
|
||||||
class TestMyUserCreationForm(TestCase):
|
class TestMyUserCreationForm(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.user = self.make_user('notalamode', 'notalamodespassword')
|
self.user = self.make_user("notalamode", "notalamodespassword")
|
||||||
|
|
||||||
def test_clean_username_success(self):
|
def test_clean_username_success(self):
|
||||||
# Instantiate the form with a new username
|
# Instantiate the form with a new username
|
||||||
form = MyUserCreationForm({
|
form = MyUserCreationForm(
|
||||||
'username': 'alamode',
|
{
|
||||||
'password1': '7jefB#f@Cc7YJB]2v',
|
"username": "alamode",
|
||||||
'password2': '7jefB#f@Cc7YJB]2v',
|
"password1": "7jefB#f@Cc7YJB]2v",
|
||||||
})
|
"password2": "7jefB#f@Cc7YJB]2v",
|
||||||
|
}
|
||||||
|
)
|
||||||
# Run is_valid() to trigger the validation
|
# Run is_valid() to trigger the validation
|
||||||
valid = form.is_valid()
|
valid = form.is_valid()
|
||||||
self.assertTrue(valid)
|
self.assertTrue(valid)
|
||||||
|
|
||||||
# Run the actual clean_username method
|
# Run the actual clean_username method
|
||||||
username = form.clean_username()
|
username = form.clean_username()
|
||||||
self.assertEqual('alamode', username)
|
self.assertEqual("alamode", username)
|
||||||
|
|
||||||
def test_clean_username_false(self):
|
def test_clean_username_false(self):
|
||||||
# Instantiate the form with the same username as self.user
|
# Instantiate the form with the same username as self.user
|
||||||
form = MyUserCreationForm({
|
form = MyUserCreationForm(
|
||||||
'username': self.user.username,
|
{
|
||||||
'password1': 'notalamodespassword',
|
"username": self.user.username,
|
||||||
'password2': 'notalamodespassword',
|
"password1": "notalamodespassword",
|
||||||
})
|
"password2": "notalamodespassword",
|
||||||
|
}
|
||||||
|
)
|
||||||
# Run is_valid() to trigger the validation, which is going to fail
|
# Run is_valid() to trigger the validation, which is going to fail
|
||||||
# because the username is already taken
|
# because the username is already taken
|
||||||
valid = form.is_valid()
|
valid = form.is_valid()
|
||||||
|
@ -37,4 +41,4 @@ class TestMyUserCreationForm(TestCase):
|
||||||
|
|
||||||
# The form.errors dict should contain a single error called 'username'
|
# The form.errors dict should contain a single error called 'username'
|
||||||
self.assertTrue(len(form.errors) == 1)
|
self.assertTrue(len(form.errors) == 1)
|
||||||
self.assertTrue('username' in form.errors)
|
self.assertTrue("username" in form.errors)
|
||||||
|
|
|
@ -9,11 +9,8 @@ class TestUser(TestCase):
|
||||||
def test__str__(self):
|
def test__str__(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.user.__str__(),
|
self.user.__str__(),
|
||||||
'testuser' # This is the default username for self.make_user()
|
"testuser", # This is the default username for self.make_user()
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_get_absolute_url(self):
|
def test_get_absolute_url(self):
|
||||||
self.assertEqual(
|
self.assertEqual(self.user.get_absolute_url(), "/users/testuser/")
|
||||||
self.user.get_absolute_url(),
|
|
||||||
'/users/testuser/'
|
|
||||||
)
|
|
||||||
|
|
|
@ -11,41 +11,34 @@ class TestUserURLs(TestCase):
|
||||||
|
|
||||||
def test_list_reverse(self):
|
def test_list_reverse(self):
|
||||||
"""users:list should reverse to /users/."""
|
"""users:list should reverse to /users/."""
|
||||||
self.assertEqual(reverse('users:list'), '/users/')
|
self.assertEqual(reverse("users:list"), "/users/")
|
||||||
|
|
||||||
def test_list_resolve(self):
|
def test_list_resolve(self):
|
||||||
"""/users/ should resolve to users:list."""
|
"""/users/ should resolve to users:list."""
|
||||||
self.assertEqual(resolve('/users/').view_name, 'users:list')
|
self.assertEqual(resolve("/users/").view_name, "users:list")
|
||||||
|
|
||||||
def test_redirect_reverse(self):
|
def test_redirect_reverse(self):
|
||||||
"""users:redirect should reverse to /users/~redirect/."""
|
"""users:redirect should reverse to /users/~redirect/."""
|
||||||
self.assertEqual(reverse('users:redirect'), '/users/~redirect/')
|
self.assertEqual(reverse("users:redirect"), "/users/~redirect/")
|
||||||
|
|
||||||
def test_redirect_resolve(self):
|
def test_redirect_resolve(self):
|
||||||
"""/users/~redirect/ should resolve to users:redirect."""
|
"""/users/~redirect/ should resolve to users:redirect."""
|
||||||
self.assertEqual(
|
self.assertEqual(resolve("/users/~redirect/").view_name, "users:redirect")
|
||||||
resolve('/users/~redirect/').view_name,
|
|
||||||
'users:redirect'
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_detail_reverse(self):
|
def test_detail_reverse(self):
|
||||||
"""users:detail should reverse to /users/testuser/."""
|
"""users:detail should reverse to /users/testuser/."""
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
reverse('users:detail', kwargs={'username': 'testuser'}),
|
reverse("users:detail", kwargs={"username": "testuser"}), "/users/testuser/"
|
||||||
'/users/testuser/'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_detail_resolve(self):
|
def test_detail_resolve(self):
|
||||||
"""/users/testuser/ should resolve to users:detail."""
|
"""/users/testuser/ should resolve to users:detail."""
|
||||||
self.assertEqual(resolve('/users/testuser/').view_name, 'users:detail')
|
self.assertEqual(resolve("/users/testuser/").view_name, "users:detail")
|
||||||
|
|
||||||
def test_update_reverse(self):
|
def test_update_reverse(self):
|
||||||
"""users:update should reverse to /users/~update/."""
|
"""users:update should reverse to /users/~update/."""
|
||||||
self.assertEqual(reverse('users:update'), '/users/~update/')
|
self.assertEqual(reverse("users:update"), "/users/~update/")
|
||||||
|
|
||||||
def test_update_resolve(self):
|
def test_update_resolve(self):
|
||||||
"""/users/~update/ should resolve to users:update."""
|
"""/users/~update/ should resolve to users:update."""
|
||||||
self.assertEqual(
|
self.assertEqual(resolve("/users/~update/").view_name, "users:update")
|
||||||
resolve('/users/~update/').view_name,
|
|
||||||
'users:update'
|
|
||||||
)
|
|
||||||
|
|
|
@ -2,10 +2,7 @@ from django.test import RequestFactory
|
||||||
|
|
||||||
from test_plus.test import TestCase
|
from test_plus.test import TestCase
|
||||||
|
|
||||||
from ..views import (
|
from ..views import UserRedirectView, UserUpdateView
|
||||||
UserRedirectView,
|
|
||||||
UserUpdateView
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseUserTestCase(TestCase):
|
class BaseUserTestCase(TestCase):
|
||||||
|
@ -21,17 +18,14 @@ class TestUserRedirectView(BaseUserTestCase):
|
||||||
# Instantiate the view directly. Never do this outside a test!
|
# Instantiate the view directly. Never do this outside a test!
|
||||||
view = UserRedirectView()
|
view = UserRedirectView()
|
||||||
# Generate a fake request
|
# Generate a fake request
|
||||||
request = self.factory.get('/fake-url')
|
request = self.factory.get("/fake-url")
|
||||||
# Attach the user to the request
|
# Attach the user to the request
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
# Attach the request to the view
|
# Attach the request to the view
|
||||||
view.request = request
|
view.request = request
|
||||||
# Expect: '/users/testuser/', as that is the default username for
|
# Expect: '/users/testuser/', as that is the default username for
|
||||||
# self.make_user()
|
# self.make_user()
|
||||||
self.assertEqual(
|
self.assertEqual(view.get_redirect_url(), "/users/testuser/")
|
||||||
view.get_redirect_url(),
|
|
||||||
'/users/testuser/'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestUserUpdateView(BaseUserTestCase):
|
class TestUserUpdateView(BaseUserTestCase):
|
||||||
|
@ -42,7 +36,7 @@ class TestUserUpdateView(BaseUserTestCase):
|
||||||
# Instantiate the view directly. Never do this outside a test!
|
# Instantiate the view directly. Never do this outside a test!
|
||||||
self.view = UserUpdateView()
|
self.view = UserUpdateView()
|
||||||
# Generate a fake request
|
# Generate a fake request
|
||||||
request = self.factory.get('/fake-url')
|
request = self.factory.get("/fake-url")
|
||||||
# Attach the user to the request
|
# Attach the user to the request
|
||||||
request.user = self.user
|
request.user = self.user
|
||||||
# Attach the request to the view
|
# Attach the request to the view
|
||||||
|
@ -51,14 +45,8 @@ class TestUserUpdateView(BaseUserTestCase):
|
||||||
def test_get_success_url(self):
|
def test_get_success_url(self):
|
||||||
# Expect: '/users/testuser/', as that is the default username for
|
# Expect: '/users/testuser/', as that is the default username for
|
||||||
# self.make_user()
|
# self.make_user()
|
||||||
self.assertEqual(
|
self.assertEqual(self.view.get_success_url(), "/users/testuser/")
|
||||||
self.view.get_success_url(),
|
|
||||||
'/users/testuser/'
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_get_object(self):
|
def test_get_object(self):
|
||||||
# Expect: self.user, as that is the request's user object
|
# Expect: self.user, as that is the request's user object
|
||||||
self.assertEqual(
|
self.assertEqual(self.view.get_object(), self.user)
|
||||||
self.view.get_object(),
|
|
||||||
self.user
|
|
||||||
)
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user