Merge branch 'master' into pathlib-migration-updated

This commit is contained in:
Bruno Alla 2020-03-17 14:16:35 +00:00
commit d2988040d5
82 changed files with 1099 additions and 890 deletions

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

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

View File

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

View File

@ -1,6 +1,68 @@
# Change Log # Change Log
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/).
## [2020-01-23]
### Changed
- Fix UserFactory to set the password if provided (@BoPeng)
- Update documentation files with latest Sphinx (@howiezhao)
## [2020-01-12]
### Changed
- Fix mypy setup and added django-stubs (@danifus)
- Add Gitlab CI as option (@ikhomutov)
## [2020-01-11]
### Changed
- Speed up & reduce size for production Django image (@maxp)
- Bumped runtime version for Heroku (@Isaac12x)
- Added Debian 10 (Buster) OS dependencies (@ddiazpinto)
- Update Traefik to v2 (@blaxpy)
- Switched Docker images from Alpine based to Debian based (@trungdong)
## [2019-10-06]
### Changed
- Default Python version is now 3.7 (@nicolas471)
## [2019-10-04]
### Fixed
- Fix static files handling on GCP (@caioariede)
## [2019-10-03]
### Fixed
- Fix incompatible combination between Whitenoise and no cloud provider (@caioariede)
## [2019-07-09]
### Fixed
- Always use test settings in pytest (@danihodovic)
- Remove gunicorn from `INSTALLED_APPS` (@danihodovic)
- Remove `EMAIL_HOST` and `EMAIL_PORT` with locmem backend (@danihodovic)
### Added
- Add `EMAIL_TIMEOUT` (@danihodovic)
## [2019-06-22]
### Fixed
- Remove redundant template debug setting (@danihodovic)
## [2019-06-19]
### Fixed
- Fix removal carriage returns in docker scripts (@timclaessens)
## [2019-06-15]
### Fixed
- Issue with Pycharm setup for running things in Docker compose (@foarsitter)
## [2019-06-06]
### Changed
- Update generated Travis config (@browniebroke)
## [2019-06-03]
### Added
- Installed `django-celery-beat` to keep scheduled tasks in DB (@keyvanm)
## [2019-05-28]
### Changed
- Use GCP acronym rather than inconsistent GCE/GCS (@tanoabeleyra)
## [2019-05-27] ## [2019-05-27]
### Changed ### Changed

View File

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

View File

