Merge commit '416edce1f9d5d3b73ae40efeb440c70a8d053d0a'

This commit is contained in:
Trung Dong Huynh 2019-06-03 16:44:39 +01:00
commit ec48a217dc
44 changed files with 493 additions and 182 deletions

View File

@ -1,4 +1,4 @@
sudo: required
dist: xenial
services:
- docker
@ -13,10 +13,14 @@ before_install:
matrix:
include:
- name: Tox Test
- name: Test results
script: tox -e py36
- name: Black template
- name: Run flake8 on result
script: tox -e flake8
- name: Run black on result
script: tox -e black
- name: Black template
script: tox -e black-template
- name: Basic Docker
script: sh tests/test_docker.sh
- name: Docker with Celery

View File

@ -2,6 +2,196 @@
All enhancements and patches to Cookiecutter Django will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## [2019-05-27]
### Changed
- Made cloud provider optional (@tanoabeleyra)
- Updated to Django 2.2.1 (@browniebroke)
### Fixed
- Celery worker-related setting names (@browniebroke)
## [2019-05-18]
### Removed
- Remove the user list view (@browniebroke)
### Fixed
- Static storage default ACL (@browniebroke)
## [2019-05-17]
### Fixed
- Added `LocaleMiddleware` to the list of middlewares (@tanoabeleyra)
- Added `LOCALE_PATH` to settings (@tanoabeleyra)
## [2019-05-16]
### Changed
- Users app to have a translated verbose name (@tanoabeleyra)
- Logging configuration for local (@browniebroke)
## [2019-05-08]
### Changed
- Upgraded to Django 2.1 (@browniebroke)
## [2019-04-07]
### Added
- Support for Google Cloud Storage (@ahhda)
## [2019-04-03]
### Added
- Command to backup Db to AWS S3 (@foarsitter)
## [2019-03-25]
### Added
- Node image to run Gulp with Docker (@browniebroke)
## [2019-03-19]
### Changed
- Replaced Caddy with Traefik (@demestav)
## [2019-03-11]
### Changed
- Sentry integration from Raven to Sentry-SDK (@gfabricio)
- Made Redis config conditional on Celery locally (@demestav)
## [2019-03-11]
### Added
- Automatic migrations on Heroku (@yunti)
## [2019-03-06]
### Fixed
- Missing script tag in Travis config (@btknu)
## [2019-03-02]
### Changed
- Celery eager setting in local setting with Docker (@keithjeb)
## [2019-03-01]
### Updated
- All NPM dependencies (@takkaria)
## [2018-11-13]
### Changed
- Security settings in Dev (@carlmjohnson)
## [2018-11-20]
### Fixed
- Passing the CSRF header from the reverse proxy to Django server for DRF (@hpbruna)
## [2018-11-12]
### Fixed
- Initialisation of Celery app (@glasslion)
## [2018-10-24]
### Fixed
- Persisting of iPython history between sessions (@davitovmasyan)
### Added
- Postgres 10.5 option (@jleclanche)
## [2018-09-18]
### Added
- Included `mypy` in dependencies and run it in tests (@apirobot)
## [2018-09-18]
### Fixed
- Avoid `$` in environment variables to workaround a bug from django-environ (@browniebroke)
## [2018-09-16]
### Fixed
- Bug in ordering of Middleware for production config (@ChrisPappalardo)
## [2018-09-12]
### Fixed
- URLs for Static and Media for S3 buckets in regions other than N. Virginia (@umrashrf)
## [2018-09-09]
### Changed
- Name of static and media storage classes (@sfdye)
## [2018-09-01]
### Changed
- Make static and media storage fully-fledged classes (@erfaan)
## [2018-08-28]
### Fixed
- Running tests in docker test script (@apirobot)
## [2018-07-23]
### Changed
- Test commands to use pytest (@jcass77)
### Removed
- Some hacks leftovers from Bootstrap v4 beta in `project.js` (@hendrikschneider)
## [2018-07-12]
### Changed
- Upgraded to Bootstrap 4.1.1 (@mostaszewski)
## [2018-06-25]
### Added
- Flower integration with Docker (@webyneter)
## [2018-06-25]
### Changed
- Rewrite user app test to use a pytest style (@webyneter)
## [2018-06-21]
### Added
- Extend & update Celery config (@webyneter & @apirobot)
## [2018-05-25]
### Fixed
- Build issues due to incompatibility between libressl & openssl (@SassanoM)
## [2018-05-21]
### Changed
- Updated Caddy to 0.11 and pin its version (@webyneter)
## [2018-05-14]
### Changed
- Replace `awesome-slugify` by `python-slugify` (@hongquan)
- Migrate to Django 2.0+ URL style (@saschalalala)
## [2018-05-05]
### Fixed
- Postgres backup & restore commands (@webyneter)
## [2018-04-10]
### Changed
- Simplify configuration (@danidee10)
## [2018-04-08]
### Added
- Adopt Black code style (@pydanny)
## [2018-03-27]
### Fixed
- Simplified extra Celery config generated when opted out (@webyneter)
## [2018-03-21]
### Removed
- Remove Opbeat support (@sfdye)
## [2018-03-16]
### Fixed
- Install `psycopg2-binary` when using Docker locally (@browniebroke)
## [2018-03-14]
### Fixed
- Fixed and improved Postgres backup & restore scripts (@webyneter)
## [2018-03-10]
### Changed
- Simplify Mailgun setting (@browniebroke)
## [2018-03-06]
### Changed
- Convert string formatting to f-strings (@sfdye)
## [2018-03-01]
### Changed
- Celery to use JSON serialization by default (@adammsteele)
- Use Docker version from Travis to run tests (@browniebroke)
## [2018-02-16]
### Changed
- Upgraded to Django 2.0 (@epicwhale)

