Merge branch 'master' into traefik-nonroot

# Conflicts:
#	{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile
#	{{cookiecutter.project_slug}}/compose/production/traefik/traefik.toml
This commit is contained in:
Bruno Alla 2020-01-11 15:42:31 +00:00
commit 0702e3cad2
37 changed files with 329 additions and 134 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: danielroygreenfeld
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.patreon.com/browniebroke']

View File

@ -5,7 +5,7 @@ services:
language: python language: python
python: 3.6 python: 3.7
before_install: before_install:
- docker-compose -v - docker-compose -v
@ -14,7 +14,7 @@ before_install:
matrix: matrix:
include: include:
- name: Test results - name: Test results
script: tox -e py36 script: tox -e py37
- name: Run flake8 on result - name: Run flake8 on result
script: tox -e flake8 script: tox -e flake8
- name: Run black on result - name: Run black on result

View File

@ -2,6 +2,25 @@
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/).
## [2020-01-11]
### Changed
- Speed up & reduce size for production Django image
- Bumped runtime version for Heroku
- Added Debian 10 (Buster) OS dependencies
- Update Traefik to v2
## [2019-10-06]
### Changed
- Default Python version is now 3.7 (@nicolas471)
## [2019-10-04]
### Fixed
- Fix static files handling on GCP (@caioariede)
## [2019-10-03]
### Fixed
- Fix incompatible combination between Whitenoise and no cloud provider (@caioariede)
## [2019-07-09] ## [2019-07-09]
### Fixed ### Fixed
- Always use test settings in pytest (@danihodovic) - Always use test settings in pytest (@danihodovic)

View File

@ -39,9 +39,9 @@ To run all tests using various versions of python in virtualenvs defined in tox.
It is possible to test with a specific version of python. To do this, the command It is possible to test with a specific version of python. To do this, the command
is:: is::
$ tox -e py36 $ tox -e py37
This will run py.test with the python3.6 interpreter, for example. This will run py.test with the python3.7 interpreter, for example.
To run a particular test with tox for against your current Python version:: To run a particular test with tox for against your current Python version::

View File