@ -42,12 +42,14 @@ Listed in alphabetical order.
Name Github Twitter Name Github Twitter
========================== ============================ ============== ========================== ============================ ==============
18 `@dezoito`_ 18 `@dezoito`_
2O4 `@2O4`_
a7p `@a7p`_ a7p `@a7p`_
Aaron Eikenberry `@aeikenberry`_ Aaron Eikenberry `@aeikenberry`_
Adam Bogdał `@bogdal`_ Adam Bogdał `@bogdal`_
Adam Dobrawy `@ad-m`_ Adam Dobrawy `@ad-m`_
Adam Steele `@adammsteele`_ Adam Steele `@adammsteele`_
Agam Dua Agam Dua
Agustín Scaramuzza `@scaramagus`_ @scaramagus
Alberto Sanchez `@alb3rto`_ Alberto Sanchez `@alb3rto`_
Alex Tsai `@caffodian`_ Alex Tsai `@caffodian`_
Alvaro [Andor] `@andor-pierdelacabeza`_ Alvaro [Andor] `@andor-pierdelacabeza`_
@ -55,6 +57,7 @@ Listed in alphabetical order.
Andreas Meistad `@ameistad`_ Andreas Meistad `@ameistad`_
Andres Gonzalez `@andresgz`_ Andres Gonzalez `@andresgz`_
Andrew Mikhnevich `@zcho`_ Andrew Mikhnevich `@zcho`_
Andrew Chen Wang `@Andrew-Chen-Wang`_
Andy Rose Andy Rose
Anna Callahan `@jazztpt`_ Anna Callahan `@jazztpt`_
Anna Sidwell `@takkaria`_ Anna Sidwell `@takkaria`_
@ -70,9 +73,12 @@ Listed in alphabetical order.
Benjamin Abel Benjamin Abel
Bert de Miranda `@bertdemiranda`_ Bert de Miranda `@bertdemiranda`_
Bo Lopker `@blopker`_ Bo Lopker `@blopker`_
Bo Peng `@BoPeng`_
Bouke Haarsma Bouke Haarsma
Brent Payne `@brentpayne`_ @brentpayne Brent Payne `@brentpayne`_ @brentpayne
Bruce Olivier `@bolivierjr`_
Burhan Khalid            `@burhan`_                   @burhan Burhan Khalid            `@burhan`_                   @burhan
Caio Ariede `@caioariede`_ @caioariede
Carl Johnson `@carlmjohnson`_ @carlmjohnson Carl Johnson `@carlmjohnson`_ @carlmjohnson
Catherine Devlin `@catherinedevlin`_ Catherine Devlin `@catherinedevlin`_
Cédric Gaspoz `@cgaspoz`_ Cédric Gaspoz `@cgaspoz`_
@ -83,14 +89,16 @@ Listed in alphabetical order.
Chris Pappalardo `@ChrisPappalardo`_ Chris Pappalardo `@ChrisPappalardo`_
Christopher Clarke `@chrisdev`_ Christopher Clarke `@chrisdev`_
Cole Mackenzie `@cmackenzie1`_ Cole Mackenzie `@cmackenzie1`_
Cole Maclean `@cole`_ @cole
Collederas `@Collederas`_ Collederas `@Collederas`_
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 Curtis St Pierre `@curtisstpierre`_ @cstpierre1388
Dan Shultz `@shultz`_ Dan Shultz `@shultz`_
Dani Hodovic `@danihodovic` Dani Hodovic `@danihodovic`_
Daniel Hepper `@dhepper`_ @danielhepper Daniel Hepper `@dhepper`_ @danielhepper
Daniel Hillier `@danifus`_
Daniele Tricoli `@eriol`_ Daniele Tricoli `@eriol`_
David Díaz `@ddiazpinto`_ @DavidDiazPinto David Díaz `@ddiazpinto`_ @DavidDiazPinto
Davit Tovmasyan `@davitovmasyan`_ Davit Tovmasyan `@davitovmasyan`_
@ -99,6 +107,7 @@ Listed in alphabetical order.
Demetris Stavrou `@demestav`_ Demetris Stavrou `@demestav`_
Denis Bobrov `@delneg`_ Denis Bobrov `@delneg`_
Denis Orehovsky `@apirobot`_ Denis Orehovsky `@apirobot`_
Denis Savran `@blaxpy`_
Diane Chen `@purplediane`_ @purplediane88 Diane Chen `@purplediane`_ @purplediane88
Dónal Adams `@epileptic-fish`_ Dónal Adams `@epileptic-fish`_
Dong Huynh `@trungdong`_ Dong Huynh `@trungdong`_
@ -107,17 +116,26 @@ Listed in alphabetical order.
Eric Groom `@ericgroom`_ Eric Groom `@ericgroom`_
Eyad Al Sibai `@eyadsibai`_ Eyad Al Sibai `@eyadsibai`_
Felipe Arruda `@arruda`_ Felipe Arruda `@arruda`_
Florian Idelberger `@step21`_ @windrush
Garry Cairns `@garry-cairns`_ Garry Cairns `@garry-cairns`_
Garry Polley `@garrypolley`_ Garry Polley `@garrypolley`_
Gilbishkosma `@Gilbishkosma`_
Guilherme Guy `@guilherme1guy`_
Hamish Durkin `@durkode`_ Hamish Durkin `@durkode`_
Hana Quadara `@hanaquadara`_ Hana Quadara `@hanaquadara`_
Harry Moreno `@morenoh149`_ @morenoh149
Harry Percival `@hjwp`_ Harry Percival `@hjwp`_
Hendrik Schneider `@hendrikschneider`_ Hendrik Schneider `@hendrikschneider`_
Henrique G. G. Pereira `@ikkebr`_ Henrique G. G. Pereira `@ikkebr`_
Howie Zhao `@howiezhao`_
Ian Lee `@IanLee1521`_ Ian Lee `@IanLee1521`_
Irfan Ahmad `@erfaan`_ @erfaan Irfan Ahmad `@erfaan`_ @erfaan
Isaac12x `@Isaac12x`_
Ivan Khomutov `@ikhomutov`_
James Williams `@jameswilliams1`_
Jan Van Bruggen `@jvanbrug`_ Jan Van Bruggen `@jvanbrug`_
Jelmer Draaijer `@foarsitter`_ Jelmer Draaijer `@foarsitter`_
Jerome Caisip `@jeromecaisip`_
Jens Nilsson `@phiberjenz`_ Jens Nilsson `@phiberjenz`_
Jerome Leclanche `@jleclanche`_ @Adys Jerome Leclanche `@jleclanche`_ @Adys
Jimmy Gitonga `@afrowave`_ @afrowave Jimmy Gitonga `@afrowave`_ @afrowave
@ -135,6 +153,7 @@ Listed in alphabetical order.
Keyvan Mosharraf `@keyvanm`_ Keyvan Mosharraf `@keyvanm`_
Krzysztof Szumny `@noisy`_ Krzysztof Szumny `@noisy`_
Krzysztof Żuraw `@krzysztofzuraw`_ Krzysztof Żuraw `@krzysztofzuraw`_
Leo won `@leollon`_
Leo Zhou `@glasslion`_ Leo Zhou `@glasslion`_
Leonardo Jimenez `@xpostudio4`_ Leonardo Jimenez `@xpostudio4`_
Lin Xianyi `@iynaix`_ Lin Xianyi `@iynaix`_
@ -155,11 +174,14 @@ Listed in alphabetical order.
Meghan Heintz `@dot2dotseurat`_ Meghan Heintz `@dot2dotseurat`_
Mesut Yılmaz `@myilmaz`_ Mesut Yılmaz `@myilmaz`_
Michael Gecht `@mimischi`_ @_mischi Michael Gecht `@mimischi`_ @_mischi
Michael Samoylov `@msamoylov`_
Min ho Kim `@minho42`_ Min ho Kim `@minho42`_
mozillazg `@mozillazg`_ mozillazg `@mozillazg`_
Nico Stefani `@nicolas471`_ @moby_dick91
Oleg Russkin `@rolep`_ Oleg Russkin `@rolep`_
Pablo `@oubiga`_ Pablo `@oubiga`_
Parbhat Puri `@parbhat`_ Parbhat Puri `@parbhat`_
Pawan Chaurasia `@rjsnh1522`_
Peter Bittner `@bittner`_ Peter Bittner `@bittner`_
Peter Coles `@mrcoles`_ Peter Coles `@mrcoles`_
Philipp Matthies `@canonnervio`_ Philipp Matthies `@canonnervio`_
@ -190,6 +212,7 @@ Listed in alphabetical order.
Tubo Shi `@Tubo`_ Tubo Shi `@Tubo`_
Umair Ashraf `@umrashrf`_ @fabumair Umair Ashraf `@umrashrf`_ @fabumair
Vadim Iskuchekov `@Egregors`_ @egregors Vadim Iskuchekov `@Egregors`_ @egregors
Vicente G. Reyes `@reyesvicente`_ @highcenburg
Vitaly Babiy Vitaly Babiy
Vivian Guillen `@viviangb`_ Vivian Guillen `@viviangb`_
Vlad Doster `@vladdoster`_ Vlad Doster `@vladdoster`_
@ -197,9 +220,11 @@ Listed in alphabetical order.
William Archinal `@archinal`_ William Archinal `@archinal`_
Xaver Y.R. Chen `@yrchen`_ @yrchen Xaver Y.R. Chen `@yrchen`_ @yrchen
Yaroslav Halchenko Yaroslav Halchenko
Yuchen Xie `@mapx`_
========================== ============================ ============== ========================== ============================ ==============
.. _@a7p: https://github.com/a7p .. _@a7p: https://github.com/a7p
.. _@2O4: https://github.com/2O4
.. _@ad-m: https://github.com/ad-m .. _@ad-m: https://github.com/ad-m
.. _@adammsteele: https://github.com/adammsteele .. _@adammsteele: https://github.com/adammsteele
.. _@aeikenberry: https://github.com/aeikenberry .. _@aeikenberry: https://github.com/aeikenberry
@ -211,15 +236,19 @@ Listed in alphabetical order.
.. _@andor-pierdelacabeza: https://github.com/andor-pierdelacabeza .. _@andor-pierdelacabeza: https://github.com/andor-pierdelacabeza
.. _@andresgz: https://github.com/andresgz .. _@andresgz: https://github.com/andresgz
.. _@antoniablair: https://github.com/antoniablair .. _@antoniablair: https://github.com/antoniablair
.. _@Andrew-Chen-Wang: https://github.com/Andrew-Chen-Wang
.. _@apirobot: https://github.com/apirobot .. _@apirobot: https://github.com/apirobot
.. _@archinal: https://github.com/archinal .. _@archinal: https://github.com/archinal
.. _@areski: https://github.com/areski .. _@areski: https://github.com/areski
.. _@arruda: https://github.com/arruda .. _@arruda: https://github.com/arruda
.. _@bertdemiranda: https://github.com/bertdemiranda .. _@bertdemiranda: https://github.com/bertdemiranda
.. _@bittner: https://github.com/bittner .. _@bittner: https://github.com/bittner
.. _@blaxpy: https://github.com/blaxpy
.. _@bloodpet: https://github.com/bloodpet .. _@bloodpet: https://github.com/bloodpet
.. _@blopker: https://github.com/blopker .. _@blopker: https://github.com/blopker
.. _@bogdal: https://github.com/bogdal .. _@bogdal: https://github.com/bogdal
.. _@bolivierjr: https://github.com/bolivierjr
.. _@BoPeng: https://github.com/BoPeng
.. _@brentpayne: https://github.com/brentpayne .. _@brentpayne: https://github.com/brentpayne
.. _@btknu: https://github.com/btknu .. _@btknu: https://github.com/btknu
.. _@burhan: https://github.com/burhan .. _@burhan: https://github.com/burhan
@ -227,6 +256,7 @@ Listed in alphabetical order.
.. _@c-rhodes: https://github.com/c-rhodes .. _@c-rhodes: https://github.com/c-rhodes
.. _@caffodian: https://github.com/caffodian .. _@caffodian: https://github.com/caffodian
.. _@canonnervio: https://github.com/canonnervio .. _@canonnervio: https://github.com/canonnervio
.. _@caioariede: https://github.com/caioariede
.. _@carlmjohnson: https://github.com/carlmjohnson .. _@carlmjohnson: https://github.com/carlmjohnson
.. _@catherinedevlin: https://github.com/catherinedevlin .. _@catherinedevlin: https://github.com/catherinedevlin
.. _@ccurvey: https://github.com/ccurvey .. _@ccurvey: https://github.com/ccurvey
@ -237,10 +267,12 @@ Listed in alphabetical order.
.. _@chuckus: https://github.com/chuckus .. _@chuckus: https://github.com/chuckus
.. _@cmackenzie1: https://github.com/cmackenzie1 .. _@cmackenzie1: https://github.com/cmackenzie1
.. _@cmargieson: https://github.com/cmargieson .. _@cmargieson: https://github.com/cmargieson
.. _@cole: https://github.com/cole
.. _@Collederas: https://github.com/Collederas .. _@Collederas: https://github.com/Collederas
.. _@curtisstpierre: https://github.com/curtisstpierre .. _@curtisstpierre: https://github.com/curtisstpierre
.. _@dadokkio: https://github.com/dadokkio .. _@dadokkio: https://github.com/dadokkio
.. _@danihodovic: https://github.com/danihodovic .. _@danihodovic: https://github.com/danihodovic
.. _@danifus: https://github.com/danifus
.. _@davitovmasyan: https://github.com/davitovmasyan .. _@davitovmasyan: https://github.com/davitovmasyan
.. _@ddiazpinto: https://github.com/ddiazpinto .. _@ddiazpinto: https://github.com/ddiazpinto
.. _@delneg: https://github.com/delneg .. _@delneg: https://github.com/delneg
@ -249,6 +281,7 @@ Listed in alphabetical order.
.. _@dhepper: https://github.com/dhepper .. _@dhepper: https://github.com/dhepper
.. _@dot2dotseurat: https://github.com/dot2dotseurat .. _@dot2dotseurat: https://github.com/dot2dotseurat
.. _@dsclementsen: https://github.com/dsclementsen .. _@dsclementsen: https://github.com/dsclementsen
.. _@guilherme1guy: https://github.com/guilherme1guy
.. _@durkode: https://github.com/durkode .. _@durkode: https://github.com/durkode
.. _@Egregors: https://github.com/Egregors .. _@Egregors: https://github.com/Egregors
.. _@epileptic-fish: https://gihub.com/epileptic-fish .. _@epileptic-fish: https://gihub.com/epileptic-fish
@ -261,6 +294,7 @@ Listed in alphabetical order.
.. _@foarsitter: https://github.com/foarsitter .. _@foarsitter: https://github.com/foarsitter
.. _@garry-cairns: https://github.com/garry-cairns .. _@garry-cairns: https://github.com/garry-cairns
.. _@garrypolley: https://github.com/garrypolley .. _@garrypolley: https://github.com/garrypolley
.. _@Gilbishkosma: https://github.com/Gilbishkosma
.. _@glasslion: https://github.com/glasslion .. _@glasslion: https://github.com/glasslion
.. _@goldhand: https://github.com/goldhand .. _@goldhand: https://github.com/goldhand
.. _@hackebrot: https://github.com/hackebrot .. _@hackebrot: https://github.com/hackebrot
@ -268,12 +302,17 @@ Listed in alphabetical order.
.. _@hanaquadara: https://github.com/hanaquadara .. _@hanaquadara: https://github.com/hanaquadara
.. _@hendrikschneider: https://github.com/hendrikschneider .. _@hendrikschneider: https://github.com/hendrikschneider
.. _@hjwp: https://github.com/hjwp .. _@hjwp: https://github.com/hjwp
.. _@howiezhao: https://github.com/howiezhao
.. _@IanLee1521: https://github.com/IanLee1521 .. _@IanLee1521: https://github.com/IanLee1521
.. _@ikhomutov: https://github.com/ikhomutov
.. _@jameswilliams1: https://github.com/jameswilliams1
.. _@ikkebr: https://github.com/ikkebr .. _@ikkebr: https://github.com/ikkebr
.. _@Isaac12x: https://github.com/Isaac12x
.. _@iynaix: https://github.com/iynaix .. _@iynaix: https://github.com/iynaix
.. _@jangeador: https://github.com/jangeador .. _@jangeador: https://github.com/jangeador
.. _@jazztpt: https://github.com/jazztpt .. _@jazztpt: https://github.com/jazztpt
.. _@jcass77: https://github.com/jcass77 .. _@jcass77: https://github.com/jcass77
.. _@jeromecaisip: https://github.com/jeromecaisip
.. _@jleclanche: https://github.com/jleclanche .. _@jleclanche: https://github.com/jleclanche
.. _@jules-ch: https://github.com/jules-ch .. _@jules-ch: https://github.com/jules-ch
.. _@juliocc: https://github.com/juliocc .. _@juliocc: https://github.com/juliocc
@ -286,7 +325,9 @@ Listed in alphabetical order.
.. _@keyvanm: https://github.com/keyvanm .. _@keyvanm: https://github.com/keyvanm
.. _@knitatoms: https://github.com/knitatoms .. _@knitatoms: https://github.com/knitatoms
.. _@krzysztofzuraw: https://github.com/krzysztofzuraw .. _@krzysztofzuraw: https://github.com/krzysztofzuraw
.. _@leollon: https://github.com/leollon
.. _@MathijsHoogland: https://github.com/MathijsHoogland .. _@MathijsHoogland: https://github.com/MathijsHoogland
.. _@mapx: https://github.com/mapx
.. _@mattayes: https://github.com/mattayes .. _@mattayes: https://github.com/mattayes
.. _@menzenski: https://github.com/menzenski .. _@menzenski: https://github.com/menzenski
.. _@mfwarren: https://github.com/mfwarren .. _@mfwarren: https://github.com/mfwarren
@ -295,24 +336,30 @@ Listed in alphabetical order.
.. _@minho42: https://github.com/minho42 .. _@minho42: https://github.com/minho42
.. _@mjsisley: https://github.com/mjsisley .. _@mjsisley: https://github.com/mjsisley
.. _@mknapper1: https://github.com/mknapper1 .. _@mknapper1: https://github.com/mknapper1
.. _@morenoh149: https://github.com/morenoh149
.. _@mostaszewski: https://github.com/mostaszewski .. _@mostaszewski: https://github.com/mostaszewski
.. _@mozillazg: https://github.com/mozillazg .. _@mozillazg: https://github.com/mozillazg
.. _@mrcoles: https://github.com/mrcoles .. _@mrcoles: https://github.com/mrcoles
.. _@msaizar: https://github.com/msaizar .. _@msaizar: https://github.com/msaizar
.. _@msamoylov: https://github.com/msamoylov
.. _@myilmaz: https://github.com/myilmaz .. _@myilmaz: https://github.com/myilmaz
.. _@nicolas471: https://github.com/nicolas471
.. _@noisy: https://github.com/noisy .. _@noisy: https://github.com/noisy
.. _@originell: https://github.com/originell .. _@originell: https://github.com/originell
.. _@oubiga: https://github.com/oubiga .. _@oubiga: https://github.com/oubiga
.. _@parbhat: https://github.com/parbhat .. _@parbhat: https://github.com/parbhat
.. _@rjsnh1522: https://github.com/rjsnh1522
.. _@pchiquet: https://github.com/pchiquet .. _@pchiquet: https://github.com/pchiquet
.. _@phiberjenz: https://github.com/phiberjenz .. _@phiberjenz: https://github.com/phiberjenz
.. _@purplediane: https://github.com/purplediane .. _@purplediane: https://github.com/purplediane
.. _@raonyguimaraes: https://github.com/raonyguimaraes .. _@raonyguimaraes: https://github.com/raonyguimaraes
.. _@reggieriser: https://github.com/reggieriser .. _@reggieriser: https://github.com/reggieriser
.. _@reyesvicente: https://github.com/reyesvicente
.. _@rm--: https://github.com/rm-- .. _@rm--: https://github.com/rm--
.. _@rolep: https://github.com/rolep .. _@rolep: https://github.com/rolep
.. _@romanosipenko: https://github.com/romanosipenko .. _@romanosipenko: https://github.com/romanosipenko
.. _@saschalalala: https://github.com/saschalalala .. _@saschalalala: https://github.com/saschalalala
.. _@scaramagus: https://github.com/scaramagus
.. _@shireenrao: https://github.com/shireenrao .. _@shireenrao: https://github.com/shireenrao
.. _@show0k: https://github.com/show0k .. _@show0k: https://github.com/show0k
.. _@shultz: https://github.com/shultz .. _@shultz: https://github.com/shultz
@ -320,6 +367,7 @@ Listed in alphabetical order.
.. _@sladinji: https://github.com/sladinji .. _@sladinji: https://github.com/sladinji
.. _@slafs: https://github.com/slafs .. _@slafs: https://github.com/slafs
.. _@ssteinerX: https://github.com/ssteinerx .. _@ssteinerX: https://github.com/ssteinerx
.. _@step21: https://github.com/step21
.. _@stepmr: https://github.com/stepmr .. _@stepmr: https://github.com/stepmr
.. _@suledev: https://github.com/suledev .. _@suledev: https://github.com/suledev
.. _@takkaria: https://github.com/takkaria .. _@takkaria: https://github.com/takkaria

