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: services:
- docker - docker
@ -13,10 +13,14 @@ before_install:
matrix: matrix:
include: include:
- name: Tox Test - name: Test results
script: tox -e py36 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 script: tox -e black
- name: Black template
script: tox -e black-template
- name: Basic Docker - name: Basic Docker
script: sh tests/test_docker.sh script: sh tests/test_docker.sh
- name: Docker with Celery - name: Docker with Celery

View File

@ -2,6 +2,196 @@
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/).
## [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] ## [2018-02-16]
### Changed ### Changed
- Upgraded to Django 2.0 (@epicwhale) - Upgraded to Django 2.0 (@epicwhale)

View File

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

View File

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

View File

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

View File

@ -42,7 +42,7 @@ master_doc = "index"
# General information about the project. # General information about the project.
project = "Cookiecutter Django" 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 # 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

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. 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. 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. 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.
.. 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 Prerequisites
------------- -------------

View File

@ -49,14 +49,11 @@ use_docker:
postgresql_version: postgresql_version:
Select a PostgreSQL_ version to use. The choices are: Select a PostgreSQL_ version to use. The choices are:
1. 10.5 1. 11.3
2. 10.4 2. 10.8
3. 10.3 3. 9.6
4. 10.2 4. 9.5
5. 10.1 5. 9.4
6. 9.6
7. 9.5
8. 9.4
js_task_runner: js_task_runner:
Select a JavaScript task runner. The choices are: 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: Select a cloud provider for static & media files. The choices are:
1. AWS_ 1. AWS_
2. GCS_ 2. GCP_
3. None
Note that if you choose no cloud provider, media files won't work.
custom_bootstrap_compilation: custom_bootstrap_compilation:
Indicates whether the project should support Bootstrap recompilation Indicates whether the project should support Bootstrap recompilation
@ -100,7 +100,7 @@ use_travisci:
keep_local_envs_in_vcs: keep_local_envs_in_vcs:
Indicates whether the project's ``.envs/.local/`` should be kept in VCS Indicates whether the project's ``.envs/.local/`` should be kept in VCS
(comes in handy when working in teams where local environment reproducibility (comes in handy when working in teams where local environment reproducibility
is strongly encouraged). is strongly encouraged).
Note: .env(s) are only utilized when Docker Compose and/or Heroku support is enabled. Note: .env(s) are only utilized when Docker Compose and/or Heroku support is enabled.
debug: debug:
@ -123,7 +123,7 @@ debug:
.. _Gulp: https://github.com/gulpjs/gulp .. _Gulp: https://github.com/gulpjs/gulp
.. _AWS: https://aws.amazon.com/s3/ .. _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 .. _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_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_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a raises error
DJANGO_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None 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 GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error
SENTRY_DSN SENTRY_DSN n/a raises error SENTRY_DSN SENTRY_DSN n/a raises error
DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO 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_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. 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. #. ``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`_) #. ``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`_) #. 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 .. _#528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373
.. _#1725: https://github.com/pydanny/cookiecutter-django/issues/1725#issuecomment-407493176 .. _#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) os.remove(file_name)
def remove_celery_app(): def remove_celery_files():
shutil.rmtree(os.path.join("{{ cookiecutter.project_slug }}", "taskapp")) 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(): def remove_dottravisyml_file():
@ -320,8 +328,14 @@ def main():
if "{{ cookiecutter.use_docker }}".lower() == "y": if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_node_dockerfile() 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": if "{{ cookiecutter.use_celery }}".lower() == "n":
remove_celery_app() remove_celery_files()
if "{{ cookiecutter.use_docker }}".lower() == "y": if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_celery_compose_dirs() remove_celery_compose_dirs()

View File