View File

@ -7,9 +7,9 @@ Core Developers
These contributors have commit flags for the repository,
and are able to accept and merge pull requests.
=========================== ================ ===========
=========================== ================= ===========
Name Github Twitter
=========================== ================ ===========
=========================== ================= ===========
Daniel Roy Greenfeld `@pydanny`_ @pydanny
Audrey Roy Greenfeld* `@audreyr`_ @audreyr
Fábio C. Barrionuevo da Luz `@luzfcb`_ @luzfcb
@ -19,7 +19,7 @@ Burhan Khalid `@burhan`_ @burhan
Nikita Shupeyko `@webyneter`_ @webyneter
Bruno Alla               `@browniebroke`_ @_BrunoAlla
Wan Liuyang `@sfdye`_ @sfdye
=========================== ================ ===========
=========================== ================= ===========
*Audrey is also the creator of Cookiecutter. Audrey and
Daniel are on the Cookiecutter core team.*
@ -64,6 +64,7 @@ Listed in alphabetical order.
Areski Belaid `@areski`_
Ashley Camba
Barclay Gauld `@yunti`_
Bartek `@btknu`_
Ben Warren `@bwarren2`_
Ben Lopatin
Benjamin Abel
@ -71,7 +72,6 @@ Listed in alphabetical order.
Bo Lopker `@blopker`_
Bouke Haarsma
Brent Payne `@brentpayne`_ @brentpayne
Bartek `@btknu`_
Burhan Khalid            `@burhan`_                   @burhan
Carl Johnson `@carlmjohnson`_ @carlmjohnson
Catherine Devlin `@catherinedevlin`_
@ -87,6 +87,7 @@ Listed in alphabetical order.
Craig Margieson `@cmargieson`_
Cristian Vargas `@cdvv7788`_
Cullen Rhodes `@c-rhodes`_
Curtis St Pierre `@curtisstpierre`_ @cstpierre1388
Dan Shultz `@shultz`_
Daniel Hepper `@dhepper`_ @danielhepper
Daniele Tricoli `@eriol`_
@ -95,6 +96,7 @@ Listed in alphabetical order.
Davur Clementsen `@dsclementsen`_ @davur
Delio Castillo `@jangeador`_ @jangeador
Demetris Stavrou `@demestav`_
Denis Bobrov `@delneg`_
Denis Orehovsky `@apirobot`_
Dónal Adams `@epileptic-fish`_
Diane Chen `@purplediane`_ @purplediane88
@ -114,8 +116,8 @@ Listed in alphabetical order.
Ian Lee `@IanLee1521`_
Irfan Ahmad `@erfaan`_ @erfaan
Jan Van Bruggen `@jvanbrug`_
Jens Nilsson `@phiberjenz`_
Jelmer Draaijer `@foarsitter`_
Jens Nilsson `@phiberjenz`_
Jerome Leclanche `@jleclanche`_ @Adys
Jimmy Gitonga `@afrowave`_ @afrowave
John Cass `@jcass77`_ @cass_john
@ -124,9 +126,10 @@ Listed in alphabetical order.
Kaido Kert `@kaidokert`_
kappataumu `@kappataumu`_ @kappataumu
Kaveh `@ka7eh`_
Keith Bailey `@keithjeb`_
Keith Webber `@townie`_
Kevin A. Stone
Kevin Ndung'u `@kevgathuku`_
Keith Webber `@townie`_
Krzysztof Szumny `@noisy`_
Krzysztof Żuraw `@krzysztofzuraw`_
Leonardo Jimenez `@xpostudio4`_
@ -141,6 +144,7 @@ Listed in alphabetical order.
Mateusz Ostaszewski `@mostaszewski`_
Mathijs Hoogland `@MathijsHoogland`_
Matt Braymer-Hayes `@mattayes`_ @mattayes
Matt Knapper `@mknapper1`_
Matt Linares
Matt Menzenski `@menzenski`_
Matt Warren `@mfwarren`_
@ -154,22 +158,24 @@ Listed in alphabetical order.
Parbhat Puri `@parbhat`_
Peter Bittner `@bittner`_
Peter Coles `@mrcoles`_
Philipp Matthies `@canonnervio`_
Pierre Chiquet `@pchiquet`_
Raphael Pierzina `@hackebrot`_
Raony Guimarães Corrêa `@raonyguimaraes`_
Raphael Pierzina `@hackebrot`_
Reggie Riser `@reggieriser`_
René Muhl `@rm--`_
Roman Afanaskin `@siauPatrick`_
Roman Osipenko `@romanosipenko`_
Russell Davies
Sascha `@saschalalala`_ @saschalalala
Sam Collins `@MightySCollins`_
Sascha `@saschalalala`_ @saschalalala
Shupeyko Nikita `@webyneter`_
Sławek Ehlert `@slafs`_
Srinivas Nyayapati `@shireenrao`_
stepmr `@stepmr`_
Steve Steiner `@ssteinerX`_
Sule Marshall `@suledev`_
Tano Abeleyra `@tanoabeleyra`_
Taylor Baldwin
Théo Segonds `@show0k`_
Tim Freund `@timfreund`_
@ -178,16 +184,13 @@ Listed in alphabetical order.
Travis McNeill `@Travistock`_ @tavistock_esq
Tubo Shi `@Tubo`_
Umair Ashraf `@umrashrf`_ @fabumair
Vlad Doster `@vladdoster`_
Vadim Iskuchekov `@Egregors`_ @egregors
Vitaly Babiy
Vivian Guillen `@viviangb`_
Vlad Doster `@vladdoster`_
Will Farley `@goldhand`_ @g01dhand
William Archinal `@archinal`_
Yaroslav Halchenko
Denis Bobrov `@delneg`_
Philipp Matthies `@canonnervio`_
Vadim Iskuchekov `@Egregors`_ @egregors
Keith Bailey `@keithjeb`_
========================== ============================ ==============
.. _@a7p: https://github.com/a7p
@ -220,6 +223,7 @@ Listed in alphabetical order.
.. _@chuckus: https://github.com/chuckus
.. _@cmackenzie1: https://github.com/cmackenzie1
.. _@Collederas: https://github.com/Collederas
.. _@curtisstpierre: https://github.com/curtisstpierre
.. _@davitovmasyan: https://github.com/davitovmasyan
.. _@ddiazpinto: https://github.com/ddiazpinto
.. _@demestav: https://github.com/demestav
@ -259,6 +263,7 @@ Listed in alphabetical order.
.. _@msaizar: https://github.com/msaizar
.. _@MathijsHoogland: https://github.com/MathijsHoogland
.. _@mattayes: https://github.com/mattayes
.. _@mknapper1: https://github.com/mknapper1
.. _@menzenski: https://github.com/menzenski
.. _@mostaszewski: https://github.com/mostaszewski
.. _@mfwarren: https://github.com/mfwarren
@ -319,6 +324,7 @@ Listed in alphabetical order.
.. _@hanaquadara: https://github.com/hanaquadara
.. _@vladdoster: https://github.com/vladdoster
.. _@cmargieson: https://github.com/cmargieson
.. _@tanoabeleyra: https://github.com/tanoabeleyra
Special Thanks
~~~~~~~~~~~~~~

