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
python: 3.6
python: 3.7
before_install:
- docker-compose -v
@ -14,11 +14,7 @@ before_install:
matrix:
include:
- name: Test results
script: tox -e py36
- name: Run flake8 on result
script: tox -e flake8
- name: Run black on result
script: tox -e black
script: tox -e py37
- name: Black template
script: tox -e black-template
- name: Basic Docker

View File

@ -1,6 +1,68 @@
# Change Log
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]
### 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
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::

View File

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

View File

@ -9,8 +9,8 @@ Cookiecutter Django
:target: https://pyup.io/repos/github/pydanny/cookiecutter-django/
:alt: Updates
.. image:: https://badges.gitter.im/Join Chat.svg
:target: https://gitter.im/pydanny/cookiecutter-django?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
.. image:: https://img.shields.io/badge/cookiecutter-Join%20on%20Slack-green?style=flat&logo=slack
:target: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U
.. image:: https://www.codetriage.com/pydanny/cookiecutter-django/badges/users.svg
:target: https://www.codetriage.com/pydanny/cookiecutter-django
@ -37,7 +37,7 @@ Features
---------
* For Django 2.2
* Works with Python 3.6
* Works with Python 3.7
* Renders Django projects with 100% starting test coverage
* Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available)
* 12-Factor_ based settings via django-environ_
@ -46,13 +46,14 @@ Features
* Registration via django-allauth_
* Comes with custom user model ready to go
* 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
* Docker support using docker-compose_ for development and production (using Traefik_ with LetsEncrypt_ support)
* Procfile_ for deploying to Heroku
* Instructions for deploying to PythonAnywhere_
* Run tests with unittest or pytest
* 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
@ -84,6 +85,7 @@ Optional Integrations
.. _PythonAnywhere: https://www.pythonanywhere.com/
.. _Traefik: https://traefik.io/
.. _LetsEncrypt: https://letsencrypt.org/
.. _pre-commit: https://github.com/pre-commit/pre-commit
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:
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
:name: Two Scoops of Django 1.11 Cover
.. image:: https://cdn.shopify.com/s/files/1/0304/6901/files/Django-Crash-Course-300x436.jpg
:name: Django Crash Course: Covers Django 3.0 and Python 3.8
:align: center
:alt: Two Scoops of Django
:target: http://twoscoopspress.com/products/two-scoops-of-django-1-11
:alt: Django Crash Course
: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
~~~~~~~~~~~~~~~~~~
@ -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::
$ pip install "cookiecutter>=1.4.0"
$ pip install "cookiecutter>=1.7.0"
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.
* 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
.. _`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
--------------------------------------------

View File

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

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;
* ``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
@ -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::
supervisorctl reread
supervisorctl update
supervisorctl start {{cookiecutter.project_slug}}
For status check, run::

View File

@ -9,7 +9,7 @@ Setting Up Development Environment
Make sure to have the following on your host:
* Python 3.6
* Python 3.7
* PostgreSQL_.
* Redis_, if using Celery
@ -17,7 +17,7 @@ First things first.
#. Create a virtualenv: ::
$ python3.6 -m venv <virtual env path>
$ python3.7 -m venv <virtual env path>
#. Activate the virtualenv you have just created: ::
@ -26,6 +26,12 @@ First things first.
#. Install development requirements: ::
$ 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_: ::

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
linters
testing
document
deployment-on-pythonanywhere
deployment-on-heroku
deployment-with-docker

View File

@ -70,6 +70,22 @@ cloud_provider:
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:
Indicates whether the project should support Bootstrap recompilation
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
to Heroku_.
use_travisci:
Indicates whether the project should be configured to use `Travis CI`_.
ci_tool:
Select a CI tool for running tests. The choices are:
1. None
2. Travis_
3. Gitlab_
keep_local_envs_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/
.. _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
.. _Celery: https://github.com/celery/celery
@ -138,3 +170,6 @@ debug:
.. _Heroku: https://github.com/heroku/heroku-buildpack-python
.. _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_DOMAIN MAILGUN_SENDER_DOMAIN n/a raises error
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: ::
$ 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.
@ -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: ::
$ 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: ::
$ docker-compose -f local.yml run django coverage report
$ docker-compose -f local.yml run --rm django coverage report
.. 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.
.. _Pytest: https://docs.pytest.org/en/latest/example/simple.html
.. _develop locally: ../developing-locally.rst
.. _develop locally with docker: ..../developing-locally-docker.rst
.. _develop locally: ./developing-locally.html
.. _develop locally with docker: ./developing-locally-docker.html
.. _customize: https://docs.pytest.org/en/latest/customize.html
.. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest
.. _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:
if (
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
continue
@ -105,6 +105,10 @@ def remove_dottravisyml_file():
os.remove(".travis.yml")
def remove_dotgitlabciyml_file():
os.remove(".gitlab-ci.yml")
def append_to_project_gitignore(path):
gitignore_file_path = ".gitignore"
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"))
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():
debug = "{{ cookiecutter.debug }}".lower() == "y"
@ -302,6 +315,12 @@ def main():
else:
remove_docker_files()
if (
"{{ cookiecutter.use_docker }}".lower() == "y"
and "{{ cookiecutter.cloud_provider}}".lower() != "aws"
):
remove_aws_dockerfile()
if "{{ cookiecutter.use_heroku }}".lower() == "n":
remove_heroku_files()
@ -339,9 +358,15 @@ def main():
if "{{ cookiecutter.use_docker }}".lower() == "y":
remove_celery_compose_dirs()
if "{{ cookiecutter.use_travisci }}".lower() == "n":
if "{{ cookiecutter.ci_tool }}".lower() != "travis":
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)

View File

@ -35,7 +35,7 @@ if "{{ cookiecutter.use_docker }}".lower() == "n":
if python_major_version == 2:
print(
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"])
while True:
@ -59,3 +59,12 @@ if "{{ cookiecutter.use_docker }}".lower() == "n":
)
+ 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]
addopts = -x --tb=short
addopts = -v --tb=short
python_paths = .
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/*
markers =
flake8: Run flake8 on all possible template combinations
black: Run black on all possible template combinations

View File

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

View File

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

View File

@ -3,7 +3,6 @@ import re
import pytest
from cookiecutter.exceptions import FailedHookException
from pytest_cases import pytest_fixture_plus
import sh
import yaml
from binaryornot.check import is_binary
@ -11,9 +10,6 @@ from binaryornot.check import is_binary
PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}"
RE_OBJ = re.compile(PATTERN)
YN_CHOICES = ["y", "n"]
CLOUD_CHOICES = ["AWS", "GCE", "None"]
@pytest.fixture
def context():
@ -29,36 +25,73 @@ def context():
}
@pytest_fixture_plus
@pytest.mark.parametrize("windows", YN_CHOICES, ids=lambda yn: f"win:{yn}")
@pytest.mark.parametrize("use_docker", YN_CHOICES, ids=lambda yn: f"docker:{yn}")
@pytest.mark.parametrize("use_celery", YN_CHOICES, ids=lambda yn: f"celery:{yn}")
@pytest.mark.parametrize("use_mailhog", YN_CHOICES, ids=lambda yn: f"mailhog:{yn}")
@pytest.mark.parametrize("use_sentry", YN_CHOICES, ids=lambda yn: f"sentry:{yn}")
@pytest.mark.parametrize("use_compressor", YN_CHOICES, ids=lambda yn: f"cmpr:{yn}")
@pytest.mark.parametrize("use_whitenoise", YN_CHOICES, ids=lambda yn: f"wnoise:{yn}")
@pytest.mark.parametrize("cloud_provider", CLOUD_CHOICES, ids=lambda yn: f"cloud:{yn}")
def context_combination(
windows,
use_docker,
use_celery,
use_mailhog,
use_sentry,
use_compressor,
use_whitenoise,
cloud_provider,
):
"""Fixture that parametrize the function where it's used."""
return {
"windows": windows,
"use_docker": use_docker,
"use_compressor": use_compressor,
"use_celery": use_celery,
"use_mailhog": use_mailhog,
"use_sentry": use_sentry,
"use_whitenoise": use_whitenoise,
"cloud_provider": cloud_provider,
}
SUPPORTED_COMBINATIONS = [
{"open_source_license": "MIT"},
{"open_source_license": "BSD"},
{"open_source_license": "GPLv3"},
{"open_source_license": "Apache Software License 2.0"},
{"open_source_license": "Not open source"},
{"windows": "y"},
{"windows": "n"},
{"use_pycharm": "y"},
{"use_pycharm": "n"},
{"use_docker": "y"},
{"use_docker": "n"},
{"postgresql_version": "11.3"},
{"postgresql_version": "10.8"},
{"postgresql_version": "9.6"},
{"postgresql_version": "9.5"},
{"postgresql_version": "9.4"},
{"cloud_provider": "AWS", "use_whitenoise": "y"},
{"cloud_provider": "AWS", "use_whitenoise": "n"},
{"cloud_provider": "GCP", "use_whitenoise": "y"},
{"cloud_provider": "GCP", "use_whitenoise": "n"},
{"cloud_provider": "None", "use_whitenoise": "y"},
# Note: cloud_provider=None AND use_whitenoise=n is not supported
{"mail_service": "Mailgun"},
{"mail_service": "Amazon SES"},
{"mail_service": "Mailjet"},
{"mail_service": "Mandrill"},
{"mail_service": "Postmark"},
{"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):
@ -71,9 +104,7 @@ def build_files_list(root_dir):
def check_paths(paths):
"""Method to check all paths have correct substitutions,
used by other tests cases
"""
"""Method to check all paths have correct substitutions."""
# Assert that no match is found in any of the files
for path in paths:
if is_binary(path):
@ -85,13 +116,10 @@ def check_paths(paths):
assert match is None, msg.format(path)
def test_project_generation(cookies, context, context_combination):
"""
Test that project is generated and fully rendered.
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context={**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."""
result = cookies.bake(extra_context={**context, **context_override})
assert result.exit_code == 0
assert result.exception is None
assert result.project.basename == context["project_slug"]
@ -102,38 +130,30 @@ def test_project_generation(cookies, context, context_combination):
check_paths(paths)
@pytest.mark.flake8
def test_flake8_passes(cookies, context_combination):
"""
Generated project should pass flake8.
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context=context_combination)
@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
def test_flake8_passes(cookies, context_override):
"""Generated project should pass flake8."""
result = cookies.bake(extra_context=context_override)
try:
sh.flake8(str(result.project))
except sh.ErrorReturnCode as e:
pytest.fail(e)
pytest.fail(e.stdout.decode())
@pytest.mark.black
def test_black_passes(cookies, context_combination):
"""
Generated project should pass black.
This is parametrized for each combination from ``context_combination`` fixture
"""
result = cookies.bake(extra_context=context_combination)
@pytest.mark.parametrize("context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id)
def test_black_passes(cookies, context_override):
"""Generated project should pass black."""
result = cookies.bake(extra_context=context_override)
try:
sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/")
except sh.ErrorReturnCode as e:
pytest.fail(e)
pytest.fail(e.stdout.decode())
def test_travis_invokes_pytest(cookies, context):
context.update({"use_travisci": "y"})
context.update({"ci_tool": "Travis"})
result = cookies.bake(extra_context=context)
assert result.exit_code == 0
@ -148,6 +168,24 @@ def test_travis_invokes_pytest(cookies, context):
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"])
def test_invalid_slug(cookies, context, slug):
"""Invalid slug should failed pre-generation hook."""
@ -157,3 +195,13 @@ def test_invalid_slug(cookies, context, slug):
assert result.exit_code != 0
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]
skipsdist = true
envlist = py36,flake8,black,black-template
envlist = py37,black-template
[testenv]
deps = -rrequirements.txt
commands = pytest -m "not flake8" -m "not black" {posargs:./tests}
[testenv:flake8]
deps = -rrequirements.txt
commands = pytest -m flake8 {posargs:./tests}
[testenv:black]
deps = -rrequirements.txt
commands = pytest -m black {posargs:./tests}
commands = pytest {posargs:./tests}
[testenv:black-template]
deps = black

View File

@ -13,10 +13,16 @@ indent_style = space
indent_size = 4
[*.py]
line_length=120
known_first_party={{ cookiecutter.project_slug }}
line_length = 88
known_first_party = {{cookiecutter.project_slug}},config
multi_line_output = 3
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}]
indent_style = space

View File

@ -13,9 +13,26 @@ DJANGO_SECURE_SSL_REDIRECT=False
# Email
# ------------------------------------------------------------------------------
MAILGUN_API_KEY=
DJANGO_SERVER_EMAIL=
{% if cookiecutter.mail_service == 'Mailgun' %}
MAILGUN_API_KEY=
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' %}
# AWS
# ------------------------------------------------------------------------------
@ -31,11 +48,7 @@ DJANGO_GCP_STORAGE_BUCKET_NAME=
# django-allauth
# ------------------------------------------------------------------------------
DJANGO_ACCOUNT_ALLOW_REGISTRATION=True
{% if cookiecutter.use_compressor == 'y' %}
# django-compressor
# ------------------------------------------------------------------------------
COMPRESS_ENABLED=
{% endif %}
# Gunicorn
# ------------------------------------------------------------------------------
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"?>
<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"
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">

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
language: python
python:
- "3.6"
- "3.7"
install:
- pip install -r requirements/local.txt
script:

View File

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

View File

@ -1,19 +1,18 @@
FROM python:3.6-alpine
FROM python:3.7-slim-buster
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
&& apk add --virtual build-deps gcc python3-dev musl-dev \
&& apk add postgresql-dev \
# Pillow dependencies
&& apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
# CFFI dependencies
&& apk add libffi-dev py-cffi \
&& apt-get install -y libpq-dev \
# Translations dependencies
&& apk add gettext \
# https://docs.djangoproject.com/en/dev/ref/django-admin/#dbshell
&& apk add postgresql-client
&& apt-get install -y gettext \
# cleaning up unused files
&& 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.
COPY ./requirements /requirements

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
set -o errexit
set -o pipefail
@ -6,4 +6,25 @@ set -o nounset
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

View File

@ -1,5 +1,5 @@
FROM traefik:alpine
FROM traefik:v2.0
RUN mkdir -p /etc/traefik/acme
RUN touch /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
from celery import Celery
# set the default Django settings module for the 'celery' program.

View File

@ -68,16 +68,20 @@ DJANGO_APPS = [
"django.contrib.staticfiles",
# "django.contrib.humanize", # Handy template tags
"django.contrib.admin",
"django.forms",
]
THIRD_PARTY_APPS = [
"crispy_forms",
"allauth",
"allauth.account",
"allauth.socialaccount",
"rest_framework",
{%- if cookiecutter.use_celery == 'y' %}
"django_celery_beat",
{%- endif %}
{%- if cookiecutter.use_drf == "y" %}
"rest_framework",
"rest_framework.authtoken",
{%- endif %}
]
LOCAL_APPS = [
@ -131,12 +135,16 @@ AUTH_PASSWORD_VALIDATORS = [
# https://docs.djangoproject.com/en/dev/ref/settings/#middleware
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
{%- if cookiecutter.use_whitenoise == 'y' %}
"whitenoise.middleware.WhiteNoiseMiddleware",
{%- endif %}
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.common.BrokenLinkEmailsMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
@ -187,10 +195,15 @@ TEMPLATES = [
"django.template.context_processors.static",
"django.template.context_processors.tz",
"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
CRISPY_TEMPLATE_PACK = "bootstrap4"
@ -291,14 +304,24 @@ ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter"
# https://django-allauth.readthedocs.io/en/latest/configuration.html
SOCIALACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter"
{% if cookiecutter.use_compressor == 'y' -%}
# django-compressor
# ------------------------------------------------------------------------------
# https://django-compressor.readthedocs.io/en/latest/quickstart/#installation
INSTALLED_APPS += ["compressor"]
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 %}
# Your stuff...
# ------------------------------------------------------------------------------

View File

@ -28,19 +28,27 @@ CACHES = {
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' -%}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = env("EMAIL_HOST", default="mailhog")
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = 1025
{%- elif cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' -%}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = "localhost"
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = 1025
{%- else -%}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-backend
EMAIL_BACKEND = env(
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend"
)
# https://docs.djangoproject.com/en/dev/ref/settings/#email-host
EMAIL_HOST = "localhost"
{%- endif %}
# https://docs.djangoproject.com/en/dev/ref/settings/#email-port
EMAIL_PORT = 1025
{%- 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
# ------------------------------------------------------------------------------

View File

@ -1,3 +1,4 @@
"""isort:skip_file"""
{% if cookiecutter.use_sentry == 'y' -%}
import logging
@ -92,7 +93,6 @@ AWS_DEFAULT_ACL = None
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None)
{% elif cookiecutter.cloud_provider == 'GCP' %}
DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage"
GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME")
GS_DEFAULT_ACL = "publicRead"
{% endif -%}
@ -105,8 +105,11 @@ GS_DEFAULT_ACL = "publicRead"
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
{% elif cookiecutter.cloud_provider == 'AWS' -%}
STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage"
COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy"
STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/"
{% 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/"
{% endif -%}
@ -132,14 +135,27 @@ class MediaRootS3Boto3Storage(S3Boto3Storage):
DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage"
MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/"
{%- 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_ROOT = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
{%- endif %}
# 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",
[
@ -167,41 +183,114 @@ EMAIL_SUBJECT_PREFIX = env(
# Django Admin URL regex.
ADMIN_URL = env("DJANGO_ADMIN_URL")
# Anymail (Mailgun)
# Anymail
# ------------------------------------------------------------------------------
# https://anymail.readthedocs.io/en/stable/installation/#installing-anymail
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
{%- if cookiecutter.mail_service == 'Mailgun' %}
# https://anymail.readthedocs.io/en/stable/esps/mailgun/
EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
ANYMAIL = {
"MAILGUN_API_KEY": env("MAILGUN_API_KEY"),
"MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"),
"MAILGUN_API_URL": env("MAILGUN_API_URL", default="https://api.mailgun.net/v3"),
}
{%- 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' -%}
# 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' -%}
{% if cookiecutter.use_compressor == 'y' -%}
# django-compressor
# ------------------------------------------------------------------------------
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED
COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True)
# https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE
{%- if cookiecutter.cloud_provider == 'AWS' %}
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
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 %}
{%- if cookiecutter.use_whitenoise == 'n' -%}
# Collectfast
# ------------------------------------------------------------------------------
# https://github.com/antonagestam/collectfast#installation
INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa F405
AWS_PRELOAD_METADATA = True
{% endif %}
# LOGGING
# ------------------------------------------------------------------------------

View File

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

View File

@ -1,9 +1,12 @@
from django.conf import settings
from django.urls import include, path
from django.conf.urls.static import static
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.generic import TemplateView
{%- if cookiecutter.use_drf == 'y' %}
from rest_framework.authtoken.views import obtain_auth_token
{%- endif %}
urlpatterns = [
path("", TemplateView.as_view(template_name="pages/home.html"), name="home"),
@ -17,6 +20,15 @@ urlpatterns = [
path("accounts/", include("allauth.urls")),
# Your stuff: custom urls includes go here
] + 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:
# 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.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Internal variables.
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
# Put it first so that "make" without argument is like "make help".
help:
@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 " 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)"
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
clean:
-rm -rf $(BUILDDIR)/*
.PHONY: help Makefile
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
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."
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -1,255 +1,55 @@
# {{ cookiecutter.project_name }} documentation build configuration file, created by
# sphinx-quickstart.
# Configuration file for the Sphinx documentation builder.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
import os
import sys
# -- Path setup --------------------------------------------------------------
# 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
# 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
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
# -- Project information -----------------------------------------------------
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 = []
# Add any paths that contain templates here, relative to this directory.
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
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
# 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 = []
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# -- Options for HTML output ---------------------------------------------------
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "default"
# 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
#
html_theme = "alabaster"
# 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,
# so a file named "default.css" will overwrite the builtin "default.css".
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
contain the root `toctree` directive.
{{ cookiecutter.project_name }} Project Documentation
====================================================================
Table of Contents:
Welcome to {{ cookiecutter.project_name }}'s documentation!
======================================================================
.. toctree::
:maxdepth: 2
:caption: Contents:
pycharm/configuration
Indices & Tables
================
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`