@ -1,3 +1,7 @@
[pytest] [pytest]
addopts = -x --tb=short
python_paths = . python_paths = .
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/* 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 # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
black==19.3b0 black==19.3b0
flake8==3.7.6 flake8==3.7.7
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
tox==3.9.0 tox==3.12.1
pytest==4.4.2 pytest==4.6.0
pytest_cases==1.6.2 pytest_cases==1.6.3
pytest-cookies==0.3.0 pytest-cookies==0.3.0
pytest-xdist==1.28.0
pyyaml==5.1 pyyaml==5.1

View File

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

View File

@ -7,11 +7,11 @@ import sh
import yaml import yaml
from binaryornot.check import is_binary from binaryornot.check import is_binary
PATTERN = "{{(\s?cookiecutter)[.](.*?)}}" PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}"
RE_OBJ = re.compile(PATTERN) RE_OBJ = re.compile(PATTERN)
YN_CHOICES = ["y", "n"] YN_CHOICES = ["y", "n"]
CLOUD_CHOICES = ["AWS", "GCE"] CLOUD_CHOICES = ["AWS", "GCE", "None"]
@pytest.fixture @pytest.fixture
@ -101,9 +101,10 @@ def test_project_generation(cookies, context, context_combination):
check_paths(paths) 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 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: except sh.ErrorReturnCode as e:
pytest.fail(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: try:
sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/") sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/")
except sh.ErrorReturnCode as e: except sh.ErrorReturnCode as e:

12
tox.ini
View File

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

View File

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

View File

@ -1,5 +1,5 @@
release: python manage.py migrate release: python 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=config.celery_app --loglevel=info
{%- endif %} {%- endif %}

View File

@ -79,7 +79,7 @@ To run a celery worker:
.. code-block:: bash .. code-block:: bash
cd {{cookiecutter.project_slug}} 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. 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' 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 \ celery flower \
--app={{cookiecutter.project_slug}}.taskapp \ --app=config.celery_app \
--broker="${CELERY_BROKER_URL}" \ --broker="${CELERY_BROKER_URL}" \
--basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}" --basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"

View File

@ -4,4 +4,4 @@ set -o errexit
set -o nounset 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 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 \ celery flower \
--app={{cookiecutter.project_slug}}.taskapp \ --app=config.celery_app \
--broker="${CELERY_BROKER_URL}" \ --broker="${CELERY_BROKER_URL}" \
--basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}" --basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}"

View File

@ -5,4 +5,4 @@ set -o pipefail
set -o nounset 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] [acme]
# Email address used for registration # Email address used for registration
email = "{{ cookiecutter.email }}" email = "{{ cookiecutter.email }}"
storageFile = "/etc/traefik/acme/acme.json" storage = "/etc/traefik/acme/acme.json"
entryPoint = "https" entryPoint = "https"
onDemand = false onDemand = false
OnHostRule = true 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 USE_L10N = True
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz # https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
USE_TZ = True USE_TZ = True
# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths
LOCALE_PATHS = [ROOT_DIR.path("locale")]
# DATABASES # DATABASES
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -75,7 +77,7 @@ THIRD_PARTY_APPS = [
"rest_framework", "rest_framework",
] ]
LOCAL_APPS = [ LOCAL_APPS = [
"{{ cookiecutter.project_slug }}.users.apps.UsersAppConfig", "{{ 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 # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
@ -126,6 +128,7 @@ AUTH_PASSWORD_VALIDATORS = [
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
@ -221,10 +224,33 @@ ADMINS = [("""{{cookiecutter.author_name}}""", "{{cookiecutter.email}}")]
# https://docs.djangoproject.com/en/dev/ref/settings/#managers # https://docs.djangoproject.com/en/dev/ref/settings/#managers
MANAGERS = ADMINS 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' -%} {% if cookiecutter.use_celery == 'y' -%}
# Celery # Celery
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
INSTALLED_APPS += ["{{cookiecutter.project_slug}}.taskapp.celery.CeleryAppConfig"]
if USE_TZ: if USE_TZ:
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-timezone # http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-timezone
CELERY_TIMEZONE = TIME_ZONE CELERY_TIMEZONE = TIME_ZONE
@ -240,10 +266,10 @@ CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json" CELERY_RESULT_SERIALIZER = "json"
# http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-time-limit # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-time-limit
# TODO: set to whatever value is adequate in your circumstances # 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 # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-soft-time-limit
# TODO: set to whatever value is adequate in your circumstances # TODO: set to whatever value is adequate in your circumstances
CELERYD_TASK_SOFT_TIME_LIMIT = 60 CELERY_TASK_SOFT_TIME_LIMIT = 60
{%- endif %} {%- endif %}
# django-allauth # django-allauth

View File

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

View File

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

View File

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

View File

@ -18,13 +18,13 @@ flower==0.9.3 # https://github.com/mher/flower
# Django # 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-environ==0.4.5 # https://github.com/joke2k/django-environ
django-model-utils==3.1.2 # https://github.com/jazzband/django-model-utils 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-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 django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms
{%- if cookiecutter.use_compressor == "y" %} {%- 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 %} {%- endif %}
django-redis==4.10.0 # https://github.com/niwinz/django-redis django-redis==4.10.0 # https://github.com/niwinz/django-redis

View File

@ -1,10 +1,10 @@
-r ./base.txt -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 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' %} {%- 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 %} {%- else %}
psycopg2-binary==2.8.2 # https://github.com/psycopg/psycopg2 psycopg2-binary==2.8.2 # https://github.com/psycopg/psycopg2
{%- endif %} {%- endif %}
@ -12,12 +12,12 @@ psycopg2-binary==2.8.2 # https://github.com/psycopg/psycopg2
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
mypy==0.701 # https://github.com/python/mypy 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 pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar
# Code quality # 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 coverage==4.5.3 # https://github.com/nedbat/coveragepy
black==19.3b0 # https://github.com/ambv/black black==19.3b0 # https://github.com/ambv/black
pylint-django==2.0.9 # https://github.com/PyCQA/pylint-django 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 # 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-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 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 pytest-django==3.4.8 # https://github.com/pytest-dev/pytest-django

View File

@ -3,19 +3,19 @@
-r ./base.txt -r ./base.txt
gunicorn==19.9.0 # https://github.com/benoitc/gunicorn 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' %} {%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==0.6.2 # https://github.com/antonagestam/collectfast Collectfast==0.6.2 # https://github.com/antonagestam/collectfast
{%- endif %} {%- endif %}
{%- if cookiecutter.use_sentry == "y" %} {%- 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 %} {%- 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.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 django-storages[google]==1.7.1 # https://github.com/jschneier/django-storages
{%- endif %} {%- 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.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class UsersAppConfig(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):
try: 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" 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(): def test_update():
assert reverse("users:update") == "/users/~update/" assert reverse("users:update") == "/users/~update/"
assert resolve("/users/~update/").view_name == "users:update" assert resolve("/users/~update/").view_name == "users:update"

View File

@ -1,7 +1,6 @@
from django.urls import path from django.urls import path
from {{ cookiecutter.project_slug }}.users.views import ( from {{ cookiecutter.project_slug }}.users.views import (
user_list_view,
user_redirect_view, user_redirect_view,
user_update_view, user_update_view,
user_detail_view, user_detail_view,
@ -9,7 +8,6 @@ from {{ cookiecutter.project_slug }}.users.views import (
app_name = "users" app_name = "users"
urlpatterns = [ urlpatterns = [
path("", view=user_list_view, name="list"),
path("~redirect/", view=user_redirect_view, name="redirect"), path("~redirect/", view=user_redirect_view, name="redirect"),
path("~update/", view=user_update_view, name="update"), path("~update/", view=user_update_view, name="update"),
path("<str:username>/", view=user_detail_view, name="detail"), 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 import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse 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() User = get_user_model()
@ -16,16 +16,6 @@ class UserDetailView(LoginRequiredMixin, DetailView):
user_detail_view = UserDetailView.as_view() 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): class UserUpdateView(LoginRequiredMixin, UpdateView):
model = User model = User