View File

@ -36,7 +36,7 @@ production-ready Django projects quickly.
Features
---------
* For Django 2.1
* For Django 2.2
* Works with Python 3.6
* Renders Django projects with 100% starting test coverage
* Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available)
@ -89,7 +89,7 @@ Constraints
-----------
* Only maintained 3rd party libraries are used.
* Uses PostgreSQL everywhere (9.4 - 10.5)
* Uses PostgreSQL everywhere (9.4 - 11.3)
* Environment variables for configuration (This won't work with Apache/mod_wsgi).
Support this Project!
@ -169,23 +169,21 @@ Answer the prompts with your own desired options_. For example::
use_heroku [n]: y
use_compressor [n]: y
Select postgresql_version:
1 - 10.5
2 - 10.4
3 - 10.3
4 - 10.2
5 - 10.1
6 - 9.6
7 - 9.5
8 - 9.4
Choose from 1, 2, 3, 4, 5, 6, 7, 8 [1]: 1
1 - 11.3
2 - 10.8
3 - 9.6
4 - 9.5
5 - 9.4
Choose from 1, 2, 3, 4, 5 [1]: 1
Select js_task_runner:
1 - None
2 - Gulp
Choose from 1, 2 [1]: 1
Select cloud_provider:
1 - AWS
2 - GCS
Choose from 1, 2 [1]: 1
2 - GCP
3 - None
Choose from 1, 2, 3 [1]: 1
custom_bootstrap_compilation [n]: n
Select open_source_license:
1 - MIT

View File

@ -18,11 +18,8 @@
"use_pycharm": "n",
"use_docker": "n",
"postgresql_version": [
"10.5",
"10.4",
"10.3",
"10.2",
"10.1",
"11.3",
"10.8",
"9.6",
"9.5",
"9.4"
@ -33,7 +30,8 @@
],
"cloud_provider": [
"AWS",
"GCE"
"GCP",
"None"
],
"custom_bootstrap_compilation": "n",
"use_compressor": "n",

View File

@ -42,7 +42,7 @@ master_doc = "index"
# General information about the project.
project = "Cookiecutter Django"
copyright = "2013-2018, Daniel Roy Greenfeld".format(now.year)
copyright = "2013-{}, Daniel Roy Greenfeld".format(now.year)
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the

View File

@ -35,7 +35,15 @@ Configuring the Stack
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 ``SENTRY_DSN`` variable.
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 ``SENTRY_DSN`` variable. Logs of level `logging.ERROR` are sent as Sentry events. Therefore, in order to send a Sentry event use:
.. code-block:: python
import logging
logging.error("This event is sent to Sentry", extra={"<example_key>": "<example_value>"})
The `extra` parameter allows you to send additional information about the context of this error.
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.

View File

@ -6,6 +6,12 @@ Getting Up and Running Locally With Docker
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.
.. note::
If you're new to Docker, please be aware that some resources are cached system-wide
and might reappear if you generate a project multiple times with the same name (e.g.
:ref:`this issue with Postgres <docker-postgres-auth-failed>`).
Prerequisites
-------------

View File

@ -49,14 +49,11 @@ use_docker:
postgresql_version:
Select a PostgreSQL_ version to use. The choices are:
1. 10.5
2. 10.4
3. 10.3
4. 10.2
5. 10.1
6. 9.6
7. 9.5
8. 9.4
1. 11.3
2. 10.8
3. 9.6
4. 9.5
5. 9.4
js_task_runner:
Select a JavaScript task runner. The choices are:
@ -68,7 +65,10 @@ cloud_provider:
Select a cloud provider for static & media files. The choices are:
1. AWS_
2. GCS_
2. GCP_
3. None
Note that if you choose no cloud provider, media files won't work.
custom_bootstrap_compilation:
Indicates whether the project should support Bootstrap recompilation
@ -123,7 +123,7 @@ debug:
.. _Gulp: https://github.com/gulpjs/gulp
.. _AWS: https://aws.amazon.com/s3/
.. _GCS: https://cloud.google.com/storage/
.. _GCP: https://cloud.google.com/storage/
.. _Django Compressor: https://github.com/django-compressor/django-compressor

View File

@ -45,12 +45,13 @@ DJANGO_AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID n/a
DJANGO_AWS_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY n/a raises error
DJANGO_AWS_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a raises error
DJANGO_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None
DJANGO_GCE_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error
DJANGO_GCP_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error
GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error
SENTRY_DSN SENTRY_DSN n/a raises error
DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO
MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error
MAILGUN_API_KEY MAILGUN_API_KEY n/a raises error
MAILGUN_DOMAIN MAILGUN_SENDER_DOMAIN n/a raises error
MAILGUN_API_URL n/a n/a "https://api.mailgun.net/v3"
======================================= =========================== ============================================== ======================================================================
--------------------------

View File

@ -3,15 +3,49 @@ Troubleshooting
This page contains some advice about errors and problems commonly encountered during the development of Cookiecutter Django applications.
Server Error on sign-up/log-in
------------------------------
Make sure you have configured the mail backend (e.g. Mailgun) by adding the API key and sender domain
.. include:: mailgun.rst
.. _docker-postgres-auth-failed:
Docker: Postgres authentication failed
--------------------------------------
Examples of logs::
postgres_1 | 2018-06-07 19:11:23.963 UTC [81] FATAL: password authentication failed for user "pydanny"
postgres_1 | 2018-06-07 19:11:23.963 UTC [81] DETAIL: Password does not match for user "pydanny".
postgres_1 | Connection matched pg_hba.conf line 95: "host all all all md5"
If you recreate the project multiple times with the same name, Docker would preserve the volumes for the postgres container between projects. Here is what happens:
#. You generate the project the first time. The .env postgres file is populated with the random password
#. You run the docker-compose and the containers are created. The postgres container creates the database based on the .env file credentials
#. You "regenerate" the project with the same name, so the postgres .env file is populated with a new random password
#. You run docker-compose. Since the names of the containers are the same, docker will try to start them (not create them from scratch i.e. it won't execute the Dockerfile to recreate the database). When this happens, it tries to start the database based on the new credentials which do not match the ones that the database was created with, and you get the error message above.
To fix this, you can either:
- Clear your project-related Docker cache with ``docker-compose -f local.yml down --volumes --rmi all``.
- Use the Docker volume sub-commands to find volumes (`ls`_) and remove them (`rm`_).
- Use the `prune`_ command to clear system-wide (use with care!).
.. _ls: https://docs.docker.com/engine/reference/commandline/volume_ls/
.. _rm: https://docs.docker.com/engine/reference/commandline/volume_rm/
.. _prune: https://docs.docker.com/v17.09/engine/reference/commandline/system_prune/
Others
------
#. ``project_slug`` must be a valid Python module name or you will have issues on imports.
#. ``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
#. New apps not getting created in project root: This is the expected behavior, because cookiecutter-django does not change the way that django startapp works, you'll have to fix this manually (see `#1725`_)
#. .. include:: mailgun.rst
.. _#528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373
.. _#1725: https://github.com/pydanny/cookiecutter-django/issues/1725#issuecomment-407493176

View File

@ -89,8 +89,16 @@ def remove_packagejson_file():
os.remove(file_name)
def remove_celery_app():
shutil.rmtree(os.path.join("{{ cookiecutter.project_slug }}", "taskapp"))
def remove_celery_files():
file_names = [
os.path.join("config", "celery_app.py"),
os.path.join("{{ cookiecutter.project_slug }}", "users", "tasks.py"),
os.path.join(
"{{ cookiecutter.project_slug }}", "users", "tests", "test_tasks.py"
),
]
for file_name in file_names:
os.remove(file_name)
def remove_dottravisyml_file():
@ -320,8 +328,14 @@ def main():
if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_node_dockerfile()
if "{{ cookiecutter.cloud_provider}}".lower() == "none":
print(
WARNING + "You chose not to use a cloud provider, "
"media files won't be served in production." + TERMINATOR
)
if "{{ cookiecutter.use_celery }}".lower() == "n":
remove_celery_app()
remove_celery_files()
if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_celery_compose_dirs()

View File

@ -1,3 +1,7 @@
[pytest]
addopts = -x --tb=short
python_paths = .
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/*
markers =
flake8: Run flake8 on all possible template combinations
black: Run black on all possible template combinations

View File

@ -5,12 +5,13 @@ binaryornot==0.4.4
# Code quality
# ------------------------------------------------------------------------------
black==19.3b0
flake8==3.7.6
flake8==3.7.7
# Testing
# ------------------------------------------------------------------------------
tox==3.9.0
pytest==4.4.2
pytest_cases==1.6.2
tox==3.12.1
pytest==4.6.0
pytest_cases==1.6.3
pytest-cookies==0.3.0
pytest-xdist==1.28.0
pyyaml==5.1

View File

@ -10,10 +10,10 @@ except ImportError:
# 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.
version = "2.0.2"
version = "2.2.1"
if sys.argv[-1] == "tag":
os.system('git tag -a %s -m "version %s"' % (version, version))
os.system(f'git tag -a {version} -m "version {version}"')
os.system("git push --tags")
sys.exit()
@ -34,7 +34,7 @@ setup(
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
"Framework :: Django :: 2.0",
"Framework :: Django :: 2.2",
"Intended Audience :: Developers",
"Natural Language :: English",
"License :: OSI Approved :: BSD License",

View File

@ -7,11 +7,11 @@ import sh
import yaml
from binaryornot.check import is_binary
PATTERN = "{{(\s?cookiecutter)[.](.*?)}}"
PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}"
RE_OBJ = re.compile(PATTERN)
YN_CHOICES = ["y", "n"]
CLOUD_CHOICES = ["AWS", "GCE"]
CLOUD_CHOICES = ["AWS", "GCE", "None"]
@pytest.fixture
@ -101,9 +101,10 @@ def test_project_generation(cookies, context, context_combination):
check_paths(paths)
def test_linting_passes(cookies, context_combination):
@pytest.mark.flake8
def test_flake8_passes(cookies, context_combination):
"""
Generated project should pass flake8 & black.
Generated project should pass flake8.
This is parametrized for each combination from ``context_combination`` fixture
"""
@ -114,6 +115,16 @@ def test_linting_passes(cookies, context_combination):
except sh.ErrorReturnCode as e:
pytest.fail(e)
@pytest.mark.black
def test_black_passes(cookies, context_combination):
"""
Generated project should pass black.
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context=context_combination)
try:
sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/")
except sh.ErrorReturnCode as e:

12
tox.ini
View File

@ -1,11 +1,19 @@
[tox]
skipsdist = true
envlist = py36,black
envlist = py36,flake8,black,black-template
[testenv]
deps = -rrequirements.txt
commands = pytest {posargs:./tests}
commands = pytest -m "not flake8" -m "not black" {posargs:./tests}
[testenv:flake8]
deps = -rrequirements.txt
commands = pytest -m flake8 {posargs:./tests}
[testenv:black]
deps = -rrequirements.txt
commands = pytest -m black {posargs:./tests}
[testenv:black-template]
deps = black
commands = black --check hooks tests setup.py docs

View File

@ -22,11 +22,11 @@ MAILGUN_DOMAIN=
DJANGO_AWS_ACCESS_KEY_ID=
DJANGO_AWS_SECRET_ACCESS_KEY=
DJANGO_AWS_STORAGE_BUCKET_NAME=
{% elif cookiecutter.cloud_provider == 'GCE' %}
# GCE
{% elif cookiecutter.cloud_provider == 'GCP' %}
# GCP
# ------------------------------------------------------------------------------
GOOGLE_APPLICATION_CREDENTIALS=
DJANGO_GCE_STORAGE_BUCKET_NAME=
DJANGO_GCP_STORAGE_BUCKET_NAME=
{% endif %}
# django-allauth
# ------------------------------------------------------------------------------

View File

@ -1,5 +1,5 @@
release: python manage.py migrate
web: gunicorn config.wsgi:application
{% if cookiecutter.use_celery == "y" -%}
worker: celery worker --app={{cookiecutter.project_slug}}.taskapp --loglevel=info
worker: celery worker --app=config.celery_app --loglevel=info
{%- endif %}

View File

@ -79,7 +79,7 @@ To run a celery worker:
.. code-block:: bash
cd {{cookiecutter.project_slug}}
celery -A {{cookiecutter.project_slug}}.taskapp worker -l info
celery -A config.celery_app worker -l info
Please note: For Celery's import magic to work, it is important *where* the celery commands are run. If you are in the same folder with *manage.py*, you should be right.

View File

@ -5,4 +5,4 @@ set -o nounset
rm -f './celerybeat.pid'
celery -A {{cookiecutter.project_slug}}.taskapp beat -l INFO
celery -A config.celery_app beat -l INFO

View File

@ -5,6 +5,6 @@ set -o nounset
celery flower \
--app={{cookiecutter.project_slug}}.taskapp \
--app=config.celery_app \
--broker="${CELERY_BROKER_URL}" \
--basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"

View File

@ -4,4 +4,4 @@ set -o errexit
set -o nounset
celery -A {{cookiecutter.project_slug}}.taskapp worker -l INFO
celery -A config.celery_app worker -l INFO

View File

@ -5,4 +5,4 @@ set -o pipefail
set -o nounset
celery -A {{cookiecutter.project_slug}}.taskapp beat -l INFO
celery -A config.celery_app beat -l INFO

View File

@ -5,6 +5,6 @@ set -o nounset
celery flower \
--app={{cookiecutter.project_slug}}.taskapp \
--app=config.celery_app \
--broker="${CELERY_BROKER_URL}" \
--basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"

View File

@ -5,4 +5,4 @@ set -o pipefail
set -o nounset
celery -A {{cookiecutter.project_slug}}.taskapp worker -l INFO
celery -A config.celery_app worker -l INFO

View File

@ -17,7 +17,7 @@ defaultEntryPoints = ["http", "https"]
[acme]
# Email address used for registration
email = "{{ cookiecutter.email }}"
storageFile = "/etc/traefik/acme/acme.json"
storage = "/etc/traefik/acme/acme.json"
entryPoint = "https"
onDemand = false
OnHostRule = true

View File

@ -0,0 +1,7 @@
{% if cookiecutter.use_celery == 'y' -%}
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celery_app import app as celery_app
__all__ = ("celery_app",)
{% endif -%}

View File

@ -0,0 +1,16 @@
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
app = Celery("{{cookiecutter.project_slug}}")
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object("django.conf:settings", namespace="CELERY")
# Load task modules from all registered Django app configs.
app.autodiscover_tasks()

View File

@ -35,6 +35,8 @@ USE_I18N = True
USE_L10N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True
# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths
LOCALE_PATHS = [ROOT_DIR.path("locale")]
# DATABASES
# ------------------------------------------------------------------------------
@ -75,7 +77,7 @@ THIRD_PARTY_APPS = [
"rest_framework",
]
LOCAL_APPS = [
"{{ cookiecutter.project_slug }}.users.apps.UsersAppConfig",
"{{ cookiecutter.project_slug }}.users.apps.UsersConfig",
# Your stuff: custom apps go here
]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
@ -126,6 +128,7 @@ AUTH_PASSWORD_VALIDATORS = [
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
@ -221,10 +224,33 @@ ADMINS = [("""{{cookiecutter.author_name}}""", "{{cookiecutter.email}}")]
# https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS
# LOGGING
# ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#logging
# See https://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "%(levelname)s %(asctime)s %(module)s "
"%(process)d %(thread)d %(message)s"
}
},
"handlers": {
"console": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "verbose",
}
},
"root": {"level": "INFO", "handlers": ["console"]},
}
{% if cookiecutter.use_celery == 'y' -%}
# Celery
# ------------------------------------------------------------------------------
INSTALLED_APPS += ["{{cookiecutter.project_slug}}.taskapp.celery.CeleryAppConfig"]
if USE_TZ:
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-timezone
CELERY_TIMEZONE = TIME_ZONE
@ -240,10 +266,10 @@ CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-time-limit
# TODO: set to whatever value is adequate in your circumstances
CELERYD_TASK_TIME_LIMIT = 5 * 60
CELERY_TASK_TIME_LIMIT = 5 * 60
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-soft-time-limit
# TODO: set to whatever value is adequate in your circumstances
CELERYD_TASK_SOFT_TIME_LIMIT = 60
CELERY_TASK_SOFT_TIME_LIMIT = 60
{%- endif %}
# django-allauth

View File

@ -66,10 +66,12 @@ SECURE_CONTENT_TYPE_NOSNIFF = env.bool(
"DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True
)
{% if cookiecutter.cloud_provider != 'None' -%}
# STORAGES
# ------------------------------------------------------------------------------
# https://django-storages.readthedocs.io/en/latest/#installation
INSTALLED_APPS += ["storages"] # noqa F405
{%- endif -%}
{% if cookiecutter.cloud_provider == 'AWS' %}
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_ACCESS_KEY_ID = env("DJANGO_AWS_ACCESS_KEY_ID")
@ -89,23 +91,24 @@ AWS_S3_OBJECT_PARAMETERS = {
AWS_DEFAULT_ACL = None
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None)
{% elif cookiecutter.cloud_provider == 'GCE' %}
{% elif cookiecutter.cloud_provider == 'GCP' %}
DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
GS_BUCKET_NAME = env("DJANGO_GCE_STORAGE_BUCKET_NAME")
GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME")
GS_DEFAULT_ACL = "publicRead"
{% endif %}
{% endif -%}
{% if cookiecutter.cloud_provider != 'None' or cookiecutter.use_whitenoise == 'y' -%}
# STATIC
# ------------------------
{% endif -%}
{% if cookiecutter.use_whitenoise == 'y' -%}
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
{%- endif -%}
{%- if cookiecutter.cloud_provider == 'AWS' %}
{% elif cookiecutter.cloud_provider == 'AWS' -%}
STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage"
STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/"
{%- elif cookiecutter.cloud_provider == 'GCE' %}
STATIC_URL = "https://storage.googleapis.com/{}/static/".format(GS_BUCKET_NAME)
{%- endif %}
{% elif cookiecutter.cloud_provider == 'GCP' -%}
STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/"
{% endif -%}
# MEDIA
# ------------------------------------------------------------------------------
@ -117,6 +120,7 @@ from storages.backends.s3boto3 import S3Boto3Storage # noqa E402
class StaticRootS3Boto3Storage(S3Boto3Storage):
location = "static"
default_acl = "public-read"
class MediaRootS3Boto3Storage(S3Boto3Storage):
@ -127,9 +131,9 @@ class MediaRootS3Boto3Storage(S3Boto3Storage):
# endregion
DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage"
MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/"
{%- elif cookiecutter.cloud_provider == 'GCE' %}
MEDIA_URL = "https://storage.googleapis.com/{}/media/".format(GS_BUCKET_NAME)
MEDIA_ROOT = "https://storage.googleapis.com/{}/media/".format(GS_BUCKET_NAME)
{%- elif cookiecutter.cloud_provider == 'GCP' %}
MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
MEDIA_ROOT = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
{%- endif %}
# TEMPLATES
@ -172,6 +176,7 @@ EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
ANYMAIL = {
"MAILGUN_API_KEY": env("MAILGUN_API_KEY"),
"MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"),
"MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"),
}
# Gunicorn
@ -193,7 +198,7 @@ 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{% if cookiecutter.use_whitenoise == 'y' %} # noqa F405{% endif %}
COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' or cookiecutter.cloud_provider == 'None' %} # noqa F405{% endif %}
{% endif %}
{%- if cookiecutter.use_whitenoise == 'n' -%}
# Collectfast
@ -233,6 +238,7 @@ LOGGING = {
"formatter": "verbose",
},
},
"root": {"level": "INFO", "handlers": ["console"]},
"loggers": {
"django.request": {
"handlers": ["mail_admins"],
@ -263,6 +269,7 @@ LOGGING = {
"formatter": "verbose",
}
},
"root": {"level": "INFO", "handlers": ["console"]},
"loggers": {
"django.db.backends": {
"level": "ERROR",
@ -286,7 +293,7 @@ SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO)
sentry_logging = LoggingIntegration(
level=SENTRY_LOG_LEVEL, # Capture info and above as breadcrumbs
event_level=None, # Send no events from log messages
event_level=logging.ERROR, # Send errors as events
)
{%- if cookiecutter.use_celery == 'y' %}

View File

@ -45,7 +45,7 @@ services:
{%- if cookiecutter.use_celery == 'y' %}
redis:
image: redis:3.2
image: redis:5.0
celeryworker:
<<: *django

View File

@ -44,7 +44,7 @@ services:
- "0.0.0.0:443:443"
redis:
image: redis:3.2
image: redis:5.0
{%- if cookiecutter.use_celery == 'y' %}
celeryworker:

View File

@ -18,13 +18,13 @@ flower==0.9.3 # https://github.com/mher/flower
# Django
# ------------------------------------------------------------------------------
django==2.1.8 # pyup: < 2.2 # https://www.djangoproject.com/
django==2.2.2 # pyup: < 3.0 # https://www.djangoproject.com/
django-environ==0.4.5 # https://github.com/joke2k/django-environ
django-model-utils==3.1.2 # https://github.com/jazzband/django-model-utils
django-allauth==0.39.1 # https://github.com/pennersr/django-allauth
django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms
{%- if cookiecutter.use_compressor == "y" %}
django-compressor==2.2 # https://github.com/django-compressor/django-compressor
django-compressor==2.3 # https://github.com/django-compressor/django-compressor
{%- endif %}
django-redis==4.10.0 # https://github.com/niwinz/django-redis

View File

@ -1,10 +1,10 @@
-r ./base.txt
Werkzeug==0.15.2 # https://github.com/pallets/werkzeug
Werkzeug==0.14.1 # pyup: < 0.15 # https://github.com/pallets/werkzeug
ipdb==0.12 # https://github.com/gotcha/ipdb
Sphinx==2.0.1 # https://github.com/sphinx-doc/sphinx
Sphinx==2.1.0 # https://github.com/sphinx-doc/sphinx
{%- if cookiecutter.use_docker == 'y' %}
psycopg2==2.8 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
psycopg2==2.8.2 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- else %}
psycopg2-binary==2.8.2 # https://github.com/psycopg/psycopg2
{%- endif %}
@ -12,12 +12,12 @@ psycopg2-binary==2.8.2 # https://github.com/psycopg/psycopg2
# Testing
# ------------------------------------------------------------------------------
mypy==0.701 # https://github.com/python/mypy
pytest==4.4.2 # https://github.com/pytest-dev/pytest
pytest==4.6.0 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar
# Code quality
# ------------------------------------------------------------------------------
flake8==3.7.5 # https://github.com/PyCQA/flake8
flake8==3.7.7 # https://github.com/PyCQA/flake8
coverage==4.5.3 # https://github.com/nedbat/coveragepy
black==19.3b0 # https://github.com/ambv/black
pylint-django==2.0.9 # https://github.com/PyCQA/pylint-django
@ -27,9 +27,9 @@ pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery
# Django
# ------------------------------------------------------------------------------
factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy
factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy
django-debug-toolbar==1.11 # https://github.com/jazzband/django-debug-toolbar
django-extensions==2.1.6 # https://github.com/django-extensions/django-extensions
django-extensions==2.1.7 # https://github.com/django-extensions/django-extensions
django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin
pytest-django==3.4.8 # https://github.com/pytest-dev/pytest-django

View File

@ -3,19 +3,19 @@
-r ./base.txt
gunicorn==19.9.0 # https://github.com/benoitc/gunicorn
psycopg2==2.8 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
psycopg2==2.8.2 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==0.6.2 # https://github.com/antonagestam/collectfast
{%- endif %}
{%- if cookiecutter.use_sentry == "y" %}
sentry-sdk==0.7.14 # https://github.com/getsentry/sentry-python
sentry-sdk==0.9.0 # https://github.com/getsentry/sentry-python
{%- endif %}
# Django
# ------------------------------------------------------------------------------
{%- if cookiecutter.cloud_provider == 'AWS' %}
django-storages[boto3]==1.7.1 # https://github.com/jschneier/django-storages
{%- elif cookiecutter.cloud_provider == 'GCE' %}
{%- elif cookiecutter.cloud_provider == 'GCP' %}
django-storages[google]==1.7.1 # https://github.com/jschneier/django-storages
{%- endif %}
django-anymail[mailgun]==6.0 # https://github.com/anymail/django-anymail
django-anymail[mailgun]==6.0.1 # https://github.com/anymail/django-anymail

View File

@ -1,38 +0,0 @@
{% if cookiecutter.use_celery == 'y' -%}
import os
from celery import Celery
from django.apps import apps, AppConfig
from django.conf import settings
if not settings.configured:
# set the default Django settings module for the 'celery' program.
os.environ.setdefault(
"DJANGO_SETTINGS_MODULE", "config.settings.local"
) # pragma: no cover
app = Celery("{{cookiecutter.project_slug}}")
# Using a string here means the worker will not have to
# pickle the object when using Windows.
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
app.config_from_object("django.conf:settings", namespace="CELERY")
class CeleryAppConfig(AppConfig):
name = "{{cookiecutter.project_slug}}.taskapp"
verbose_name = "Celery Config"
def ready(self):
installed_apps = [app_config.name for app_config in apps.get_app_configs()]
app.autodiscover_tasks(lambda: installed_apps, force=True)
@app.task(bind=True)
def debug_task(self):
print(f"Request: {self.request!r}") # pragma: no cover
{% else %}
# Use this as a starting point for your project with celery.
# If you are not using celery, you can remove this app
{% endif -%}

View File

@ -1,10 +1,10 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class UsersAppConfig(AppConfig):
class UsersConfig(AppConfig):
name = "{{ cookiecutter.project_slug }}.users"
verbose_name = "Users"
verbose_name = _("Users")
def ready(self):
try:

View File

@ -0,0 +1,11 @@
from django.contrib.auth import get_user_model
from config import celery_app
User = get_user_model()
@celery_app.task()
def get_users_count():
"""A pointless Celery task to demonstrate usage."""
return User.objects.count()

View File

@ -0,0 +1,16 @@
import pytest
from celery.result import EagerResult
from {{ cookiecutter.project_slug }}.users.tasks import get_users_count
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
@pytest.mark.django_db
def test_user_count(settings):
"""A basic test to execute the get_users_count Celery task."""
UserFactory.create_batch(3)
settings.CELERY_TASK_ALWAYS_EAGER = True
task_result = get_users_count.delay()
assert isinstance(task_result, EagerResult)
assert task_result.result == 3

View File

@ -13,11 +13,6 @@ def test_detail(user: settings.AUTH_USER_MODEL):
assert resolve(f"/users/{user.username}/").view_name == "users:detail"
def test_list():
assert reverse("users:list") == "/users/"
assert resolve("/users/").view_name == "users:list"
def test_update():
assert reverse("users:update") == "/users/~update/"
assert resolve("/users/~update/").view_name == "users:update"

View File

@ -1,7 +1,6 @@
from django.urls import path
from {{ cookiecutter.project_slug }}.users.views import (
user_list_view,
user_redirect_view,
user_update_view,
user_detail_view,
@ -9,7 +8,6 @@ from {{ cookiecutter.project_slug }}.users.views import (
app_name = "users"
urlpatterns = [
path("", view=user_list_view, name="list"),
path("~redirect/", view=user_redirect_view, name="redirect"),
path("~update/", view=user_update_view, name="update"),
path("<str:username>/", view=user_detail_view, name="detail"),

View File

@ -1,7 +1,7 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from django.views.generic import DetailView, RedirectView, UpdateView
User = get_user_model()
@ -16,16 +16,6 @@ class UserDetailView(LoginRequiredMixin, DetailView):
user_detail_view = UserDetailView.as_view()
class UserListView(LoginRequiredMixin, ListView):
model = User
slug_field = "username"
slug_url_kwarg = "username"
user_list_view = UserListView.as_view()
class UserUpdateView(LoginRequiredMixin, UpdateView):
model = User