@ -73,7 +73,9 @@ Listed in alphabetical order.
Bo Lopker `@blopker`_ Bo Lopker `@blopker`_
Bouke Haarsma Bouke Haarsma
Brent Payne `@brentpayne`_ @brentpayne Brent Payne `@brentpayne`_ @brentpayne
Bruce Olivier `@bolivierjr`_
Burhan Khalid            `@burhan`_                   @burhan Burhan Khalid            `@burhan`_                   @burhan
Caio Ariede `@caioariede`_ @caioariede
Carl Johnson `@carlmjohnson`_ @carlmjohnson Carl Johnson `@carlmjohnson`_ @carlmjohnson
Catherine Devlin `@catherinedevlin`_ Catherine Devlin `@catherinedevlin`_
Cédric Gaspoz `@cgaspoz`_ Cédric Gaspoz `@cgaspoz`_
@ -100,6 +102,7 @@ Listed in alphabetical order.
Demetris Stavrou `@demestav`_ Demetris Stavrou `@demestav`_
Denis Bobrov `@delneg`_ Denis Bobrov `@delneg`_
Denis Orehovsky `@apirobot`_ Denis Orehovsky `@apirobot`_
Denis Savran `@blaxpy`_
Diane Chen `@purplediane`_ @purplediane88 Diane Chen `@purplediane`_ @purplediane88
Dónal Adams `@epileptic-fish`_ Dónal Adams `@epileptic-fish`_
Dong Huynh `@trungdong`_ Dong Huynh `@trungdong`_
@ -111,13 +114,16 @@ Listed in alphabetical order.
Florian Idelberger `@step21`_ @windrush Florian Idelberger `@step21`_ @windrush
Garry Cairns `@garry-cairns`_ Garry Cairns `@garry-cairns`_
Garry Polley `@garrypolley`_ Garry Polley `@garrypolley`_
Gilbishkosma `@Gilbishkosma`_
Hamish Durkin `@durkode`_ Hamish Durkin `@durkode`_
Hana Quadara `@hanaquadara`_ Hana Quadara `@hanaquadara`_
Harry Moreno `@morenoh149`_ @morenoh149
Harry Percival `@hjwp`_ Harry Percival `@hjwp`_
Hendrik Schneider `@hendrikschneider`_ Hendrik Schneider `@hendrikschneider`_
Henrique G. G. Pereira `@ikkebr`_ Henrique G. G. Pereira `@ikkebr`_
Ian Lee `@IanLee1521`_ Ian Lee `@IanLee1521`_
Irfan Ahmad `@erfaan`_ @erfaan Irfan Ahmad `@erfaan`_ @erfaan
Isaac12x `@Isaac12x`_
Jan Van Bruggen `@jvanbrug`_ Jan Van Bruggen `@jvanbrug`_
Jelmer Draaijer `@foarsitter`_ Jelmer Draaijer `@foarsitter`_
Jens Nilsson `@phiberjenz`_ Jens Nilsson `@phiberjenz`_
@ -136,6 +142,7 @@ Listed in alphabetical order.
Keyvan Mosharraf `@keyvanm`_ Keyvan Mosharraf `@keyvanm`_
Krzysztof Szumny `@noisy`_ Krzysztof Szumny `@noisy`_
Krzysztof Żuraw `@krzysztofzuraw`_ Krzysztof Żuraw `@krzysztofzuraw`_
Leo won `@leollon`_
Leo Zhou `@glasslion`_ Leo Zhou `@glasslion`_
Leonardo Jimenez `@xpostudio4`_ Leonardo Jimenez `@xpostudio4`_
Lin Xianyi `@iynaix`_ Lin Xianyi `@iynaix`_
@ -158,6 +165,7 @@ Listed in alphabetical order.
Michael Gecht `@mimischi`_ @_mischi Michael Gecht `@mimischi`_ @_mischi
Min ho Kim `@minho42`_ Min ho Kim `@minho42`_
mozillazg `@mozillazg`_ mozillazg `@mozillazg`_
Nico Stefani `@nicolas471`_ @moby_dick91
Oleg Russkin `@rolep`_ Oleg Russkin `@rolep`_
Pablo `@oubiga`_ Pablo `@oubiga`_
Parbhat Puri `@parbhat`_ Parbhat Puri `@parbhat`_
@ -198,6 +206,7 @@ Listed in alphabetical order.
William Archinal `@archinal`_ William Archinal `@archinal`_
Xaver Y.R. Chen `@yrchen`_ @yrchen Xaver Y.R. Chen `@yrchen`_ @yrchen
Yaroslav Halchenko Yaroslav Halchenko
Yuchen Xie `@mapx`_
========================== ============================ ============== ========================== ============================ ==============
.. _@a7p: https://github.com/a7p .. _@a7p: https://github.com/a7p
@ -219,9 +228,11 @@ Listed in alphabetical order.
.. _@arruda: https://github.com/arruda .. _@arruda: https://github.com/arruda
.. _@bertdemiranda: https://github.com/bertdemiranda .. _@bertdemiranda: https://github.com/bertdemiranda
.. _@bittner: https://github.com/bittner .. _@bittner: https://github.com/bittner
.. _@blaxpy: https://github.com/blaxpy
.. _@bloodpet: https://github.com/bloodpet .. _@bloodpet: https://github.com/bloodpet
.. _@blopker: https://github.com/blopker .. _@blopker: https://github.com/blopker
.. _@bogdal: https://github.com/bogdal .. _@bogdal: https://github.com/bogdal
.. _@bolivierjr: https://github.com/bolivierjr
.. _@brentpayne: https://github.com/brentpayne .. _@brentpayne: https://github.com/brentpayne
.. _@btknu: https://github.com/btknu .. _@btknu: https://github.com/btknu
.. _@burhan: https://github.com/burhan .. _@burhan: https://github.com/burhan
@ -229,6 +240,7 @@ Listed in alphabetical order.
.. _@c-rhodes: https://github.com/c-rhodes .. _@c-rhodes: https://github.com/c-rhodes
.. _@caffodian: https://github.com/caffodian .. _@caffodian: https://github.com/caffodian
.. _@canonnervio: https://github.com/canonnervio .. _@canonnervio: https://github.com/canonnervio
.. _@caioariede: https://github.com/caioariede
.. _@carlmjohnson: https://github.com/carlmjohnson .. _@carlmjohnson: https://github.com/carlmjohnson
.. _@catherinedevlin: https://github.com/catherinedevlin .. _@catherinedevlin: https://github.com/catherinedevlin
.. _@ccurvey: https://github.com/ccurvey .. _@ccurvey: https://github.com/ccurvey
@ -263,6 +275,7 @@ Listed in alphabetical order.
.. _@foarsitter: https://github.com/foarsitter .. _@foarsitter: https://github.com/foarsitter
.. _@garry-cairns: https://github.com/garry-cairns .. _@garry-cairns: https://github.com/garry-cairns
.. _@garrypolley: https://github.com/garrypolley .. _@garrypolley: https://github.com/garrypolley
.. _@Gilbishkosma: https://github.com/Gilbishkosma
.. _@glasslion: https://github.com/glasslion .. _@glasslion: https://github.com/glasslion
.. _@goldhand: https://github.com/goldhand .. _@goldhand: https://github.com/goldhand
.. _@hackebrot: https://github.com/hackebrot .. _@hackebrot: https://github.com/hackebrot
@ -272,6 +285,7 @@ Listed in alphabetical order.
.. _@hjwp: https://github.com/hjwp .. _@hjwp: https://github.com/hjwp
.. _@IanLee1521: https://github.com/IanLee1521 .. _@IanLee1521: https://github.com/IanLee1521
.. _@ikkebr: https://github.com/ikkebr .. _@ikkebr: https://github.com/ikkebr
.. _@Isaac12x: https://github.com/Isaac12x
.. _@iynaix: https://github.com/iynaix .. _@iynaix: https://github.com/iynaix
.. _@jangeador: https://github.com/jangeador .. _@jangeador: https://github.com/jangeador
.. _@jazztpt: https://github.com/jazztpt .. _@jazztpt: https://github.com/jazztpt
@ -287,7 +301,9 @@ Listed in alphabetical order.
.. _@keyvanm: https://github.com/keyvanm .. _@keyvanm: https://github.com/keyvanm
.. _@knitatoms: https://github.com/knitatoms .. _@knitatoms: https://github.com/knitatoms
.. _@krzysztofzuraw: https://github.com/krzysztofzuraw .. _@krzysztofzuraw: https://github.com/krzysztofzuraw
.. _@leollon: https://github.com/leollon
.. _@MathijsHoogland: https://github.com/MathijsHoogland .. _@MathijsHoogland: https://github.com/MathijsHoogland
.. _@mapx: https://github.com/mapx
.. _@mattayes: https://github.com/mattayes .. _@mattayes: https://github.com/mattayes
.. _@menzenski: https://github.com/menzenski .. _@menzenski: https://github.com/menzenski
.. _@mfwarren: https://github.com/mfwarren .. _@mfwarren: https://github.com/mfwarren
@ -296,11 +312,13 @@ Listed in alphabetical order.
.. _@minho42: https://github.com/minho42 .. _@minho42: https://github.com/minho42
.. _@mjsisley: https://github.com/mjsisley .. _@mjsisley: https://github.com/mjsisley
.. _@mknapper1: https://github.com/mknapper1 .. _@mknapper1: https://github.com/mknapper1
.. _@morenoh149: https://github.com/morenoh149
.. _@mostaszewski: https://github.com/mostaszewski .. _@mostaszewski: https://github.com/mostaszewski
.. _@mozillazg: https://github.com/mozillazg .. _@mozillazg: https://github.com/mozillazg
.. _@mrcoles: https://github.com/mrcoles .. _@mrcoles: https://github.com/mrcoles
.. _@msaizar: https://github.com/msaizar .. _@msaizar: https://github.com/msaizar
.. _@myilmaz: https://github.com/myilmaz .. _@myilmaz: https://github.com/myilmaz
.. _@nicolas471: https://github.com/nicolas471
.. _@noisy: https://github.com/noisy .. _@noisy: https://github.com/noisy
.. _@originell: https://github.com/originell .. _@originell: https://github.com/originell
.. _@oubiga: https://github.com/oubiga .. _@oubiga: https://github.com/oubiga