View File

@ -1,190 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
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" == "help" (
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
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
)
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
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.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
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
)
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
: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**:
* run and debug python code
.. image:: images/f1.png
* run and debug tests
.. image:: images/f2.png
.. image:: images/f3.png
* run and debug migrations or different django management commands
.. image:: images/f4.png
* and many others..
Known issues

View File

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

View File

@ -1,5 +1,6 @@
[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' %}
norecursedirs = node_modules
{%- endif %}

View File

@ -1,17 +1,17 @@
pytz==2019.1 # https://github.com/stub42/pytz
python-slugify==3.0.2 # https://github.com/un33k/python-slugify
Pillow==6.1.0 # https://github.com/python-pillow/Pillow
pytz==2019.3 # https://github.com/stub42/pytz
python-slugify==4.0.0 # https://github.com/un33k/python-slugify
Pillow==7.0.0 # https://github.com/python-pillow/Pillow
{%- 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
{%- 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' %}
whitenoise==4.1.2 # https://github.com/evansd/whitenoise
whitenoise==5.0.1 # https://github.com/evansd/whitenoise
{%- 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" %}
celery==4.3.0 # pyup: < 5.0 # https://github.com/celery/celery
django-celery-beat==1.5.0 # https://github.com/celery/django-celery-beat
celery==4.4.2 # pyup: < 5.0 # https://github.com/celery/celery
django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat
{%- if cookiecutter.use_docker == 'y' %}
flower==0.9.3 # https://github.com/mher/flower
{%- endif %}
@ -19,16 +19,16 @@ flower==0.9.3 # https://github.com/mher/flower
# 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-model-utils==3.2.0 # https://github.com/jazzband/django-model-utils
django-allauth==0.39.1 # https://github.com/pennersr/django-allauth
django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms
django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils
django-allauth==0.41.0 # https://github.com/pennersr/django-allauth
django-crispy-forms==1.9.0 # https://github.com/django-crispy-forms/django-crispy-forms
{%- 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 %}
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
djangorestframework==3.9.4 # https://github.com/encode/django-rest-framework
coreapi==2.3.3 # https://github.com/core-api/python-client
djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework
{%- endif %}

View File

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

View File

@ -2,20 +2,38 @@
-r ./base.txt
gunicorn==19.9.0 # https://github.com/benoitc/gunicorn
psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==0.6.2 # https://github.com/antonagestam/collectfast
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast
{%- endif %}
{%- 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 %}
# Django
# ------------------------------------------------------------------------------
{%- 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' %}
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 %}
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
[mypy]
python_version = 3.6
python_version = 3.7
check_untyped_defs = True
ignore_errors = False
ignore_missing_imports = True
strict_optional = True
warn_unused_ignores = True
warn_redundant_casts = True
warn_unused_configs = True
plugins = mypy_django_plugin.main
[mypy.plugins.django-stubs]
django_settings_module = config.settings.test
[mypy-*.migrations.*]
# 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
from django.conf import settings
from django.test import RequestFactory
from {{ cookiecutter.project_slug }}.users.models import User
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
@ -11,7 +11,7 @@ def media_storage(settings, tmpdir):
@pytest.fixture
def user() -> settings.AUTH_USER_MODEL:
def user() -> User:
return UserFactory()

View File

@ -15,7 +15,7 @@
<form method="POST" action=".">
{% csrf_token %}
{{ 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>
{% else %}
<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">
{% csrf_token %}
{{ 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>
{% endblock %}
{% endraw %}

View File

@ -80,7 +80,7 @@
{% if 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 %}
{% endif %}

View File

@ -10,7 +10,7 @@
{{ form|crispy }}
<div class="control-group">
<div class="controls">
<button type="submit" class="btn">Update</button>
<button type="submit" class="btn btn-primary">Update</button>
</div>
</div>
</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.utils.translation import ugettext_lazy as _

View File

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

View File

@ -1,8 +1,9 @@
import pytest
from django.conf import settings
from {{ cookiecutter.project_slug }}.users.models import User
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}/"

View File

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

View File

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

View File

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

View File

@ -1,9 +1,9 @@
from django.urls import path
from {{ cookiecutter.project_slug }}.users.views import (
user_detail_view,
user_redirect_view,
user_update_view,
user_detail_view,
)
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.mixins import LoginRequiredMixin
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.views.generic import DetailView, RedirectView, UpdateView
User = get_user_model()
@ -27,6 +29,12 @@ class UserUpdateView(LoginRequiredMixin, UpdateView):
def get_object(self):
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()

View File

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