View File

@ -9,8 +9,8 @@ Cookiecutter Django
:target: https://pyup.io/repos/github/pydanny/cookiecutter-django/ :target: https://pyup.io/repos/github/pydanny/cookiecutter-django/
:alt: Updates :alt: Updates
.. image:: https://badges.gitter.im/Join Chat.svg .. image:: https://img.shields.io/badge/cookiecutter-Join%20on%20Slack-green?style=flat&logo=slack
:target: https://gitter.im/pydanny/cookiecutter-django?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge :target: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U
.. image:: https://www.codetriage.com/pydanny/cookiecutter-django/badges/users.svg .. image:: https://www.codetriage.com/pydanny/cookiecutter-django/badges/users.svg
:target: https://www.codetriage.com/pydanny/cookiecutter-django :target: https://www.codetriage.com/pydanny/cookiecutter-django
@ -37,7 +37,7 @@ Features
--------- ---------
* For Django 2.2 * For Django 2.2
* Works with Python 3.6 * Works with Python 3.7
* Renders Django projects with 100% starting test coverage * Renders Django projects with 100% starting test coverage
* Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available) * Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available)
* 12-Factor_ based settings via django-environ_ * 12-Factor_ based settings via django-environ_
@ -46,13 +46,14 @@ Features
* Registration via django-allauth_ * Registration via django-allauth_
* Comes with custom user model ready to go * Comes with custom user model ready to go
* Optional custom static build using Gulp and livereload * Optional custom static build using Gulp and livereload
* Send emails via Anymail_ (using Mailgun_ by default, but switchable) * Send emails via Anymail_ (using Mailgun_ by default or Amazon SES if AWS is selected cloud provider, but switchable)
* Media storage using Amazon S3 or Google Cloud Storage * Media storage using Amazon S3 or Google Cloud Storage
* Docker support using docker-compose_ for development and production (using Traefik_ with LetsEncrypt_ support) * Docker support using docker-compose_ for development and production (using Traefik_ with LetsEncrypt_ support)
* Procfile_ for deploying to Heroku * Procfile_ for deploying to Heroku
* Instructions for deploying to PythonAnywhere_ * Instructions for deploying to PythonAnywhere_
* Run tests with unittest or pytest * Run tests with unittest or pytest
* Customizable PostgreSQL version * Customizable PostgreSQL version
* Default integration with pre-commit_ for identifying simple issues before submission to code review
.. _`maintained Foundation fork`: https://github.com/Parbhat/cookiecutter-django-foundation .. _`maintained Foundation fork`: https://github.com/Parbhat/cookiecutter-django-foundation
@ -84,6 +85,7 @@ Optional Integrations
.. _PythonAnywhere: https://www.pythonanywhere.com/ .. _PythonAnywhere: https://www.pythonanywhere.com/
.. _Traefik: https://traefik.io/ .. _Traefik: https://traefik.io/
.. _LetsEncrypt: https://letsencrypt.org/ .. _LetsEncrypt: https://letsencrypt.org/
.. _pre-commit: https://github.com/pre-commit/pre-commit
Constraints Constraints
----------- -----------
@ -103,16 +105,16 @@ This project is run by volunteers. Please support them in their efforts to maint
Projects that provide financial support to the maintainers: Projects that provide financial support to the maintainers:
Two Scoops of Django 1.11 Django Crash Course
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
.. image:: https://cdn.shopify.com/s/files/1/0304/6901/products/tsd-111-alpha_medium.jpg?v=1499531513 .. image:: https://cdn.shopify.com/s/files/1/0304/6901/files/Django-Crash-Course-300x436.jpg
:name: Two Scoops of Django 1.11 Cover :name: Django Crash Course: Covers Django 3.0 and Python 3.8
:align: center :align: center
:alt: Two Scoops of Django :alt: Django Crash Course
:target: http://twoscoopspress.com/products/two-scoops-of-django-1-11 :target: https://www.roygreenfeld.com/products/django-crash-course
Two Scoops of Django is the best dessert-themed Django reference in the universe Django Crash Course for Django 3.0 and Python 3.8 is the best cheese-themed Django reference in the universe!
pyup pyup
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
@ -133,7 +135,7 @@ and then editing the results to include your name, email, and various configurat
First, get Cookiecutter. Trust me, it's awesome:: First, get Cookiecutter. Trust me, it's awesome::
$ pip install "cookiecutter>=1.4.0" $ pip install "cookiecutter>=1.7.0"
Now run it against this repo:: Now run it against this repo::
@ -224,11 +226,11 @@ Community
* Have questions? **Before you ask questions anywhere else**, please post your question on `Stack Overflow`_ under the *cookiecutter-django* tag. We check there periodically for questions. * Have questions? **Before you ask questions anywhere else**, please post your question on `Stack Overflow`_ under the *cookiecutter-django* tag. We check there periodically for questions.
* If you think you found a bug or want to request a feature, please open an issue_. * If you think you found a bug or want to request a feature, please open an issue_.
* For anything else, you can chat with us on `Gitter`_. * For anything else, you can chat with us on `Slack`_.
.. _`Stack Overflow`: http://stackoverflow.com/questions/tagged/cookiecutter-django .. _`Stack Overflow`: http://stackoverflow.com/questions/tagged/cookiecutter-django
.. _`issue`: https://github.com/pydanny/cookiecutter-django/issues .. _`issue`: https://github.com/pydanny/cookiecutter-django/issues
.. _`Gitter`: https://gitter.im/pydanny/cookiecutter-django?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge .. _`Slack`: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U
For Readers of Two Scoops of Django For Readers of Two Scoops of Django
-------------------------------------------- --------------------------------------------

View File

@ -33,6 +33,18 @@
"GCP", "GCP",
"None" "None"
], ],
"mail_service": [
"Mailgun",
"Amazon SES",
"Mailjet",
"Mandrill",
"Postmark",
"Sendgrid",
"SendinBlue",
"SparkPost",
"Other SMTP"
],
"use_drf": "n",
"custom_bootstrap_compilation": "n", "custom_bootstrap_compilation": "n",
"use_compressor": "n", "use_compressor": "n",
"use_celery": "n", "use_celery": "n",
@ -40,7 +52,11 @@
"use_sentry": "n", "use_sentry": "n",
"use_whitenoise": "n", "use_whitenoise": "n",
"use_heroku": "n", "use_heroku": "n",
"use_travisci": "n", "ci_tool": [
"None",
"Travis",
"Gitlab"
],
"keep_local_envs_in_vcs": "y", "keep_local_envs_in_vcs": "y",
"debug": "n" "debug": "n"

View File

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

View File

@ -25,7 +25,9 @@ Provided you have opted for Celery (via setting ``use_celery`` to ``y``) there a
* ``celeryworker`` running a Celery worker process; * ``celeryworker`` running a Celery worker process;
* ``celerybeat`` running a Celery beat process; * ``celerybeat`` running a Celery beat process;
* ``flower`` running Flower_ (for more info, check out :ref:`CeleryFlower` instructions for local environment). * ``flower`` running Flower_.
The ``flower`` service is served by Traefik over HTTPS, through the port ``5555``. For more information about Flower and its login credentials, check out :ref:`CeleryFlower` instructions for local environment.
.. _`Flower`: https://github.com/mher/flower .. _`Flower`: https://github.com/mher/flower
@ -152,6 +154,7 @@ If you are using ``supervisor``, you can use this file as a starting point::
Move it to ``/etc/supervisor/conf.d/{{cookiecutter.project_slug}}.conf`` and run:: Move it to ``/etc/supervisor/conf.d/{{cookiecutter.project_slug}}.conf`` and run::
supervisorctl reread supervisorctl reread
supervisorctl update
supervisorctl start {{cookiecutter.project_slug}} supervisorctl start {{cookiecutter.project_slug}}
For status check, run:: For status check, run::

View File

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

45
docs/document.rst Normal file
View File