View File

@ -37,7 +37,7 @@ Features
--------- ---------
* For Django 2.2 * For Django 2.2
* Works with Python 3.6 * Works with Python 3.7
* Renders Django projects with 100% starting test coverage * Renders Django projects with 100% starting test coverage
* Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available) * Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available)
* 12-Factor_ based settings via django-environ_ * 12-Factor_ based settings via django-environ_
@ -53,6 +53,7 @@ Features
* Instructions for deploying to PythonAnywhere_ * Instructions for deploying to PythonAnywhere_
* Run tests with unittest or pytest * Run tests with unittest or pytest
* Customizable PostgreSQL version * Customizable PostgreSQL version
* Default integration with pre-commit_ for identifying simple issues before submission to code review
.. _`maintained Foundation fork`: https://github.com/Parbhat/cookiecutter-django-foundation .. _`maintained Foundation fork`: https://github.com/Parbhat/cookiecutter-django-foundation
@ -84,6 +85,7 @@ Optional Integrations
.. _PythonAnywhere: https://www.pythonanywhere.com/ .. _PythonAnywhere: https://www.pythonanywhere.com/
.. _Traefik: https://traefik.io/ .. _Traefik: https://traefik.io/
.. _LetsEncrypt: https://letsencrypt.org/ .. _LetsEncrypt: https://letsencrypt.org/
.. _pre-commit: https://github.com/pre-commit/pre-commit
Constraints Constraints
----------- -----------
@ -106,7 +108,7 @@ Projects that provide financial support to the maintainers:
Two Scoops of Django 1.11 Two Scoops of Django 1.11
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
.. image:: https://cdn.shopify.com/s/files/1/0304/6901/products/tsd-111-alpha_medium.jpg?v=1499531513 .. image:: https://cdn.shopify.com/s/files/1/0304/6901/products/2017-06-29-tsd11-sticker-02.png
: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

View File

@ -35,7 +35,7 @@ Make sure your project is fully committed and pushed up to Bitbucket or Github o
git clone <my-repo-url> # you can also use hg git clone <my-repo-url> # you can also use hg
cd my-project-name cd my-project-name
mkvirtualenv --python=/usr/bin/python3.6 my-project-name mkvirtualenv --python=/usr/bin/python3.7 my-project-name
pip install -r requirements/production.txt # may take a few minutes pip install -r requirements/production.txt # may take a few minutes

View File

@ -152,6 +152,7 @@ If you are using ``supervisor``, you can use this file as a starting point::
Move it to ``/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 update
supervisorctl start {{cookiecutter.project_slug}} supervisorctl start {{cookiecutter.project_slug}}
For status check, run:: For status check, run::

View File

@ -9,7 +9,7 @@ Setting Up Development Environment
Make sure to have the following on your host: Make sure to have the following on your host:
* Python 3.6 * Python 3.7
* PostgreSQL_. * PostgreSQL_.
* Redis_, if using Celery * Redis_, if using Celery
@ -17,7 +17,7 @@ First things first.
#. Create a virtualenv: :: #. Create a virtualenv: ::
$ python3.6 -m venv <virtual env path> $ python3.7 -m venv <virtual env path>
#. Activate the virtualenv you have just created: :: #. Activate the virtualenv you have just created: ::
@ -26,6 +26,12 @@ First things first.
#. Install development requirements: :: #. Install development requirements: ::
$ pip install -r requirements/local.txt $ pip install -r requirements/local.txt
$ pre-commit install
.. note::
the `pre-commit` exists in the generated project as default.
for the details of `pre-commit`, follow the [site of pre-commit](https://pre-commit.com/).
#. Create a new PostgreSQL database using createdb_: :: #. Create a new PostgreSQL database using createdb_: ::

View File

@ -279,6 +279,10 @@ def remove_node_dockerfile():
shutil.rmtree(os.path.join("compose", "local", "node")) shutil.rmtree(os.path.join("compose", "local", "node"))
def remove_aws_dockerfile():
shutil.rmtree(os.path.join("compose", "production", "aws"))
def main(): def main():
debug = "{{ cookiecutter.debug }}".lower() == "y" debug = "{{ cookiecutter.debug }}".lower() == "y"
@ -302,6 +306,12 @@ def main():
else: else:
remove_docker_files() remove_docker_files()
if (
"{{ cookiecutter.use_docker }}".lower() == "y"
and "{{ cookiecutter.cloud_provider}}".lower() != "aws"
):
remove_aws_dockerfile()
if "{{ cookiecutter.use_heroku }}".lower() == "n": if "{{ cookiecutter.use_heroku }}".lower() == "n":
remove_heroku_files() remove_heroku_files()

View File

@ -35,7 +35,7 @@ if "{{ cookiecutter.use_docker }}".lower() == "n":
if python_major_version == 2: if python_major_version == 2:
print( print(
WARNING + "You're running cookiecutter under Python 2, but the generated " WARNING + "You're running cookiecutter under Python 2, but the generated "
"project requires Python 3.6+. Do you want to proceed (y/n)? " + TERMINATOR "project requires Python 3.7+. Do you want to proceed (y/n)? " + TERMINATOR
) )
yes_options, no_options = frozenset(["y"]), frozenset(["n"]) yes_options, no_options = frozenset(["y"]), frozenset(["n"])
while True: while True:
@ -59,3 +59,12 @@ if "{{ cookiecutter.use_docker }}".lower() == "n":
) )
+ TERMINATOR + TERMINATOR
) )
if (
"{{ cookiecutter.use_whitenoise }}".lower() == "n"
and "{{ cookiecutter.cloud_provider }}" == "None"
):
print(
"You should either use Whitenoise or select a Cloud Provider to serve static files"
)
sys.exit(1)

View File

@ -1,17 +1,17 @@
cookiecutter==1.6.0 cookiecutter==1.7.0
sh==1.12.14 sh==1.12.14
binaryornot==0.4.4 binaryornot==0.4.4
# Code quality # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
black==19.3b0 black==19.10b0
flake8==3.7.8 flake8==3.7.9
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
tox==3.14.0 tox==3.14.3
pytest==5.1.2 pytest==5.3.2
pytest_cases==1.11.2 pytest_cases==1.12.0
pytest-cookies==0.4.0 pytest-cookies==0.4.0
pytest-xdist==1.29.0 pytest-xdist==1.31.0
pyyaml==5.1.2 pyyaml==5.3

View File

@ -40,7 +40,7 @@ setup(
"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.7",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development", "Topic :: Software Development",
], ],

View File