@ -0,0 +1,45 @@
.. _document:
Document
=========
This project uses Sphinx_ documentation generator.
After you have set up to `develop locally`_, run the following commands to generate the HTML documentation: ::
$ sphinx-build docs/ docs/_build/html/
If you set up your project to `develop locally with docker`_, run the following command: ::
$ docker-compose -f local.yml run --rm django sphinx-build docs/ docs/_build/html/
Generate API documentation
----------------------------
Sphinx can automatically generate documentation from docstrings, to enable this feature, follow these steps:
1. Add Sphinx extension in ``docs/conf.py`` file, like below: ::
extensions = [
'sphinx.ext.autodoc',
]
2. Uncomment the following lines in the ``docs/conf.py`` file: ::
# import django
# sys.path.insert(0, os.path.abspath('..'))
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
# django.setup()
3. Run the following command: ::
$ sphinx-apidoc -f -o ./docs/modules/ ./tpub/ migrations/*
If you set up your project to `develop locally with docker`_, run the following command: ::
$ docker-compose -f local.yml run --rm django sphinx-apidoc -f -o ./docs/modules ./tpub/ migrations/*
4. Regenerate HTML documentation as written above.
.. _Sphinx: https://www.sphinx-doc.org/en/master/index.html
.. _develop locally: ./developing-locally.html
.. _develop locally with docker: ./developing-locally-docker.html

View File

@ -18,6 +18,7 @@ Contents:
settings settings
linters linters
testing testing
document
deployment-on-pythonanywhere deployment-on-pythonanywhere
deployment-on-heroku deployment-on-heroku
deployment-with-docker deployment-with-docker

View File

@ -70,6 +70,22 @@ cloud_provider:
Note that if you choose no cloud provider, media files won't work. Note that if you choose no cloud provider, media files won't work.
mail_service:
Select an email service that Django-Anymail provides
1. Amazon SES_
2. Mailgun_
3. Mailjet_
4. Mandrill_
5. Postmark_
6. SendGrid_
7. SendinBlue_
8. SparkPost_
9. Plain/Vanilla Django-Anymail_
use_drf:
Indicates whether the project should be configured to use `Django Rest Framework`_.
custom_bootstrap_compilation: custom_bootstrap_compilation:
Indicates whether the project should support Bootstrap recompilation Indicates whether the project should support Bootstrap recompilation
via the selected JavaScript task runner's task. This can be useful via the selected JavaScript task runner's task. This can be useful
@ -94,8 +110,12 @@ use_heroku:
Indicates whether the project should be configured so as to be deployable Indicates whether the project should be configured so as to be deployable
to Heroku_. to Heroku_.
use_travisci: ci_tool:
Indicates whether the project should be configured to use `Travis CI`_. Select a CI tool for running tests. The choices are:
1. None
2. Travis_
3. Gitlab_
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
@ -125,6 +145,18 @@ debug:
.. _AWS: https://aws.amazon.com/s3/ .. _AWS: https://aws.amazon.com/s3/
.. _GCP: https://cloud.google.com/storage/ .. _GCP: https://cloud.google.com/storage/
.. _SES: https://aws.amazon.com/ses/
.. _Mailgun: https://www.mailgun.com
.. _Mailjet: https://www.mailjet.com
.. _Mandrill: http://mandrill.com
.. _Postmark: https://postmarkapp.com
.. _SendGrid: https://sendgrid.com
.. _SendinBlue: https://www.sendinblue.com
.. _SparkPost: https://www.sparkpost.com
.. _Django-Anymail: https://anymail.readthedocs.io/en/stable/
.. _Django Rest Framework: https://github.com/encode/django-rest-framework/
.. _Django Compressor: https://github.com/django-compressor/django-compressor .. _Django Compressor: https://github.com/django-compressor/django-compressor
.. _Celery: https://github.com/celery/celery .. _Celery: https://github.com/celery/celery
@ -138,3 +170,6 @@ debug:
.. _Heroku: https://github.com/heroku/heroku-buildpack-python .. _Heroku: https://github.com/heroku/heroku-buildpack-python
.. _Travis CI: https://travis-ci.org/ .. _Travis CI: https://travis-ci.org/
.. _GitLab CI: https://docs.gitlab.com/ee/ci/

View File

@ -52,6 +52,21 @@ DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a
MAILGUN_API_KEY MAILGUN_API_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" MAILGUN_API_URL n/a n/a "https://api.mailgun.net/v3"
MAILJET_API_KEY MAILJET_API_KEY n/a raises error
MAILJET_SECRET_KEY MAILJET_SECRET_KEY n/a raises error
MAILJET_API_URL n/a n/a "https://api.mailjet.com/v3"
MANDRILL_API_KEY MANDRILL_API_KEY n/a raises error
MANDRILL_API_URL n/a n/a "https://mandrillapp.com/api/1.0"
POSTMARK_SERVER_TOKEN POSTMARK_SERVER_TOKEN n/a raises error
POSTMARK_API_URL n/a n/a "https://api.postmarkapp.com/"
SENDGRID_API_KEY SENDGRID_API_KEY n/a raises error
SENDGRID_GENERATE_MESSAGE_ID True n/a raises error
SENDGRID_MERGE_FIELD_FORMAT None n/a raises error
SENDGRID_API_URL n/a n/a "https://api.sendgrid.com/v3/"
SENDINBLUE_API_KEY SENDINBLUE_API_KEY n/a raises error
SENDINBLUE_API_URL n/a n/a "https://api.sendinblue.com/v3/"
SPARKPOST_API_KEY SPARKPOST_API_KEY n/a raises error
SPARKPOST_API_URL n/a n/a "https://api.sparkpost.com/api/v1"
======================================= =========================== ============================================== ====================================================================== ======================================= =========================== ============================================== ======================================================================
-------------------------- --------------------------

View File

@ -19,7 +19,7 @@ You will get a readout of the `users` app that has already been set up with test
If you set up your project to `develop locally with docker`_, run the following command: :: If you set up your project to `develop locally with docker`_, run the following command: ::
$ docker-compose -f local.yml run django pytest $ docker-compose -f local.yml run --rm django pytest
Targeting particular apps for testing in ``docker`` follows a similar pattern as previously shown above. Targeting particular apps for testing in ``docker`` follows a similar pattern as previously shown above.
@ -28,11 +28,11 @@ Coverage
You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: :: You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: ::
$ docker-compose -f local.yml run django coverage run -m pytest $ docker-compose -f local.yml run --rm django coverage run -m pytest
Once the tests are complete, in order to see the code coverage, run the following command: :: Once the tests are complete, in order to see the code coverage, run the following command: ::
$ docker-compose -f local.yml run django coverage report $ docker-compose -f local.yml run --rm django coverage report
.. note:: .. note::
@ -49,8 +49,8 @@ Once the tests are complete, in order to see the code coverage, run the followin
Since this is a fresh install, and there are no tests built using the Python `unittest`_ library yet, you should get feedback that says there were no tests carried out. Since this is a fresh install, and there are no tests built using the Python `unittest`_ library yet, you should get feedback that says there were no tests carried out.
.. _Pytest: https://docs.pytest.org/en/latest/example/simple.html .. _Pytest: https://docs.pytest.org/en/latest/example/simple.html
.. _develop locally: ../developing-locally.rst .. _develop locally: ./developing-locally.html
.. _develop locally with docker: ..../developing-locally-docker.rst .. _develop locally with docker: ./developing-locally-docker.html
.. _customize: https://docs.pytest.org/en/latest/customize.html .. _customize: https://docs.pytest.org/en/latest/customize.html
.. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest .. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest
.. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html .. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html

View File

@ -70,7 +70,7 @@ def remove_heroku_files():
for file_name in file_names: for file_name in file_names:
if ( if (
file_name == "requirements.txt" file_name == "requirements.txt"
and "{{ cookiecutter.use_travisci }}".lower() == "y" and "{{ cookiecutter.ci_tool }}".lower() == "travis"
): ):
# don't remove the file if we are using travisci but not using heroku # don't remove the file if we are using travisci but not using heroku
continue continue
@ -105,6 +105,10 @@ def remove_dottravisyml_file():
os.remove(".travis.yml") os.remove(".travis.yml")
def remove_dotgitlabciyml_file():
os.remove(".gitlab-ci.yml")
def append_to_project_gitignore(path): def append_to_project_gitignore(path):
gitignore_file_path = ".gitignore" gitignore_file_path = ".gitignore"
with open(gitignore_file_path, "a") as gitignore_file: with open(gitignore_file_path, "a") as gitignore_file:
@ -279,6 +283,15 @@ def remove_node_dockerfile():
shutil.rmtree(os.path.join("compose", "local", "node")) shutil.rmtree(os.path.join("compose", "local", "node"))
def remove_aws_dockerfile():
shutil.rmtree(os.path.join("compose", "production", "aws"))
def remove_drf_starter_files():
os.remove(os.path.join("config", "api_router.py"))
shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "users", "api"))
def main(): def main():
debug = "{{ cookiecutter.debug }}".lower() == "y" debug = "{{ cookiecutter.debug }}".lower() == "y"
@ -302,6 +315,12 @@ def main():
else: else:
remove_docker_files() remove_docker_files()
if (
"{{ cookiecutter.use_docker }}".lower() == "y"
and "{{ cookiecutter.cloud_provider}}".lower() != "aws"
):
remove_aws_dockerfile()
if "{{ cookiecutter.use_heroku }}".lower() == "n": if "{{ cookiecutter.use_heroku }}".lower() == "n":
remove_heroku_files() remove_heroku_files()
@ -339,9 +358,15 @@ def main():
if "{{ cookiecutter.use_docker }}".lower() == "y": if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_celery_compose_dirs() remove_celery_compose_dirs()
if "{{ cookiecutter.use_travisci }}".lower() == "n": if "{{ cookiecutter.ci_tool }}".lower() != "travis":
remove_dottravisyml_file() remove_dottravisyml_file()
if "{{ cookiecutter.ci_tool }}".lower() != "gitlab":
remove_dotgitlabciyml_file()
if "{{ cookiecutter.use_drf }}".lower() == "n":
remove_drf_starter_files()
print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR) print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)

View File

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

View File

@ -1,7 +1,4 @@
[pytest] [pytest]
addopts = -x --tb=short addopts = -v --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

@ -1,17 +1,17 @@
cookiecutter==1.6.0 cookiecutter==1.7.0
sh==1.12.14 sh==1.12.14
binaryornot==0.4.4 binaryornot==0.4.4
# Code quality # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
black==19.3b0 black==19.10b0
flake8==3.7.8 flake8==3.7.9
flake8-isort==2.9.0
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
tox==3.13.2 tox==3.14.5
pytest==5.0.1 pytest==5.4.1
pytest_cases==1.10.1 pytest-cookies==0.5.1
pytest-cookies==0.4.0 pytest-instafail==0.4.1.post0
pytest-xdist==1.29.0 pyyaml==5.3
pyyaml==5.1.1

View File

@ -40,7 +40,7 @@ setup(
"License :: OSI Approved :: BSD License", "License :: OSI Approved :: BSD License",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7",
"Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: CPython",
"Topic :: Software Development", "Topic :: Software Development",
], ],

View File

@ -3,7 +3,6 @@ import re
import pytest import pytest
from cookiecutter.exceptions import FailedHookException from cookiecutter.exceptions import FailedHookException
from pytest_cases import pytest_fixture_plus
import sh import sh
import yaml import yaml
from binaryornot.check import is_binary from binaryornot.check import is_binary
@ -11,9 +10,6 @@ from binaryornot.check import is_binary
PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}" PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}"
RE_OBJ = re.compile(PATTERN) RE_OBJ = re.compile(PATTERN)
YN_CHOICES = ["y", "n"]
CLOUD_CHOICES = ["AWS", "GCE", "None"]
@pytest.fixture @pytest.fixture
def context(): def context():
@ -29,36 +25,73 @@ def context():
} }
@pytest_fixture_plus SUPPORTED_COMBINATIONS = [
@pytest.mark.parametrize("windows", YN_CHOICES, ids=lambda yn: f"win:{yn}") {"open_source_license": "MIT"},
@pytest.mark.parametrize("use_docker", YN_CHOICES, ids=lambda yn: f"docker:{yn}") {"open_source_license": "BSD"},
@pytest.mark.parametrize("use_celery", YN_CHOICES, ids=lambda yn: f"celery:{yn}") {"open_source_license": "GPLv3"},
@pytest.mark.parametrize("use_mailhog", YN_CHOICES, ids=lambda yn: f"mailhog:{yn}") {"open_source_license": "Apache Software License 2.0"},
@pytest.mark.parametrize("use_sentry", YN_CHOICES, ids=lambda yn: f"sentry:{yn}") {"open_source_license": "Not open source"},
@pytest.mark.parametrize("use_compressor", YN_CHOICES, ids=lambda yn: f"cmpr:{yn}") {"windows": "y"},
@pytest.mark.parametrize("use_whitenoise", YN_CHOICES, ids=lambda yn: f"wnoise:{yn}") {"windows": "n"},
@pytest.mark.parametrize("cloud_provider", CLOUD_CHOICES, ids=lambda yn: f"cloud:{yn}") {"use_pycharm": "y"},
def context_combination( {"use_pycharm": "n"},
windows, {"use_docker": "y"},
use_docker, {"use_docker": "n"},
use_celery, {"postgresql_version": "11.3"},
use_mailhog, {"postgresql_version": "10.8"},
use_sentry, {"postgresql_version": "9.6"},
use_compressor, {"postgresql_version": "9.5"},
use_whitenoise, {"postgresql_version": "9.4"},
cloud_provider, {"cloud_provider": "AWS", "use_whitenoise": "y"},
): {"cloud_provider": "AWS", "use_whitenoise": "n"},
"""Fixture that parametrize the function where it's used.""" {"cloud_provider": "GCP", "use_whitenoise": "y"},
return { {"cloud_provider": "GCP", "use_whitenoise": "n"},
"windows": windows, {"cloud_provider": "None", "use_whitenoise": "y"},
"use_docker": use_docker, # Note: cloud_provider=None AND use_whitenoise=n is not supported
"use_compressor": use_compressor, {"mail_service": "Mailgun"},
"use_celery": use_celery, {"mail_service": "Amazon SES"},
"use_mailhog": use_mailhog, {"mail_service": "Mailjet"},
"use_sentry": use_sentry, {"mail_service": "Mandrill"},
"use_whitenoise": use_whitenoise, {"mail_service": "Postmark"},
"cloud_provider": cloud_provider, {"mail_service": "Sendgrid"},
} {"mail_service": "SendinBlue"},
{"mail_service": "SparkPost"},
{"mail_service": "Other SMTP"},
{"use_drf": "y"},
{"use_drf": "n"},
{"js_task_runner": "None"},
{"js_task_runner": "Gulp"},
{"custom_bootstrap_compilation": "y"},
{"custom_bootstrap_compilation": "n"},
{"use_compressor": "y"},
{"use_compressor": "n"},
{"use_celery": "y"},
{"use_celery": "n"},
{"use_mailhog": "y"},
{"use_mailhog": "n"},
{"use_sentry": "y"},
{"use_sentry": "n"},
{"use_whitenoise": "y"},
{"use_whitenoise": "n"},
{"use_heroku": "y"},
{"use_heroku": "n"},
{"ci_tool": "None"},
{"ci_tool": "Travis"},
{"ci_tool": "Gitlab"},
{"keep_local_envs_in_vcs": "y"},
{"keep_local_envs_in_vcs": "n"},
{"debug": "y"},
{"debug": "n"},
]
UNSUPPORTED_COMBINATIONS = [
{"cloud_provider": "None", "use_whitenoise": "n"},
]
def _fixture_id(ctx):
"""Helper to get a user friendly test name from the parametrized context."""
return "-".join(f"{key}:{value}" for key, value in ctx.items())
def build_files_list(root_dir): def build_files_list(root_dir):
@ -71,9 +104,7 @@ def build_files_list(root_dir):
def check_paths(paths): def check_paths(paths):
"""Method to check all paths have correct substitutions, """Method to check all paths have correct substitutions."""
used by other tests cases
"""
# Assert that no match is found in any of the files # Assert that no match is found in any of the files
for path in paths: for path in paths:
if is_binary(path): if is_binary(path):
@ -85,13 +116,10 @@ def check_paths(paths):
assert match is None, msg.format(path) assert match is None, msg.format(path)
def test_project_generation(cookies, context, context_combination): @pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
""" def test_project_generation(cookies, context, context_override):
Test that project is generated and fully rendered. """Test that project is generated and fully rendered."""
result = cookies.bake(extra_context={**context, **context_override})
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context={**context, **context_combination})
assert result.exit_code == 0 assert result.exit_code == 0
assert result.exception is None assert result.exception is None
assert result.project.basename == context["project_slug"] assert result.project.basename == context["project_slug"]
@ -102,38 +130,30 @@ def test_project_generation(cookies, context, context_combination):
check_paths(paths) check_paths(paths)
@pytest.mark.flake8 @pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
def test_flake8_passes(cookies, context_combination): def test_flake8_passes(cookies, context_override):
""" """Generated project should pass flake8."""
Generated project should pass flake8. result = cookies.bake(extra_context=context_override)
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context=context_combination)
try: try:
sh.flake8(str(result.project)) sh.flake8(str(result.project))
except sh.ErrorReturnCode as e: except sh.ErrorReturnCode as e:
pytest.fail(e) pytest.fail(e.stdout.decode())
@pytest.mark.black @pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
def test_black_passes(cookies, context_combination): def test_black_passes(cookies, context_override):
""" """Generated project should pass black."""
Generated project should pass black. result = cookies.bake(extra_context=context_override)
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:
pytest.fail(e) pytest.fail(e.stdout.decode())
def test_travis_invokes_pytest(cookies, context): def test_travis_invokes_pytest(cookies, context):
context.update({"use_travisci": "y"}) context.update({"ci_tool": "Travis"})
result = cookies.bake(extra_context=context) result = cookies.bake(extra_context=context)
assert result.exit_code == 0 assert result.exit_code == 0
@ -148,6 +168,24 @@ def test_travis_invokes_pytest(cookies, context):
pytest.fail(e) pytest.fail(e)
def test_gitlab_invokes_flake8_and_pytest(cookies, context):
context.update({"ci_tool": "Gitlab"})
result = cookies.bake(extra_context=context)
assert result.exit_code == 0
assert result.exception is None
assert result.project.basename == context["project_slug"]
assert result.project.isdir()
with open(f"{result.project}/.gitlab-ci.yml", "r") as gitlab_yml:
try:
gitlab_config = yaml.load(gitlab_yml)
assert gitlab_config["flake8"]["script"] == ["flake8"]
assert gitlab_config["pytest"]["script"] == ["pytest"]
except yaml.YAMLError as e:
pytest.fail(e)
@pytest.mark.parametrize("slug", ["project slug", "Project_Slug"]) @pytest.mark.parametrize("slug", ["project slug", "Project_Slug"])
def test_invalid_slug(cookies, context, slug): def test_invalid_slug(cookies, context, slug):
"""Invalid slug should failed pre-generation hook.""" """Invalid slug should failed pre-generation hook."""
@ -157,3 +195,13 @@ def test_invalid_slug(cookies, context, slug):
assert result.exit_code != 0 assert result.exit_code != 0
assert isinstance(result.exception, FailedHookException) assert isinstance(result.exception, FailedHookException)
@pytest.mark.parametrize("invalid_context", UNSUPPORTED_COMBINATIONS)
def test_error_if_incompatible(cookies, context, invalid_context):
"""It should not generate project an incompatible combination is selected."""
context.update(invalid_context)
result = cookies.bake(extra_context=context)
assert result.exit_code != 0
assert isinstance(result.exception, FailedHookException)

12
tox.ini
View File

@ -1,18 +1,10 @@
[tox] [tox]
skipsdist = true skipsdist = true
envlist = py36,flake8,black,black-template envlist = py37,black-template
[testenv] [testenv]
deps = -rrequirements.txt deps = -rrequirements.txt
commands = pytest -m "not flake8" -m "not black" {posargs:./tests} commands = pytest {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] [testenv:black-template]
deps = black deps = black

View File

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

View File

@ -13,9 +13,26 @@ DJANGO_SECURE_SSL_REDIRECT=False
# Email # Email
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
MAILGUN_API_KEY=
DJANGO_SERVER_EMAIL= DJANGO_SERVER_EMAIL=
{% if cookiecutter.mail_service == 'Mailgun' %}
MAILGUN_API_KEY=
MAILGUN_DOMAIN= MAILGUN_DOMAIN=
{% elif cookiecutter.mail_service == 'Mailjet' %}
MAILJET_API_KEY=
MAILJET_SECRET_KEY=
{% elif cookiecutter.mail_service == 'Mandrill' %}
MANDRILL_API_KEY=
{% elif cookiecutter.mail_service == 'Postmark' %}
POSTMARK_SERVER_TOKEN=
{% elif cookiecutter.mail_service == 'Sendgrid' %}
SENDGRID_API_KEY=
SENDGRID_GENERATE_MESSAGE_ID=True
SENDGRID_MERGE_FIELD_FORMAT=None
{% elif cookiecutter.mail_service == 'SendinBlue' %}
SENDINBLUE_API_KEY=
{% elif cookiecutter.mail_service == 'SparkPost' %}
SPARKPOST_API_KEY=
{% endif %}
{% if cookiecutter.cloud_provider == 'AWS' %} {% if cookiecutter.cloud_provider == 'AWS' %}
# AWS # AWS
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -31,11 +48,7 @@ DJANGO_GCP_STORAGE_BUCKET_NAME=
# django-allauth # django-allauth
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
DJANGO_ACCOUNT_ALLOW_REGISTRATION=True DJANGO_ACCOUNT_ALLOW_REGISTRATION=True
{% if cookiecutter.use_compressor == 'y' %}
# django-compressor
# ------------------------------------------------------------------------------
COMPRESS_ENABLED=
{% endif %}
# Gunicorn # Gunicorn
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
WEB_CONCURRENCY=4 WEB_CONCURRENCY=4

View File

@ -0,0 +1,34 @@
stages:
- lint
- test
variables:
POSTGRES_USER: '{{ cookiecutter.project_slug }}'
POSTGRES_PASSWORD: ''
POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}'
POSTGRES_HOST_AUTH_METHOD: trust
flake8:
stage: lint
image: python:3.7-alpine
before_script:
- pip install -q flake8
script:
- flake8
pytest:
stage: test
image: python:3.7
tags:
- docker
services:
- postgres:11
variables:
DATABASE_URL: pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB
before_script:
- pip install -r requirements/local.txt
script:
- pytest

View File

@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_docker == 'n' %}
<component name="DjangoConsoleOptions"
custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;import os&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)"
module-name="{{ cookiecutter.project_slug }}" is-module-sdk="true">
</component>
{%- elif cookiecutter.use_celery == 'y' %}
<component name="DjangoConsoleOptions" <component name="DjangoConsoleOptions"
custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;import os&#10;os.environ.setdefault(&quot;DATABASE_URL&quot;,&quot;postgres://{}:{}@{}:{}/{}&quot;.format(os.environ['POSTGRES_USER'], os.environ['POSTGRES_PASSWORD'], os.environ['POSTGRES_HOST'], os.environ['POSTGRES_PORT'], os.environ['POSTGRES_DB']))&#10;os.environ.setdefault(&quot;CELERY_BROKER_URL&quot;, os.environ['REDIS_URL'])&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)" custom-start-script="import sys; print('Python %s on %s' % (sys.version, sys.platform))&#10;import django; print('Django %s' % django.get_version())&#10;import os&#10;os.environ.setdefault(&quot;DATABASE_URL&quot;,&quot;postgres://{}:{}@{}:{}/{}&quot;.format(os.environ['POSTGRES_USER'], os.environ['POSTGRES_PASSWORD'], os.environ['POSTGRES_HOST'], os.environ['POSTGRES_PORT'], os.environ['POSTGRES_DB']))&#10;os.environ.setdefault(&quot;CELERY_BROKER_URL&quot;, os.environ['REDIS_URL'])&#10;sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS])&#10;if 'setup' in dir(django): django.setup()&#10;import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)"
module-name="{{ cookiecutter.project_slug }}" is-module-sdk="true"> module-name="{{ cookiecutter.project_slug }}" is-module-sdk="true">

View File

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

View File

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

View File

@ -2,4 +2,5 @@ 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=config.celery_app --loglevel=info worker: celery worker --app=config.celery_app --loglevel=info
beat: celery beat --app=config.celery_app --loglevel=info
{%- endif %} {%- endif %}

View File

@ -1,19 +1,18 @@
FROM python:3.6-alpine FROM python:3.7-slim-buster
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1
RUN apk update \ RUN apt-get update \
# dependencies for building Python packages
&& apt-get install -y build-essential \
# psycopg2 dependencies # psycopg2 dependencies
&& apk add --virtual build-deps gcc python3-dev musl-dev \ && apt-get install -y libpq-dev \
&& apk add postgresql-dev \
# Pillow dependencies
&& apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
# CFFI dependencies
&& apk add libffi-dev py-cffi \
# Translations dependencies # Translations dependencies
&& apk add gettext \ && apt-get install -y gettext \
# https://docs.djangoproject.com/en/dev/ref/django-admin/#dbshell # cleaning up unused files
&& apk add postgresql-client && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& rm -rf /var/lib/apt/lists/*
# Requirements are installed here to ensure they will be cached. # Requirements are installed here to ensure they will be cached.
COPY ./requirements /requirements COPY ./requirements /requirements

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o nounset set -o nounset

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o nounset set -o nounset

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o nounset set -o nounset

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -9,21 +9,23 @@ RUN npm run build
# Python build stage # Python build stage
{%- endif %} {%- endif %}
FROM python:3.6-alpine FROM python:3.7-slim-buster
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
RUN apk update \ RUN apt-get update \
# dependencies for building Python packages
&& apt-get install -y build-essential \
# psycopg2 dependencies # psycopg2 dependencies
&& apk add --virtual build-deps gcc python3-dev musl-dev \ && apt-get install -y libpq-dev \
&& apk add postgresql-dev \ # Translations dependencies
# Pillow dependencies && apt-get install -y gettext \
&& apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \ # cleaning up unused files
# CFFI dependencies && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \
&& apk add libffi-dev py-cffi && rm -rf /var/lib/apt/lists/*
RUN addgroup -S django \ RUN addgroup --system django \
&& adduser -S -G django django && adduser --system --ingroup django django
# Requirements are installed here to ensure they will be cached. # Requirements are installed here to ensure they will be cached.
COPY ./requirements /requirements COPY ./requirements /requirements
@ -57,13 +59,11 @@ RUN chmod +x /start-flower
{%- endif %} {%- endif %}
{%- if cookiecutter.js_task_runner == 'Gulp' %} {%- if cookiecutter.js_task_runner == 'Gulp' %}
COPY --from=client-builder /app /app COPY --from=client-builder --chown=django:django /app /app
{% else %} {% else %}
COPY . /app COPY --chown=django:django . /app
{%- endif %} {%- endif %}
RUN chown -R django /app
USER django USER django
WORKDIR /app WORKDIR /app

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o nounset set -o nounset

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/bash
set -o errexit set -o errexit
set -o pipefail set -o pipefail
@ -6,4 +6,25 @@ set -o nounset
python /app/manage.py collectstatic --noinput python /app/manage.py collectstatic --noinput
{% if cookiecutter.use_whitenoise == 'y' and cookiecutter.use_compressor == 'y' %}
compress_enabled() {
python << END
import sys
from environ import Env
env = Env(COMPRESS_ENABLED=(bool, True))
if env('COMPRESS_ENABLED'):
sys.exit(0)
else:
sys.exit(1)
END
}
if compress_enabled; then
# NOTE this command will fail if django-compressor is disabled
python /app/manage.py compress
fi
{%- endif %}
/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app /usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app

View File

@ -1,5 +1,5 @@
FROM traefik:alpine FROM traefik:v2.0
RUN mkdir -p /etc/traefik/acme RUN mkdir -p /etc/traefik/acme
RUN touch /etc/traefik/acme/acme.json RUN touch /etc/traefik/acme/acme.json
RUN chmod 600 /etc/traefik/acme/acme.json RUN chmod 600 /etc/traefik/acme/acme.json
COPY ./compose/production/traefik/traefik.toml /etc/traefik COPY ./compose/production/traefik/traefik.yml /etc/traefik

View File

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

View File

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

View File

@ -0,0 +1,15 @@
from django.conf import settings
from rest_framework.routers import DefaultRouter, SimpleRouter
from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet
if settings.DEBUG:
router = DefaultRouter()
else:
router = SimpleRouter()
router.register("users", UserViewSet)
app_name = "api"
urlpatterns = router.urls

View File

@ -1,4 +1,5 @@
import os import os
from celery import Celery from celery import Celery
# set the default Django settings module for the 'celery' program. # set the default Django settings module for the 'celery' program.

View File

@ -68,16 +68,20 @@ DJANGO_APPS = [
"django.contrib.staticfiles", "django.contrib.staticfiles",
# "django.contrib.humanize", # Handy template tags # "django.contrib.humanize", # Handy template tags
"django.contrib.admin", "django.contrib.admin",
"django.forms",
] ]
THIRD_PARTY_APPS = [ THIRD_PARTY_APPS = [
"crispy_forms", "crispy_forms",
"allauth", "allauth",
"allauth.account", "allauth.account",
"allauth.socialaccount", "allauth.socialaccount",
"rest_framework",
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
"django_celery_beat", "django_celery_beat",
{%- endif %} {%- endif %}
{%- if cookiecutter.use_drf == "y" %}
"rest_framework",
"rest_framework.authtoken",
{%- endif %}
] ]
LOCAL_APPS = [ LOCAL_APPS = [
@ -131,12 +135,16 @@ AUTH_PASSWORD_VALIDATORS = [
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware # https://docs.djangoproject.com/en/dev/ref/settings/#middleware
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
{%- if cookiecutter.use_whitenoise == 'y' %}
"whitenoise.middleware.WhiteNoiseMiddleware",
{%- endif %}
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware", "django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.common.BrokenLinkEmailsMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
] ]
@ -187,10 +195,15 @@ TEMPLATES = [
"django.template.context_processors.static", "django.template.context_processors.static",
"django.template.context_processors.tz", "django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages", "django.contrib.messages.context_processors.messages",
"{{ cookiecutter.project_slug }}.utils.context_processors.settings_context",
], ],
}, },
} }
] ]
# https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer
FORM_RENDERER = "django.forms.renderers.TemplatesSetting"
# http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs # http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs
CRISPY_TEMPLATE_PACK = "bootstrap4" CRISPY_TEMPLATE_PACK = "bootstrap4"
@ -291,14 +304,24 @@ ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter" ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter"
# https://django-allauth.readthedocs.io/en/latest/configuration.html # https://django-allauth.readthedocs.io/en/latest/configuration.html
SOCIALACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter" SOCIALACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter"
{% if cookiecutter.use_compressor == 'y' -%} {% if cookiecutter.use_compressor == 'y' -%}
# django-compressor # django-compressor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://django-compressor.readthedocs.io/en/latest/quickstart/#installation # https://django-compressor.readthedocs.io/en/latest/quickstart/#installation
INSTALLED_APPS += ["compressor"] INSTALLED_APPS += ["compressor"]
STATICFILES_FINDERS += ["compressor.finders.CompressorFinder"] STATICFILES_FINDERS += ["compressor.finders.CompressorFinder"]
{%- endif %}
{% if cookiecutter.use_drf == "y" -%}
# django-reset-framework
# -------------------------------------------------------------------------------
# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.TokenAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
}
{%- endif %} {%- endif %}
# Your stuff... # Your stuff...
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -28,19 +28,27 @@ CACHES = {
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' -%} {% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' -%}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host # https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = env("EMAIL_HOST", default="mailhog") EMAIL_HOST = env("EMAIL_HOST", default="mailhog")
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = 1025
{%- elif cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' -%} {%- elif cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' -%}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host # https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = "localhost" EMAIL_HOST = "localhost"
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = 1025
{%- else -%} {%- else -%}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = env( EMAIL_BACKEND = env(
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
) )
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = "localhost"
{%- endif %} {%- endif %}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = 1025 {%- if cookiecutter.use_whitenoise == 'y' %}
# WhiteNoise
# ------------------------------------------------------------------------------
# http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development
INSTALLED_APPS = ["whitenoise.runserver_nostatic"] + INSTALLED_APPS # noqa F405
{% endif %}
# django-debug-toolbar # django-debug-toolbar
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -1,3 +1,4 @@
"""isort:skip_file"""
{% if cookiecutter.use_sentry == 'y' -%} {% if cookiecutter.use_sentry == 'y' -%}
import logging import logging
@ -92,7 +93,6 @@ AWS_DEFAULT_ACL = None
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None)
{% elif cookiecutter.cloud_provider == 'GCP' %} {% elif cookiecutter.cloud_provider == 'GCP' %}
DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME") GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME")
GS_DEFAULT_ACL = "publicRead" GS_DEFAULT_ACL = "publicRead"
{% endif -%} {% endif -%}
@ -105,8 +105,11 @@ GS_DEFAULT_ACL = "publicRead"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
{% elif cookiecutter.cloud_provider == 'AWS' -%} {% elif cookiecutter.cloud_provider == 'AWS' -%}
STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage" STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage"
COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy"
STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/" STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/"
{% elif cookiecutter.cloud_provider == 'GCP' -%} {% elif cookiecutter.cloud_provider == 'GCP' -%}
STATICFILES_STORAGE = "config.settings.production.StaticRootGoogleCloudStorage"
COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy"
STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/" STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/"
{% endif -%} {% endif -%}
@ -132,14 +135,27 @@ class MediaRootS3Boto3Storage(S3Boto3Storage):
DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage" DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage"
MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/" MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/"
{%- elif cookiecutter.cloud_provider == 'GCP' %} {%- elif cookiecutter.cloud_provider == 'GCP' %}
from storages.backends.gcloud import GoogleCloudStorage # noqa E402
class StaticRootGoogleCloudStorage(GoogleCloudStorage):
location = "static"
default_acl = "publicRead"
class MediaRootGoogleCloudStorage(GoogleCloudStorage):
location = "media"
file_overwrite = False
DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootGoogleCloudStorage"
MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
MEDIA_ROOT = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
{%- endif %} {%- endif %}
# TEMPLATES # TEMPLATES
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#templates # https://docs.djangoproject.com/en/dev/ref/settings/#templates
TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405 TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405
( (
"django.template.loaders.cached.Loader", "django.template.loaders.cached.Loader",
[ [
@ -167,41 +183,114 @@ EMAIL_SUBJECT_PREFIX = env(
# Django Admin URL regex. # Django Admin URL regex.
ADMIN_URL = env("DJANGO_ADMIN_URL") ADMIN_URL = env("DJANGO_ADMIN_URL")
# Anymail (Mailgun) # Anymail
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://anymail.readthedocs.io/en/stable/installation/#installing-anymail # https://anymail.readthedocs.io/en/stable/installation/#installing-anymail
INSTALLED_APPS += ["anymail"] # noqa F405 INSTALLED_APPS += ["anymail"] # noqa F405
EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
# https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference
{%- if cookiecutter.mail_service == 'Mailgun' %}
# https://anymail.readthedocs.io/en/stable/esps/mailgun/
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"), "MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"),
} }
{%- elif cookiecutter.mail_service == 'Amazon SES' %}
# https://anymail.readthedocs.io/en/stable/esps/amazon_ses/
EMAIL_BACKEND = "anymail.backends.amazon_ses.EmailBackend"
ANYMAIL = {}
{%- elif cookiecutter.mail_service == 'Mailjet' %}
# https://anymail.readthedocs.io/en/stable/esps/mailjet/
EMAIL_BACKEND = "anymail.backends.mailjet.EmailBackend"
ANYMAIL = {
"MAILJET_API_KEY": env("MAILJET_API_KEY"),
"MAILJET_SECRET_KEY": env("MAILJET_SECRET_KEY"),
"MAILJET_API_URL": env("MAILJET_API_URL", default="https://api.mailjet.com/v3"),
}
{%- elif cookiecutter.mail_service == 'Mandrill' %}
# https://anymail.readthedocs.io/en/stable/esps/mandrill/
EMAIL_BACKEND = "anymail.backends.mandrill.EmailBackend"
ANYMAIL = {
"MANDRILL_API_KEY": env("MANDRILL_API_KEY"),
"MANDRILL_API_URL": env(
"MANDRILL_API_URL", default="https://mandrillapp.com/api/1.0"
),
}
{%- elif cookiecutter.mail_service == 'Postmark' %}
# https://anymail.readthedocs.io/en/stable/esps/postmark/
EMAIL_BACKEND = "anymail.backends.postmark.EmailBackend"
ANYMAIL = {
"POSTMARK_SERVER_TOKEN": env("POSTMARK_SERVER_TOKEN"),
"POSTMARK_API_URL": env("POSTMARK_API_URL", default="https://api.postmarkapp.com/"),
}
{%- elif cookiecutter.mail_service == 'Sendgrid' %}
# https://anymail.readthedocs.io/en/stable/esps/sendgrid/
EMAIL_BACKEND = "anymail.backends.sendgrid.EmailBackend"
ANYMAIL = {
"SENDGRID_API_KEY": env("SENDGRID_API_KEY"),
"SENDGRID_GENERATE_MESSAGE_ID": env("SENDGRID_GENERATE_MESSAGE_ID"),
"SENDGRID_MERGE_FIELD_FORMAT": env("SENDGRID_MERGE_FIELD_FORMAT"),
"SENDGRID_API_URL": env("SENDGRID_API_URL", default="https://api.sendgrid.com/v3/"),
}
{%- elif cookiecutter.mail_service == 'SendinBlue' %}
# https://anymail.readthedocs.io/en/stable/esps/sendinblue/
EMAIL_BACKEND = "anymail.backends.sendinblue.EmailBackend"
ANYMAIL = {
"SENDINBLUE_API_KEY": env("SENDINBLUE_API_KEY"),
"SENDINBLUE_API_URL": env(
"SENDINBLUE_API_URL", default="https://api.sendinblue.com/v3/"
),
}
{%- elif cookiecutter.mail_service == 'SparkPost' %}
# https://anymail.readthedocs.io/en/stable/esps/sparkpost/
EMAIL_BACKEND = "anymail.backends.sparkpost.EmailBackend"
ANYMAIL = {
"SPARKPOST_API_KEY": env("SPARKPOST_API_KEY"),
"SPARKPOST_API_URL": env(
"SPARKPOST_API_URL", default="https://api.sparkpost.com/api/v1"
),
}
{%- elif cookiecutter.mail_service == 'Other SMTP' %}
# https://anymail.readthedocs.io/en/stable/esps
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
ANYMAIL = {}
{%- endif %}
{% if cookiecutter.use_whitenoise == 'y' -%} {% if cookiecutter.use_compressor == 'y' -%}
# WhiteNoise
# ------------------------------------------------------------------------------
# http://whitenoise.evans.io/en/latest/django.html#enable-whitenoise
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware") # noqa F405
{% endif %}
{%- if cookiecutter.use_compressor == 'y' -%}
# django-compressor # django-compressor
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED
COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True) 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
{%- if cookiecutter.cloud_provider == 'AWS' %}
COMPRESS_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" COMPRESS_STORAGE = "storages.backends.s3boto3.S3Boto3Storage"
{%- elif cookiecutter.cloud_provider == 'GCP' %}
COMPRESS_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
{%- elif cookiecutter.cloud_provider == 'None' %}
COMPRESS_STORAGE = "compressor.storage.GzipCompressorFileStorage"
{%- endif %}
# 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' or cookiecutter.cloud_provider == 'None' %} # noqa F405{% endif %} COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' or cookiecutter.cloud_provider == 'None' %} # noqa F405{% endif %}
{%- if cookiecutter.use_whitenoise == 'y' %}
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_OFFLINE
COMPRESS_OFFLINE = True # Offline compression is required when using Whitenoise
{%- endif %}
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_FILTERS
COMPRESS_FILTERS = {
"css": [
"compressor.filters.css_default.CssAbsoluteFilter",
"compressor.filters.cssmin.rCSSMinFilter",
],
"js": ["compressor.filters.jsmin.JSMinFilter"],
}
{% endif %} {% endif %}
{%- if cookiecutter.use_whitenoise == 'n' -%} {%- if cookiecutter.use_whitenoise == 'n' -%}
# Collectfast # Collectfast
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://github.com/antonagestam/collectfast#installation # https://github.com/antonagestam/collectfast#installation
INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa F405 INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa F405
AWS_PRELOAD_METADATA = True
{% endif %} {% endif %}
# LOGGING # LOGGING
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -7,8 +7,6 @@ from .base import env
# GENERAL # GENERAL
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# https://docs.djangoproject.com/en/dev/ref/settings/#debug
DEBUG = False
# https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
SECRET_KEY = env( SECRET_KEY = env(
"DJANGO_SECRET_KEY", "DJANGO_SECRET_KEY",
@ -34,7 +32,7 @@ PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
# TEMPLATES # TEMPLATES
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405 TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405
( (
"django.template.loaders.cached.Loader", "django.template.loaders.cached.Loader",
[ [

View File

@ -1,9 +1,12 @@
from django.conf import settings from django.conf import settings
from django.urls import include, path
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.views.generic import TemplateView from django.urls import include, path
from django.views import defaults as default_views from django.views import defaults as default_views
from django.views.generic import TemplateView
{%- if cookiecutter.use_drf == 'y' %}
from rest_framework.authtoken.views import obtain_auth_token
{%- endif %}
urlpatterns = [ urlpatterns = [
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
@ -17,6 +20,15 @@ urlpatterns = [
path("accounts/", include("allauth.urls")), path("accounts/", include("allauth.urls")),
# Your stuff: custom urls includes go here # Your stuff: custom urls includes go here
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
{% if cookiecutter.use_drf == 'y' -%}
# API URLS
urlpatterns += [
# API base url
path("api/", include("config.api_router")),
# DRF auth token
path("auth-token/", obtain_auth_token),
]
{%- endif %}
if settings.DEBUG: if settings.DEBUG:
# This allows the error pages to be debugged during development, just visit # This allows the error pages to be debugged during development, just visit

View File

@ -1,153 +1,20 @@
# Makefile for Sphinx documentation # Minimal makefile for Sphinx documentation
# #
# You can set these variables from the command line. # You can set these variables from the command line, and also
SPHINXOPTS = # from the environment for the first two.
SPHINXBUILD = sphinx-build SPHINXOPTS ?=
PAPER = SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build BUILDDIR = _build
# Internal variables. # Put it first so that "make" without argument is like "make help".
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help: help:
@echo "Please use \`make <target>' where <target> is one of" @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean: .PHONY: help Makefile
-rm -rf $(BUILDDIR)/*
html: # Catch-all target: route all unknown targets to Sphinx using the new
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
@echo %: Makefile
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html." @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/{{ cookiecutter.project_slug }}.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/{{ cookiecutter.project_slug }}.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/{{ cookiecutter.project_slug }}"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/{{ cookiecutter.project_slug }}"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@ -1,255 +1,55 @@
# {{ cookiecutter.project_name }} documentation build configuration file, created by # Configuration file for the Sphinx documentation builder.
# sphinx-quickstart.
# #
# This file is execfile()d with the current directory set to its containing dir. # This file only contains a selection of the most common options. For a full
# # list see the documentation:
# Note that not all possible configuration values are present in this # https://www.sphinx-doc.org/en/master/usage/configuration.html
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os # -- Path setup --------------------------------------------------------------
import sys
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.')) #
import os
import sys
# -- General configuration ----------------------------------------------------- # import django
# sys.path.insert(0, os.path.abspath('..'))
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
# django.setup()
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions # -- Project information -----------------------------------------------------
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
project = "{{ cookiecutter.project_name }}"
copyright = """{% now 'utc', '%Y' %}, {{ cookiecutter.author_name }}"""
author = "{{ cookiecutter.author_name }}"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [] extensions = []
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"] templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = ".rst"
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "{{ cookiecutter.project_name }}"
copyright = """{% now 'utc', '%Y' %}, {{ cookiecutter.author_name }}"""
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = "0.1"
# The full version, including alpha/beta/rc tags.
release = "0.1"
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
exclude_patterns = ["_build"] # This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# -- Options for HTML output --------------------------------------------------- # -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
html_theme = "default" #
html_theme = "alabaster"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"] html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = "{{ cookiecutter.project_slug }}doc"
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
(
"index",
"{{ cookiecutter.project_slug }}.tex",
"{{ cookiecutter.project_name }} Documentation",
"""{{ cookiecutter.author_name }}""",
"manual",
)
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(
"index",
"{{ cookiecutter.project_slug }}",
"{{ cookiecutter.project_name }} Documentation",
["""{{ cookiecutter.author_name }}"""],
1,
)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
"index",
"{{ cookiecutter.project_slug }}",
"{{ cookiecutter.project_name }} Documentation",
"""{{ cookiecutter.author_name }}""",
"{{ cookiecutter.project_name }}",
"""{{ cookiecutter.description }}""",
"Miscellaneous",
)
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'

View File

@ -3,17 +3,19 @@
You can adapt this file completely to your liking, but it should at least You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive. contain the root `toctree` directive.
{{ cookiecutter.project_name }} Project Documentation Welcome to {{ cookiecutter.project_name }}'s documentation!
==================================================================== ======================================================================
Table of Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
:caption: Contents:
pycharm/configuration
Indices & Tables
================ Indices and tables
==================
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`

View File

@ -1,190 +1,35 @@
@ECHO OFF @ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" ( if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build set SPHINXBUILD=sphinx-build
) )
set SOURCEDIR=.
set BUILDDIR=_build set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help if "%1" == "" goto help
if "%1" == "help" ( %SPHINXBUILD% >NUL 2>NUL
:help if errorlevel 9009 (
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo. echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
goto end echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
) )
if "%1" == "dirhtml" ( %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml goto end
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" ( :help
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\{{ cookiecutter.project_slug }}.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\{{ cookiecutter.project_slug }}.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
:end :end
popd

View File

@ -36,12 +36,18 @@ After few seconds, all *Run/Debug Configurations* should be ready to use.
**Things you can do with provided configuration**: **Things you can do with provided configuration**:
* run and debug python code * run and debug python code
.. image:: images/f1.png .. image:: images/f1.png
* run and debug tests * run and debug tests
.. image:: images/f2.png .. image:: images/f2.png
.. image:: images/f3.png .. image:: images/f3.png
* run and debug migrations or different django management commands * run and debug migrations or different django management commands
.. image:: images/f4.png .. image:: images/f4.png
* and many others.. * and many others..
Known issues Known issues

View File

@ -42,6 +42,9 @@ services:
ports: ports:
- "0.0.0.0:80:80" - "0.0.0.0:80:80"
- "0.0.0.0:443:443" - "0.0.0.0:443:443"
{%- if cookiecutter.use_celery == 'y' %}
- "0.0.0.0:5555:5555"
{%- endif %}
redis: redis:
image: redis:5.0 image: redis:5.0
@ -60,11 +63,11 @@ services:
flower: flower:
<<: *django <<: *django
image: {{ cookiecutter.project_slug }}_production_flower image: {{ cookiecutter.project_slug }}_production_flower
ports:
- "5555:5555"
command: /start-flower command: /start-flower
{%- endif %} {%- endif %}
{% if cookiecutter.cloud_provider == 'AWS' %}
awscli: awscli:
build: build:
context: . context: .
@ -73,3 +76,4 @@ services:
- ./.envs/.production/.django - ./.envs/.production/.django
volumes: volumes:
- production_postgres_data_backups:/backups - production_postgres_data_backups:/backups
{%- endif %}

View File

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

View File

@ -1,17 +1,17 @@
pytz==2019.1 # https://github.com/stub42/pytz pytz==2019.3 # https://github.com/stub42/pytz
python-slugify==3.0.2 # https://github.com/un33k/python-slugify python-slugify==4.0.0 # https://github.com/un33k/python-slugify
Pillow==6.1.0 # https://github.com/python-pillow/Pillow Pillow==7.0.0 # https://github.com/python-pillow/Pillow
{%- if cookiecutter.use_compressor == "y" %} {%- if cookiecutter.use_compressor == "y" %}
rcssmin==1.0.6{% if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin rcssmin==1.0.6{% if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin
{%- endif %} {%- endif %}
argon2-cffi==19.1.0 # https://github.com/hynek/argon2_cffi argon2-cffi==19.2.0 # https://github.com/hynek/argon2_cffi
{%- if cookiecutter.use_whitenoise == 'y' %} {%- if cookiecutter.use_whitenoise == 'y' %}
whitenoise==4.1.2 # https://github.com/evansd/whitenoise whitenoise==5.0.1 # https://github.com/evansd/whitenoise
{%- endif %} {%- endif %}
redis==3.2.1 # https://github.com/antirez/redis redis==3.4.1 # https://github.com/andymccurdy/redis-py
{%- if cookiecutter.use_celery == "y" %} {%- if cookiecutter.use_celery == "y" %}
celery==4.3.0 # pyup: < 5.0 # https://github.com/celery/celery celery==4.4.2 # pyup: < 5.0 # https://github.com/celery/celery
django-celery-beat==1.5.0 # https://github.com/celery/django-celery-beat django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
flower==0.9.3 # https://github.com/mher/flower flower==0.9.3 # https://github.com/mher/flower
{%- endif %} {%- endif %}
@ -19,16 +19,16 @@ flower==0.9.3 # https://github.com/mher/flower
# Django # Django
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
django==2.2.3 # pyup: < 3.0 # https://www.djangoproject.com/ django==2.2.11 # pyup: < 3.0 # https://www.djangoproject.com/
django-environ==0.4.5 # https://github.com/joke2k/django-environ django-environ==0.4.5 # https://github.com/joke2k/django-environ
django-model-utils==3.2.0 # https://github.com/jazzband/django-model-utils django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils
django-allauth==0.39.1 # https://github.com/pennersr/django-allauth django-allauth==0.41.0 # https://github.com/pennersr/django-allauth
django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms django-crispy-forms==1.9.0 # https://github.com/django-crispy-forms/django-crispy-forms
{%- if cookiecutter.use_compressor == "y" %} {%- if cookiecutter.use_compressor == "y" %}
django-compressor==2.3 # https://github.com/django-compressor/django-compressor django-compressor==2.4 # https://github.com/django-compressor/django-compressor
{%- endif %} {%- endif %}
django-redis==4.10.0 # https://github.com/niwinz/django-redis django-redis==4.11.0 # https://github.com/niwinz/django-redis
{%- if cookiecutter.use_drf == "y" %}
# Django REST Framework # Django REST Framework
djangorestframework==3.9.4 # https://github.com/encode/django-rest-framework djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework
coreapi==2.3.3 # https://github.com/core-api/python-client {%- endif %}

View File

@ -1,35 +1,38 @@
-r ./base.txt -r ./base.txt
Werkzeug==0.14.1 # pyup: < 0.15 # https://github.com/pallets/werkzeug Werkzeug==1.0.0 # https://github.com/pallets/werkzeug
ipdb==0.12 # https://github.com/gotcha/ipdb ipdb==0.13.2 # https://github.com/gotcha/ipdb
Sphinx==2.1.2 # https://github.com/sphinx-doc/sphinx Sphinx==2.4.4 # https://github.com/sphinx-doc/sphinx
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- else %} {%- else %}
psycopg2-binary==2.8.3 # https://github.com/psycopg/psycopg2 psycopg2-binary==2.8.4 # https://github.com/psycopg/psycopg2
{%- endif %} {%- endif %}
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
mypy==0.711 # https://github.com/python/mypy mypy==0.770 # https://github.com/python/mypy
pytest==5.0.1 # https://github.com/pytest-dev/pytest django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs
pytest==5.3.5 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar
# Code quality # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
flake8==3.7.8 # https://github.com/PyCQA/flake8 flake8==3.7.9 # https://github.com/PyCQA/flake8
coverage==4.5.3 # https://github.com/nedbat/coveragepy flake8-isort==2.9.0 # https://github.com/gforcada/flake8-isort
black==19.3b0 # https://github.com/ambv/black coverage==5.0.4 # https://github.com/nedbat/coveragepy
pylint-django==2.0.10 # https://github.com/PyCQA/pylint-django black==19.10b0 # https://github.com/ambv/black
pylint-django==2.0.14 # https://github.com/PyCQA/pylint-django
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery
{%- endif %} {%- endif %}
pre-commit==2.2.0 # https://github.com/pre-commit/pre-commit
# Django # Django
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy
django-debug-toolbar==2.0 # https://github.com/jazzband/django-debug-toolbar django-debug-toolbar==2.2 # https://github.com/jazzband/django-debug-toolbar
django-extensions==2.1.9 # https://github.com/django-extensions/django-extensions django-extensions==2.2.8 # https://github.com/django-extensions/django-extensions
django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin
pytest-django==3.5.1 # https://github.com/pytest-dev/pytest-django pytest-django==3.8.0 # https://github.com/pytest-dev/pytest-django

View File

@ -2,20 +2,38 @@
-r ./base.txt -r ./base.txt
gunicorn==19.9.0 # https://github.com/benoitc/gunicorn gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 psycopg2==2.8.4 --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==2.1.0 # https://github.com/antonagestam/collectfast
{%- endif %} {%- endif %}
{%- if cookiecutter.use_sentry == "y" %} {%- if cookiecutter.use_sentry == "y" %}
sentry-sdk==0.10.1 # https://github.com/getsentry/sentry-python sentry-sdk==0.14.2 # 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.9.1 # https://github.com/jschneier/django-storages
{%- elif cookiecutter.cloud_provider == 'GCP' %} {%- elif cookiecutter.cloud_provider == 'GCP' %}
django-storages[google]==1.7.1 # https://github.com/jschneier/django-storages django-storages[google]==1.9.1 # https://github.com/jschneier/django-storages
{%- endif %}
{%- if cookiecutter.mail_service == 'Mailgun' %}
django-anymail[mailgun]==7.0.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Amazon SES' %}
django-anymail[amazon_ses]==7.0.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Mailjet' %}
django-anymail[mailjet]==7.0.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Mandrill' %}
django-anymail[mandrill]==7.0.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Postmark' %}
django-anymail[postmark]==7.0.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Sendgrid' %}
django-anymail[sendgrid]==7.0.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'SendinBlue' %}
django-anymail[sendinblue]==7.0.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'SparkPost' %}
django-anymail[sparkpost]==7.0.0 # https://github.com/anymail/django-anymail
{%- elif cookiecutter.mail_service == 'Other SMTP' %}
django-anymail==7.0.0 # https://github.com/anymail/django-anymail
{%- endif %} {%- endif %}
django-anymail[mailgun]==6.1.0 # https://github.com/anymail/django-anymail

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import pytest import pytest
from django.conf import settings
from django.test import RequestFactory from django.test import RequestFactory
from {{ cookiecutter.project_slug }}.users.models import User
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
@ -11,7 +11,7 @@ def media_storage(settings, tmpdir):
@pytest.fixture @pytest.fixture
def user() -> settings.AUTH_USER_MODEL: def user() -> User:
return UserFactory() return UserFactory()

View File

@ -15,7 +15,7 @@
<form method="POST" action="."> <form method="POST" action=".">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<input type="submit" name="action" value="{% trans 'change password' %}"/> <input class="btn btn-primary" type="submit" name="action" value="{% trans 'change password' %}"/>
</form> </form>
{% else %} {% else %}
<p>{% trans 'Your password is now changed.' %}</p> <p>{% trans 'Your password is now changed.' %}</p>

View File

@ -11,7 +11,7 @@
<form method="POST" action="{% url 'account_set_password' %}" class="password_set"> <form method="POST" action="{% url 'account_set_password' %}" class="password_set">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<input type="submit" name="action" value="{% trans 'Set Password' %}"/> <input class="btn btn-primary" type="submit" name="action" value="{% trans 'Set Password' %}"/>
</form> </form>
{% endblock %} {% endblock %}
{% endraw %} {% endraw %}

View File

@ -80,7 +80,7 @@
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">{{ message }}</div> <div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">{{ message }}<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}

View File

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

View File

@ -1,17 +0,0 @@
{% raw %}{% extends "base.html" %}
{% load static i18n %}
{% block title %}Members{% endblock %}
{% block content %}
<div class="container">
<h2>Users</h2>
<div class="list-group">
{% for user in user_list %}
<a href="{% url 'users:detail' user.username %}" class="list-group-item">
<h4 class="list-group-item-heading">{{ user.username }}</h4>
</a>
{% endfor %}
</div>
</div>
{% endblock content %}{% endraw %}

View File

@ -0,0 +1,13 @@
from rest_framework import serializers
from {{ cookiecutter.project_slug }}.users.models import User
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["username", "email", "name", "url"]
extra_kwargs = {
"url": {"view_name": "api:user-detail", "lookup_field": "username"}
}

View File

@ -0,0 +1,24 @@
from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from .serializers import UserSerializer
User = get_user_model()
class UserViewSet(RetrieveModelMixin, ListModelMixin, UpdateModelMixin, GenericViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = "username"
def get_queryset(self, *args, **kwargs):
return self.queryset.filter(id=self.request.user.id)
@action(detail=False, methods=["GET"])
def me(self, request):
serializer = UserSerializer(request.user, context={"request": request})
return Response(status=status.HTTP_200_OK, data=serializer.data)

View File

@ -1,4 +1,4 @@
from django.contrib.auth import get_user_model, forms from django.contrib.auth import forms, get_user_model
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _

View File

@ -12,7 +12,10 @@ class UserFactory(DjangoModelFactory):
@post_generation @post_generation
def password(self, create: bool, extracted: Sequence[Any], **kwargs): def password(self, create: bool, extracted: Sequence[Any], **kwargs):
password = Faker( password = (
extracted
if extracted
else Faker(
"password", "password",
length=42, length=42,
special_chars=True, special_chars=True,
@ -20,6 +23,7 @@ class UserFactory(DjangoModelFactory):
upper_case=True, upper_case=True,
lower_case=True, lower_case=True,
).generate(extra_kwargs={}) ).generate(extra_kwargs={})
)
self.set_password(password) self.set_password(password)
class Meta: class Meta:

View File

@ -1,8 +1,9 @@
import pytest import pytest
from django.conf import settings
from {{ cookiecutter.project_slug }}.users.models import User
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
def test_user_get_absolute_url(user: settings.AUTH_USER_MODEL): def test_user_get_absolute_url(user: User):
assert user.get_absolute_url() == f"/users/{user.username}/" assert user.get_absolute_url() == f"/users/{user.username}/"

View File

@ -1,7 +1,6 @@
import pytest import pytest
from celery.result import EagerResult from celery.result import EagerResult
from {{ cookiecutter.project_slug }}.users.tasks import get_users_count from {{ cookiecutter.project_slug }}.users.tasks import get_users_count
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory

View File

@ -1,11 +1,12 @@
import pytest import pytest
from django.conf import settings from django.urls import resolve, reverse
from django.urls import reverse, resolve
from {{ cookiecutter.project_slug }}.users.models import User
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
def test_detail(user: settings.AUTH_USER_MODEL): def test_detail(user: User):
assert ( assert (
reverse("users:detail", kwargs={"username": user.username}) reverse("users:detail", kwargs={"username": user.username})
== f"/users/{user.username}/" == f"/users/{user.username}/"

View File

@ -1,7 +1,7 @@
import pytest import pytest
from django.conf import settings
from django.test import RequestFactory from django.test import RequestFactory
from {{ cookiecutter.project_slug }}.users.models import User
from {{ cookiecutter.project_slug }}.users.views import UserRedirectView, UserUpdateView from {{ cookiecutter.project_slug }}.users.views import UserRedirectView, UserUpdateView
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@ -16,9 +16,7 @@ class TestUserUpdateView:
https://github.com/pytest-dev/pytest-django/pull/258 https://github.com/pytest-dev/pytest-django/pull/258
""" """
def test_get_success_url( def test_get_success_url(self, user: User, request_factory: RequestFactory):
self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
):
view = UserUpdateView() view = UserUpdateView()
request = request_factory.get("/fake-url/") request = request_factory.get("/fake-url/")
request.user = user request.user = user
@ -27,9 +25,7 @@ class TestUserUpdateView:
assert view.get_success_url() == f"/users/{user.username}/" assert view.get_success_url() == f"/users/{user.username}/"
def test_get_object( def test_get_object(self, user: User, request_factory: RequestFactory):
self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
):
view = UserUpdateView() view = UserUpdateView()
request = request_factory.get("/fake-url/") request = request_factory.get("/fake-url/")
request.user = user request.user = user
@ -40,9 +36,7 @@ class TestUserUpdateView:
class TestUserRedirectView: class TestUserRedirectView:
def test_get_redirect_url( def test_get_redirect_url(self, user: User, request_factory: RequestFactory):
self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory
):
view = UserRedirectView() view = UserRedirectView()
request = request_factory.get("/fake-url") request = request_factory.get("/fake-url")
request.user = user request.user = user

View File

@ -1,9 +1,9 @@
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_detail_view,
user_redirect_view, user_redirect_view,
user_update_view, user_update_view,
user_detail_view,
) )
app_name = "users" app_name = "users"

View File

@ -1,6 +1,8 @@
from django.contrib import messages
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.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView, RedirectView, UpdateView from django.views.generic import DetailView, RedirectView, UpdateView
User = get_user_model() User = get_user_model()
@ -27,6 +29,12 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
def get_object(self): def get_object(self):
return User.objects.get(username=self.request.user.username) return User.objects.get(username=self.request.user.username)
def form_valid(self, form):
messages.add_message(
self.request, messages.INFO, _("Infos successfully updated")
)
return super().form_valid(form)
user_update_view = UserUpdateView.as_view() user_update_view = UserUpdateView.as_view()

View File

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