@ -11,9 +11,6 @@ from binaryornot.check import is_binary
PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}" PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}"
RE_OBJ = re.compile(PATTERN) RE_OBJ = re.compile(PATTERN)
YN_CHOICES = ["y", "n"]
CLOUD_CHOICES = ["AWS", "GCE", "None"]
@pytest.fixture @pytest.fixture
def context(): def context():
@ -30,14 +27,24 @@ def context():
@pytest_fixture_plus @pytest_fixture_plus
@pytest.mark.parametrize("windows", YN_CHOICES, ids=lambda yn: f"win:{yn}") @pytest.mark.parametrize("windows", ["y", "n"], ids=lambda yn: f"win:{yn}")
@pytest.mark.parametrize("use_docker", YN_CHOICES, ids=lambda yn: f"docker:{yn}") @pytest.mark.parametrize("use_docker", ["y", "n"], ids=lambda yn: f"docker:{yn}")
@pytest.mark.parametrize("use_celery", YN_CHOICES, ids=lambda yn: f"celery:{yn}") @pytest.mark.parametrize("use_celery", ["y", "n"], ids=lambda yn: f"celery:{yn}")
@pytest.mark.parametrize("use_mailhog", YN_CHOICES, ids=lambda yn: f"mailhog:{yn}") @pytest.mark.parametrize("use_mailhog", ["y", "n"], ids=lambda yn: f"mailhog:{yn}")
@pytest.mark.parametrize("use_sentry", YN_CHOICES, ids=lambda yn: f"sentry:{yn}") @pytest.mark.parametrize("use_sentry", ["y", "n"], ids=lambda yn: f"sentry:{yn}")
@pytest.mark.parametrize("use_compressor", YN_CHOICES, ids=lambda yn: f"cmpr:{yn}") @pytest.mark.parametrize("use_compressor", ["y", "n"], ids=lambda yn: f"cmpr:{yn}")
@pytest.mark.parametrize("use_whitenoise", YN_CHOICES, ids=lambda yn: f"wnoise:{yn}") @pytest.mark.parametrize(
@pytest.mark.parametrize("cloud_provider", CLOUD_CHOICES, ids=lambda yn: f"cloud:{yn}") "use_whitenoise,cloud_provider",
[
("y", "AWS"),
("y", "GCP"),
("y", "None"),
("n", "AWS"),
("n", "GCP"),
# no whitenoise + co cloud provider is not supported
],
ids=lambda id: f"wnoise:{id[0]}-cloud:{id[1]}",
)
def context_combination( def context_combination(
windows, windows,
use_docker, use_docker,
@ -157,3 +164,12 @@ def test_invalid_slug(cookies, context, slug):
assert result.exit_code != 0 assert result.exit_code != 0
assert isinstance(result.exception, FailedHookException) assert isinstance(result.exception, FailedHookException)
def test_no_whitenoise_and_no_cloud_provider(cookies, context):
"""It should not generate project if neither whitenoise or cloud provider are set"""
context.update({"use_whitenoise": "n", "cloud_provider": "None"})
result = cookies.bake(extra_context=context)
assert result.exit_code != 0
assert isinstance(result.exception, FailedHookException)

View File

@ -1,6 +1,6 @@
[tox] [tox]
skipsdist = true skipsdist = true
envlist = py36,flake8,black,black-template envlist = py37,flake8,black,black-template
[testenv] [testenv]
deps = -rrequirements.txt deps = -rrequirements.txt

View File

@ -13,10 +13,16 @@ indent_style = space
indent_size = 4 indent_size = 4
[*.py] [*.py]
line_length=120 line_length = 120
known_first_party={{ cookiecutter.project_slug }} known_first_party = {{ cookiecutter.project_slug }}
multi_line_output=3 multi_line_output = 3
default_section=THIRDPARTY default_section = THIRDPARTY
recursive = true
skip = venv/
skip_glob = **/migrations/*.py
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
[*.{html,css,scss,json,yml}] [*.{html,css,scss,json,yml}]
indent_style = space indent_style = space

View File

@ -0,0 +1,19 @@
exclude: 'docs|node_modules|migrations|.git|.tox'
default_stages: [commit]
fail_fast: true
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: master
hooks:
- id: trailing-whitespace
files: (^|/)a/.+\.(py|html|sh|css|js)$
- repo: local
hooks:
- id: flake8
name: flake8
entry: flake8
language: python
types: [python]

View File

@ -10,7 +10,7 @@ before_install:
- sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm - sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm
language: python language: python
python: python:
- "3.6" - "3.7"
install: install:
- pip install -r requirements/local.txt - pip install -r requirements/local.txt
script: script:

View File

@ -1,4 +1,4 @@
FROM python:3.6-alpine FROM python:3.7-alpine
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1

View File

@ -9,7 +9,7 @@ RUN npm run build
# Python build stage # Python build stage
{%- endif %} {%- endif %}
FROM python:3.6-alpine FROM python:3.7-alpine
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
@ -57,13 +57,11 @@ RUN chmod +x /start-flower
{%- endif %} {%- endif %}
{%- if cookiecutter.js_task_runner == 'Gulp' %} {%- if cookiecutter.js_task_runner == 'Gulp' %}
COPY --from=client-builder /app /app COPY --from=client-builder --chown=django:django /app /app
{% else %} {% else %}
COPY . /app COPY --chown=django:django . /app
{%- endif %} {%- endif %}
RUN chown -R django /app
USER django USER django
WORKDIR /app WORKDIR /app

View File

@ -1,8 +1,8 @@
FROM traefik:alpine FROM traefik:v2.0
RUN mkdir -p /etc/traefik/acme RUN mkdir -p /etc/traefik/acme
RUN touch /etc/traefik/acme/acme.json RUN touch /etc/traefik/acme/acme.json
RUN chmod 600 /etc/traefik/acme/acme.json RUN chmod 600 /etc/traefik/acme/acme.json
COPY ./compose/production/traefik/traefik.toml /etc/traefik COPY ./compose/production/traefik/traefik.yml /etc/traefik
RUN addgroup -g 101 -S traefik RUN addgroup -g 101 -S traefik
RUN adduser -S -D -H -u 101 -s /sbin/nologin -G traefik -g traefik traefik RUN adduser -S -D -H -u 101 -s /sbin/nologin -G traefik -g traefik traefik

View File

@ -1,41 +0,0 @@
logLevel = "INFO"
defaultEntryPoints = ["http", "https"]
# Entrypoints, http and https
[entryPoints]
# http should be redirected to https
[entryPoints.http]
address = ":8080"
[entryPoints.http.redirect]
entryPoint = "https"
# https is the default
[entryPoints.https]
address = ":8443"
[entryPoints.https.tls]
# Enable ACME (Let's Encrypt): automatic SSL
[acme]
# Email address used for registration
email = "{{ cookiecutter.email }}"
storage = "/etc/traefik/acme/acme.json"
entryPoint = "https"
onDemand = false
OnHostRule = true
# Use a HTTP-01 acme challenge rather than TLS-SNI-01 challenge
[acme.httpChallenge]
entryPoint = "http"
[file]
[backends]
[backends.django]
[backends.django.servers.server1]
url = "http://django:5000"
[frontends]
[frontends.django]
backend = "django"
passHostHeader = true
[frontends.django.headers]
HostsProxyHeaders = ['X-CSRFToken']
[frontends.django.routes.dr1]
rule = "Host:{{ cookiecutter.domain_name }}"

View File

@ -0,0 +1,67 @@
log:
level: INFO
entryPoints:
web:
# http
address: ":80"
web-secure:
# https
address: ":443"
certificatesResolvers:
letsencrypt:
# https://docs.traefik.io/master/https/acme/#lets-encrypt
acme:
email: "{{ cookiecutter.email }}"
storage: /etc/traefik/acme/acme.json
# https://docs.traefik.io/master/https/acme/#httpchallenge
httpChallenge:
entryPoint: web
http:
routers:
web-router:
rule: "Host(`{{ cookiecutter.domain_name }}`)"
entryPoints:
- web
middlewares:
- redirect
- csrf
service: django
web-secure-router:
rule: "Host(`{{ cookiecutter.domain_name }}`)"
entryPoints:
- web-secure
middlewares:
- csrf
service: django
tls:
# https://docs.traefik.io/master/routing/routers/#certresolver
certResolver: letsencrypt
middlewares:
redirect:
# https://docs.traefik.io/master/middlewares/redirectscheme/
redirectScheme:
scheme: https
permanent: true
csrf:
# https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders
# https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
headers:
hostsProxyHeaders: ['X-CSRFToken']
services:
django:
loadBalancer:
servers:
- url: http://django:5000
providers:
# https://docs.traefik.io/master/providers/file/
file:
filename: /etc/traefik/traefik.yml
watch: true

View File

@ -131,6 +131,9 @@ AUTH_PASSWORD_VALIDATORS = [
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware # https://docs.djangoproject.com/en/dev/ref/settings/#middleware
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
{%- if cookiecutter.use_whitenoise == 'y' %}
"whitenoise.middleware.WhiteNoiseMiddleware",
{%- endif %}
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware", "django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
@ -187,6 +190,7 @@ TEMPLATES = [
"django.template.context_processors.static", "django.template.context_processors.static",
"django.template.context_processors.tz", "django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"{{ cookiecutter.project_slug }}.utils.context_processors.settings_context",
], ],
}, },
} }

View File

@ -42,6 +42,14 @@ EMAIL_BACKEND = env(
) )
{%- endif %} {%- endif %}
{%- if cookiecutter.use_whitenoise == 'y' %}
# WhiteNoise
# ------------------------------------------------------------------------------
# http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development
INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405
{% endif %}
# django-debug-toolbar # django-debug-toolbar
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites

View File

@ -92,7 +92,6 @@ AWS_DEFAULT_ACL = None
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None)
{% elif cookiecutter.cloud_provider == 'GCP' %} {% elif cookiecutter.cloud_provider == 'GCP' %}
DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME") GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME")
GS_DEFAULT_ACL = "publicRead" GS_DEFAULT_ACL = "publicRead"
{% endif -%} {% endif -%}
@ -105,8 +104,11 @@ GS_DEFAULT_ACL = "publicRead"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
{% elif cookiecutter.cloud_provider == 'AWS' -%} {% elif cookiecutter.cloud_provider == 'AWS' -%}
STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage" STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage"
COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy"
STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/" STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/"
{% elif cookiecutter.cloud_provider == 'GCP' -%} {% elif cookiecutter.cloud_provider == 'GCP' -%}
STATICFILES_STORAGE = "config.settings.production.StaticRootGoogleCloudStorage"
COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy"
STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/" STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/"
{% endif -%} {% endif -%}
@ -132,8 +134,21 @@ class MediaRootS3Boto3Storage(S3Boto3Storage):
DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage" DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage"
MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/" MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/"
{%- elif cookiecutter.cloud_provider == 'GCP' %} {%- elif cookiecutter.cloud_provider == 'GCP' %}
from storages.backends.gcloud import GoogleCloudStorage # noqa E402
class StaticRootGoogleCloudStorage(GoogleCloudStorage):
location = "static"
default_acl = "publicRead"
class MediaRootGoogleCloudStorage(GoogleCloudStorage):
location = "media"
file_overwrite = False
DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootGoogleCloudStorage"
MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
MEDIA_ROOT = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
{%- endif %} {%- endif %}
# TEMPLATES # TEMPLATES
@ -179,14 +194,7 @@ ANYMAIL = {
"MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"), "MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"),
} }
{% if cookiecutter.use_whitenoise == 'y' -%} {% if cookiecutter.use_compressor == 'y' -%}
# WhiteNoise
# ------------------------------------------------------------------------------
# http://whitenoise.evans.io/en/latest/django.html#enable-whitenoise
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware") # noqa F405
{% endif %}
{%- if cookiecutter.use_compressor == 'y' -%}
# django-compressor # django-compressor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED

View File

@ -65,6 +65,8 @@ services:
command: /start-flower command: /start-flower
{%- endif %} {%- endif %}
{% if cookiecutter.cloud_provider == 'AWS' %}
awscli: awscli:
build: build:
context: . context: .
@ -73,3 +75,4 @@ services:
- ./.envs/.production/.django - ./.envs/.production/.django
volumes: volumes:
- production_postgres_data_backups:/backups - production_postgres_data_backups:/backups
{%- endif %}

View File

@ -1,5 +1,5 @@
[pytest] [pytest]
addopts = --ds=config.settings.test addopts = --ds=config.settings.test --reuse-db
python_files = tests.py test_*.py python_files = tests.py test_*.py
{%- if cookiecutter.js_task_runner != 'None' %} {%- if cookiecutter.js_task_runner != 'None' %}
norecursedirs = node_modules norecursedirs = node_modules

View File

@ -1,16 +1,16 @@
pytz==2019.2 # https://github.com/stub42/pytz pytz==2019.3 # https://github.com/stub42/pytz
python-slugify==3.0.3 # https://github.com/un33k/python-slugify python-slugify==4.0.0 # https://github.com/un33k/python-slugify
Pillow==6.1.0 # https://github.com/python-pillow/Pillow Pillow==7.0.0 # https://github.com/python-pillow/Pillow
{%- if cookiecutter.use_compressor == "y" %} {%- if cookiecutter.use_compressor == "y" %}
rcssmin==1.0.6{% if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin rcssmin==1.0.6{% if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin
{%- endif %} {%- endif %}
argon2-cffi==19.1.0 # https://github.com/hynek/argon2_cffi argon2-cffi==19.2.0 # https://github.com/hynek/argon2_cffi
{%- if cookiecutter.use_whitenoise == 'y' %} {%- if cookiecutter.use_whitenoise == 'y' %}
whitenoise==4.1.3 # https://github.com/evansd/whitenoise whitenoise==5.0.1 # https://github.com/evansd/whitenoise
{%- endif %} {%- endif %}
redis==3.3.8 # https://github.com/antirez/redis redis==3.3.11 # https://github.com/antirez/redis
{%- if cookiecutter.use_celery == "y" %} {%- if cookiecutter.use_celery == "y" %}
celery==4.3.0 # pyup: < 5.0 # https://github.com/celery/celery celery==4.4.0 # pyup: < 5.0 # https://github.com/celery/celery
django-celery-beat==1.5.0 # https://github.com/celery/django-celery-beat django-celery-beat==1.5.0 # https://github.com/celery/django-celery-beat
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
flower==0.9.3 # https://github.com/mher/flower flower==0.9.3 # https://github.com/mher/flower
@ -19,16 +19,16 @@ flower==0.9.3 # https://github.com/mher/flower
# Django # Django
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
django==2.2.5 # pyup: < 3.0 # https://www.djangoproject.com/ django==2.2.9 # pyup: < 3.0 # https://www.djangoproject.com/
django-environ==0.4.5 # https://github.com/joke2k/django-environ django-environ==0.4.5 # https://github.com/joke2k/django-environ
django-model-utils==3.2.0 # https://github.com/jazzband/django-model-utils django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils
django-allauth==0.40.0 # https://github.com/pennersr/django-allauth django-allauth==0.41.0 # https://github.com/pennersr/django-allauth
django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms django-crispy-forms==1.8.1 # https://github.com/django-crispy-forms/django-crispy-forms
{%- if cookiecutter.use_compressor == "y" %} {%- if cookiecutter.use_compressor == "y" %}
django-compressor==2.3 # https://github.com/django-compressor/django-compressor django-compressor==2.4 # https://github.com/django-compressor/django-compressor
{%- endif %} {%- endif %}
django-redis==4.10.0 # https://github.com/niwinz/django-redis django-redis==4.11.0 # https://github.com/niwinz/django-redis
# Django REST Framework # Django REST Framework
djangorestframework==3.10.3 # https://github.com/encode/django-rest-framework djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework
coreapi==2.3.3 # https://github.com/core-api/python-client coreapi==2.3.3 # https://github.com/core-api/python-client

View File

@ -1,35 +1,36 @@
-r ./base.txt -r ./base.txt
Werkzeug==0.14.1 # pyup: < 0.15 # https://github.com/pallets/werkzeug Werkzeug==0.16.0 # https://github.com/pallets/werkzeug
ipdb==0.12.2 # https://github.com/gotcha/ipdb ipdb==0.12.3 # https://github.com/gotcha/ipdb
Sphinx==2.2.0 # https://github.com/sphinx-doc/sphinx Sphinx==2.3.1 # https://github.com/sphinx-doc/sphinx
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- else %} {%- else %}
psycopg2-binary==2.8.3 # https://github.com/psycopg/psycopg2 psycopg2-binary==2.8.4 # https://github.com/psycopg/psycopg2
{%- endif %} {%- endif %}
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
mypy==0.720 # https://github.com/python/mypy mypy==0.761 # https://github.com/python/mypy
pytest==5.1.2 # https://github.com/pytest-dev/pytest pytest==5.3.1 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar
# Code quality # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
flake8==3.7.8 # https://github.com/PyCQA/flake8 flake8==3.7.9 # https://github.com/PyCQA/flake8
coverage==4.5.4 # https://github.com/nedbat/coveragepy coverage==5.0.2 # https://github.com/nedbat/coveragepy
black==19.3b0 # https://github.com/ambv/black black==19.10b0 # https://github.com/ambv/black
pylint-django==2.0.11 # https://github.com/PyCQA/pylint-django pylint-django==2.0.13 # https://github.com/PyCQA/pylint-django
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery
{%- endif %} {%- endif %}
pre-commit==1.21.0 # https://github.com/pre-commit/pre-commit
# Django # Django
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy
django-debug-toolbar==2.0 # https://github.com/jazzband/django-debug-toolbar django-debug-toolbar==2.1 # https://github.com/jazzband/django-debug-toolbar
django-extensions==2.2.1 # https://github.com/django-extensions/django-extensions django-extensions==2.2.5 # https://github.com/django-extensions/django-extensions
django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin
pytest-django==3.5.1 # https://github.com/pytest-dev/pytest-django pytest-django==3.7.0 # https://github.com/pytest-dev/pytest-django

View File

@ -2,20 +2,20 @@
-r ./base.txt -r ./base.txt
gunicorn==19.9.0 # https://github.com/benoitc/gunicorn gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- if cookiecutter.use_whitenoise == 'n' %} {%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==1.0.0 # https://github.com/antonagestam/collectfast Collectfast==1.3.1 # https://github.com/antonagestam/collectfast
{%- endif %} {%- endif %}
{%- if cookiecutter.use_sentry == "y" %} {%- if cookiecutter.use_sentry == "y" %}
sentry-sdk==0.11.2 # https://github.com/getsentry/sentry-python sentry-sdk==0.14.0 # https://github.com/getsentry/sentry-python
{%- endif %} {%- endif %}
# Django # Django
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
{%- if cookiecutter.cloud_provider == 'AWS' %} {%- if cookiecutter.cloud_provider == 'AWS' %}
django-storages[boto3]==1.7.1 # https://github.com/jschneier/django-storages django-storages[boto3]==1.8 # https://github.com/jschneier/django-storages
{%- elif cookiecutter.cloud_provider == 'GCP' %} {%- elif cookiecutter.cloud_provider == 'GCP' %}
django-storages[google]==1.7.1 # https://github.com/jschneier/django-storages django-storages[google]==1.8 # https://github.com/jschneier/django-storages
{%- endif %} {%- endif %}
django-anymail[mailgun]==6.1.0 # https://github.com/anymail/django-anymail django-anymail[mailgun]==7.0.0 # https://github.com/anymail/django-anymail

View File

@ -1 +1 @@
python-3.6.8 python-3.7.6

View File

@ -7,15 +7,16 @@ max-line-length = 120
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
[mypy] [mypy]
python_version = 3.6 python_version = 3.7
check_untyped_defs = True check_untyped_defs = True
ignore_errors = False
ignore_missing_imports = True ignore_missing_imports = True
strict_optional = True
warn_unused_ignores = True warn_unused_ignores = True
warn_redundant_casts = True warn_redundant_casts = True
warn_unused_configs = True warn_unused_configs = True
[mypy.plugins.django-stubs]
django_settings_module = config.settings.test
[mypy-*.migrations.*] [mypy-*.migrations.*]
# Django migrations should not produce any errors: # Django migrations should not produce any errors:
ignore_errors = True ignore_errors = True

View File

@ -0,0 +1,23 @@
##basic build dependencies of various Django apps for Debian Jessie 10.x
#build-essential metapackage install: make, gcc, g++,
build-essential
#required to translate
gettext
python3-dev
##shared dependencies of:
##Pillow, pylibmc
zlib1g-dev
##Postgresql and psycopg2 dependencies
libpq-dev
##Pillow dependencies
libtiff5-dev
libjpeg62-turbo-dev
libfreetype6-dev
liblcms2-dev
libwebp-dev
##django-extensions
libgraphviz-dev

View File

@ -10,7 +10,7 @@
{{ form|crispy }} {{ form|crispy }}
<div class="control-group"> <div class="control-group">
<div class="controls"> <div class="controls">
<button type="submit" class="btn">Update</button> <button type="submit" class="btn btn-primary">Update</button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -0,0 +1,5 @@
from django.conf import settings
def settings_context(_request):
return {"settings": settings}