mirror of
https://github.com/cookiecutter/cookiecutter-django.git
synced 2024-11-23 18:14:01 +03:00
commit
acf61d2985
4
.github/FUNDING.yml
vendored
4
.github/FUNDING.yml
vendored
|
@ -1,7 +1,7 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: danielroygreenfeld
|
||||
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
|
||||
|
|
11
.github/labeler.yml
vendored
Normal file
11
.github/labeler.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Add 'docs' to any changes within 'docs' folder or any subfolders
|
||||
docs:
|
||||
- 'README.rst'
|
||||
- 'docs/**/*'
|
||||
- '{{cookiecutter.project_slug}}/docs/**/*'
|
||||
|
||||
# Flag PR related to docker
|
||||
docker:
|
||||
- '{{cookiecutter.project_slug}}/compose/**/*'
|
||||
- '{{cookiecutter.project_slug}}/local.yml'
|
||||
- '{{cookiecutter.project_slug}}/production.yml'
|
29
.github/release-drafter.yml
vendored
Normal file
29
.github/release-drafter.yml
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
categories:
|
||||
- title: 'Breaking Changes'
|
||||
labels:
|
||||
- 'breaking'
|
||||
- title: 'Major Changes'
|
||||
labels:
|
||||
- 'major'
|
||||
- title: 'Minor Changes'
|
||||
labels:
|
||||
- 'enhancement'
|
||||
- title: 'Bugfixes'
|
||||
labels:
|
||||
- 'bug'
|
||||
- title: 'Removals'
|
||||
labels:
|
||||
- 'removed'
|
||||
- title: 'Documentation updates'
|
||||
labels:
|
||||
- 'docs'
|
||||
|
||||
exclude-labels:
|
||||
- 'skip-changelog'
|
||||
- 'update'
|
||||
- 'project infrastructure'
|
||||
|
||||
template: |
|
||||
## Changes
|
||||
|
||||
$CHANGES
|
14
.github/workflows/draft-release.yml
vendored
Normal file
14
.github/workflows/draft-release.yml
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
name: Release Drafter
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
release_notes:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
20
.github/workflows/label.yml
vendored
Normal file
20
.github/workflows/label.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
# This workflow will triage pull requests and apply a label based on the
|
||||
# paths that are modified in the pull request.
|
||||
#
|
||||
# To use this workflow, you will need to set up a .github/labeler.yml
|
||||
# file with configuration. For more information, see:
|
||||
# https://github.com/actions/labeler/blob/master/README.md
|
||||
|
||||
|
||||
name: Labeler
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
label:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/labeler@v2
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
@ -8,6 +8,11 @@ update: all
|
|||
# allowed: True, False
|
||||
pin: True
|
||||
|
||||
# add a label to pull requests, default is not set
|
||||
# requires private repo permissions, even on public repos
|
||||
# default: empty
|
||||
label_prs: update
|
||||
|
||||
# Specify requirement files by hand, pyup seems to struggle to
|
||||
# find the ones in the project_slug folder
|
||||
requirements:
|
||||
|
|
|
@ -5,7 +5,7 @@ services:
|
|||
|
||||
language: python
|
||||
|
||||
python: 3.7
|
||||
python: 3.8
|
||||
|
||||
before_install:
|
||||
- docker-compose -v
|
||||
|
@ -14,11 +14,7 @@ before_install:
|
|||
matrix:
|
||||
include:
|
||||
- name: Test results
|
||||
script: tox -e py37
|
||||
- name: Run flake8 on result
|
||||
script: tox -e flake8
|
||||
- name: Run black on result
|
||||
script: tox -e black
|
||||
script: tox -e py38
|
||||
- name: Black template
|
||||
script: tox -e black-template
|
||||
- name: Basic Docker
|
||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -1,6 +1,36 @@
|
|||
# Change Log
|
||||
All enhancements and patches to Cookiecutter Django will be documented in this file.
|
||||
|
||||
## [2020-04-13]
|
||||
### Changed
|
||||
- Updated to Python 3.8 (@codnee)
|
||||
- Moved converage config in setup.cfg (@danihodovic)
|
||||
|
||||
## [2020-04-08]
|
||||
### Fixed
|
||||
- Internal IPs for debug toolbar (@dudanogueira)
|
||||
|
||||
## [2020-04-04]
|
||||
### Fixed
|
||||
- Added compress command command with Django compressor (@gwiskur)
|
||||
|
||||
## [2020-03-23]
|
||||
### Changed
|
||||
- Updated project to Django 3.0
|
||||
|
||||
## [2020-03-17]
|
||||
### Changed
|
||||
- Handle paths using Pathlib (@jules-ch)
|
||||
|
||||
### Fixed
|
||||
- Pre-commit hook regex (@demestav)
|
||||
|
||||
## [2020-03-16]
|
||||
### Added
|
||||
- Support for all Anymail providers (@Andrew-Chen-Wang)
|
||||
### Fixed
|
||||
- Django compressor setup (@jameswilliams1)
|
||||
|
||||
## [2020-01-23]
|
||||
### Changed
|
||||
- Fix UserFactory to set the password if provided (@BoPeng)
|
||||
|
|
|
@ -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 py37
|
||||
$ tox -e py38
|
||||
|
||||
This will run py.test with the python3.7 interpreter, for example.
|
||||
This will run py.test with the python3.8 interpreter, for example.
|
||||
|
||||
To run a particular test with tox for against your current Python version::
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ Listed in alphabetical order.
|
|||
Adam Dobrawy `@ad-m`_
|
||||
Adam Steele `@adammsteele`_
|
||||
Agam Dua
|
||||
Agustín Scaramuzza `@scaramagus`_ @scaramagus
|
||||
Alberto Sanchez `@alb3rto`_
|
||||
Alex Tsai `@caffodian`_
|
||||
Alvaro [Andor] `@andor-pierdelacabeza`_
|
||||
|
@ -56,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`_
|
||||
|
@ -87,6 +89,7 @@ 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`_
|
||||
|
@ -96,6 +99,7 @@ Listed in alphabetical order.
|
|||
Dani Hodovic `@danihodovic`_
|
||||
Daniel Hepper `@dhepper`_ @danielhepper
|
||||
Daniel Hillier `@danifus`_
|
||||
Daniel Sears `@highpost`_ @highpost
|
||||
Daniele Tricoli `@eriol`_
|
||||
David Díaz `@ddiazpinto`_ @DavidDiazPinto
|
||||
Davit Tovmasyan `@davitovmasyan`_
|
||||
|
@ -108,15 +112,20 @@ Listed in alphabetical order.
|
|||
Diane Chen `@purplediane`_ @purplediane88
|
||||
Dónal Adams `@epileptic-fish`_
|
||||
Dong Huynh `@trungdong`_
|
||||
Duda Nogueira `@dudanogueira`_ @dudanogueira
|
||||
Emanuel Calso `@bloodpet`_ @bloodpet
|
||||
Eraldo Energy `@eraldo`_
|
||||
Eric Groom `@ericgroom`_
|
||||
Ernesto Cedeno `@codnee`_
|
||||
Eyad Al Sibai `@eyadsibai`_
|
||||
Felipe Arruda `@arruda`_
|
||||
Florian Idelberger `@step21`_ @windrush
|
||||
Gabriel Mejia `@elgartoinf`_ @elgartoinf
|
||||
Garry Cairns `@garry-cairns`_
|
||||
Garry Polley `@garrypolley`_
|
||||
Gilbishkosma `@Gilbishkosma`_
|
||||
Glenn Wiskur `@gwiskur`_
|
||||
Guilherme Guy `@guilherme1guy`_
|
||||
Hamish Durkin `@durkode`_
|
||||
Hana Quadara `@hanaquadara`_
|
||||
Harry Moreno `@morenoh149`_ @morenoh149
|
||||
|
@ -128,6 +137,7 @@ Listed in alphabetical order.
|
|||
Irfan Ahmad `@erfaan`_ @erfaan
|
||||
Isaac12x `@Isaac12x`_
|
||||
Ivan Khomutov `@ikhomutov`_
|
||||
James Williams `@jameswilliams1`_
|
||||
Jan Van Bruggen `@jvanbrug`_
|
||||
Jelmer Draaijer `@foarsitter`_
|
||||
Jerome Caisip `@jeromecaisip`_
|
||||
|
@ -135,6 +145,7 @@ Listed in alphabetical order.
|
|||
Jerome Leclanche `@jleclanche`_ @Adys
|
||||
Jimmy Gitonga `@afrowave`_ @afrowave
|
||||
John Cass `@jcass77`_ @cass_john
|
||||
Jules Cheron `@jules-ch`_
|
||||
Julien Almarcha `@sladinji`_
|
||||
Julio Castillo `@juliocc`_
|
||||
Kaido Kert `@kaidokert`_
|
||||
|
@ -175,6 +186,7 @@ Listed in alphabetical order.
|
|||
Oleg Russkin `@rolep`_
|
||||
Pablo `@oubiga`_
|
||||
Parbhat Puri `@parbhat`_
|
||||
Pawan Chaurasia `@rjsnh1522`_
|
||||
Peter Bittner `@bittner`_
|
||||
Peter Coles `@mrcoles`_
|
||||
Philipp Matthies `@canonnervio`_
|
||||
|
@ -190,6 +202,7 @@ Listed in alphabetical order.
|
|||
Sascha `@saschalalala`_ @saschalalala
|
||||
Shupeyko Nikita `@webyneter`_
|
||||
Sławek Ehlert `@slafs`_
|
||||
Sorasful `@sorasful`_
|
||||
Srinivas Nyayapati `@shireenrao`_
|
||||
stepmr `@stepmr`_
|
||||
Steve Steiner `@ssteinerX`_
|
||||
|
@ -205,6 +218,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`_
|
||||
|
@ -228,6 +242,7 @@ 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
|
||||
|
@ -258,6 +273,8 @@ Listed in alphabetical order.
|
|||
.. _@chuckus: https://github.com/chuckus
|
||||
.. _@cmackenzie1: https://github.com/cmackenzie1
|
||||
.. _@cmargieson: https://github.com/cmargieson
|
||||
.. _@codnee: https://github.com/codnee
|
||||
.. _@cole: https://github.com/cole
|
||||
.. _@Collederas: https://github.com/Collederas
|
||||
.. _@curtisstpierre: https://github.com/curtisstpierre
|
||||
.. _@dadokkio: https://github.com/dadokkio
|
||||
|
@ -270,9 +287,12 @@ Listed in alphabetical order.
|
|||
.. _@dezoito: https://github.com/dezoito
|
||||
.. _@dhepper: https://github.com/dhepper
|
||||
.. _@dot2dotseurat: https://github.com/dot2dotseurat
|
||||
.. _@dudanogueira: https://github.com/dudanogueira
|
||||
.. _@dsclementsen: https://github.com/dsclementsen
|
||||
.. _@guilherme1guy: https://github.com/guilherme1guy
|
||||
.. _@durkode: https://github.com/durkode
|
||||
.. _@Egregors: https://github.com/Egregors
|
||||
.. _@elgartoinf: https://gihub.com/elgartoinf
|
||||
.. _@epileptic-fish: https://gihub.com/epileptic-fish
|
||||
.. _@eraldo: https://github.com/eraldo
|
||||
.. _@erfaan: https://github.com/erfaan
|
||||
|
@ -284,16 +304,19 @@ Listed in alphabetical order.
|
|||
.. _@garry-cairns: https://github.com/garry-cairns
|
||||
.. _@garrypolley: https://github.com/garrypolley
|
||||
.. _@Gilbishkosma: https://github.com/Gilbishkosma
|
||||
.. _@gwiskur: https://github.com/gwiskur
|
||||
.. _@glasslion: https://github.com/glasslion
|
||||
.. _@goldhand: https://github.com/goldhand
|
||||
.. _@hackebrot: https://github.com/hackebrot
|
||||
.. _@hairychris: https://github.com/hairychris
|
||||
.. _@hanaquadara: https://github.com/hanaquadara
|
||||
.. _@hendrikschneider: https://github.com/hendrikschneider
|
||||
.. _@highpost: https://github.com/highpost
|
||||
.. _@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
|
||||
|
@ -302,6 +325,7 @@ Listed in alphabetical order.
|
|||
.. _@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
|
||||
.. _@jvanbrug: https://github.com/jvanbrug
|
||||
.. _@ka7eh: https://github.com/ka7eh
|
||||
|
@ -335,21 +359,25 @@ Listed in alphabetical order.
|
|||
.. _@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
|
||||
.. _@siauPatrick: https://github.com/siauPatrick
|
||||
.. _@sladinji: https://github.com/sladinji
|
||||
.. _@slafs: https://github.com/slafs
|
||||
.. _@sorasful:: https://github.com/sorasful
|
||||
.. _@ssteinerX: https://github.com/ssteinerx
|
||||
.. _@step21: https://github.com/step21
|
||||
.. _@stepmr: https://github.com/stepmr
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,4 +1,4 @@
|
|||
Copyright (c) 2013-2018, Daniel Roy Greenfeld
|
||||
Copyright (c) 2013-2020, Daniel Roy Greenfeld
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
|
|
25
README.rst
25
README.rst
|
@ -36,8 +36,8 @@ production-ready Django projects quickly.
|
|||
Features
|
||||
---------
|
||||
|
||||
* For Django 2.2
|
||||
* Works with Python 3.7
|
||||
* For Django 3.0
|
||||
* Works with Python 3.8
|
||||
* Renders Django projects with 100% starting test coverage
|
||||
* Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available)
|
||||
* 12-Factor_ based settings via django-environ_
|
||||
|
@ -45,8 +45,9 @@ Features
|
|||
* Optimized development and production settings
|
||||
* Registration via django-allauth_
|
||||
* Comes with custom user model ready to go
|
||||
* Optional basic ASGI setup for Websockets
|
||||
* 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
|
||||
|
@ -85,7 +86,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
|
||||
.. _pre-commit: https://github.com/pre-commit/pre-commit
|
||||
|
||||
Constraints
|
||||
-----------
|
||||
|
@ -105,16 +106,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/2017-06-29-tsd11-sticker-02.png
|
||||
: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
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
@ -135,7 +136,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::
|
||||
|
||||
|
@ -272,7 +273,7 @@ If you do rename your fork, I encourage you to submit it to the following places
|
|||
* cookiecutter_ so it gets listed in the README as a template.
|
||||
* The cookiecutter grid_ on Django Packages.
|
||||
|
||||
.. _cookiecutter: https://github.com/audreyr/cookiecutter
|
||||
.. _cookiecutter: https://github.com/cookiecutter/cookiecutter
|
||||
.. _grid: https://www.djangopackages.com/grids/g/cookiecutters/
|
||||
|
||||
Submit a Pull Request
|
||||
|
|
|
@ -33,6 +33,18 @@
|
|||
"GCP",
|
||||
"None"
|
||||
],
|
||||
"mail_service": [
|
||||
"Mailgun",
|
||||
"Amazon SES",
|
||||
"Mailjet",
|
||||
"Mandrill",
|
||||
"Postmark",
|
||||
"Sendgrid",
|
||||
"SendinBlue",
|
||||
"SparkPost",
|
||||
"Other SMTP"
|
||||
],
|
||||
"use_async": "n",
|
||||
"use_drf": "n",
|
||||
"custom_bootstrap_compilation": "n",
|
||||
"use_compressor": "n",
|
||||
|
|
|
@ -15,7 +15,7 @@ Full instructions follow, but here's a high-level view.
|
|||
|
||||
2. Set your config variables in the *postactivate* script
|
||||
|
||||
3. Run the *manage.py* ``migrate`` and ``collectstatic`` commands
|
||||
3. Run the *manage.py* ``migrate`` and ``collectstatic`` {%- if cookiecutter.use_compressor == "y" %}and ``compress`` {%- endif %}commands
|
||||
|
||||
4. Add an entry to the PythonAnywhere *Web tab*
|
||||
|
||||
|
@ -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.7 my-project-name
|
||||
mkvirtualenv --python=/usr/bin/python3.8 my-project-name
|
||||
pip install -r requirements/production.txt # may take a few minutes
|
||||
|
||||
|
||||
|
@ -109,6 +109,7 @@ Now run the migration, and collectstatic:
|
|||
source $VIRTUAL_ENV/bin/postactivate
|
||||
python manage.py migrate
|
||||
python manage.py collectstatic
|
||||
{%- if cookiecutter.use_compressor == "y" %}python manage.py compress {%- endif %}
|
||||
# and, optionally
|
||||
python manage.py createsuperuser
|
||||
|
||||
|
@ -175,6 +176,7 @@ For subsequent deployments, the procedure is much simpler. In a Bash console:
|
|||
git pull
|
||||
python manage.py migrate
|
||||
python manage.py collectstatic
|
||||
{%- if cookiecutter.use_compressor == "y" %}python manage.py compress {%- endif %}
|
||||
|
||||
And then go to the Web tab and hit **Reload**
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ Setting Up Development Environment
|
|||
|
||||
Make sure to have the following on your host:
|
||||
|
||||
* Python 3.7
|
||||
* Python 3.8
|
||||
* PostgreSQL_.
|
||||
* Redis_, if using Celery
|
||||
|
||||
|
@ -17,7 +17,7 @@ First things first.
|
|||
|
||||
#. Create a virtualenv: ::
|
||||
|
||||
$ python3.7 -m venv <virtual env path>
|
||||
$ python3.8 -m venv <virtual env path>
|
||||
|
||||
#. Activate the virtualenv you have just created: ::
|
||||
|
||||
|
@ -68,10 +68,14 @@ First things first.
|
|||
|
||||
$ python manage.py migrate
|
||||
|
||||
#. See the application being served through Django development server: ::
|
||||
#. If you're running synchronously, see the application being served through Django development server: ::
|
||||
|
||||
$ python manage.py runserver 0.0.0.0:8000
|
||||
|
||||
or if you're running asynchronously: ::
|
||||
|
||||
$ gunicorn config.asgi --bind 0.0.0.0:8000 -k uvicorn.workers.UvicornWorker --reload
|
||||
|
||||
.. _PostgreSQL: https://www.postgresql.org/download/
|
||||
.. _Redis: https://redis.io/download
|
||||
.. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html
|
||||
|
|
|
@ -24,4 +24,4 @@ Why doesn't this follow the layout from Two Scoops of Django?
|
|||
|
||||
You may notice that some elements of this project do not exactly match what we describe in chapter 3 of `Two Scoops of Django 1.11`_. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored.
|
||||
|
||||
.. _Two Scoops of Django 1.11: https://www.twoscoopspress.com/collections/django/products/two-scoops-of-django-1-11
|
||||
.. _Two Scoops of Django 1.11: https://www.feldroy.com/collections/django/products/two-scoops-of-django-1-11
|
||||
|
|
|
@ -5,7 +5,7 @@ Welcome to Cookiecutter Django's documentation!
|
|||
|
||||
A Cookiecutter_ template for Django.
|
||||
|
||||
.. _cookiecutter: https://github.com/audreyr/cookiecutter
|
||||
.. _cookiecutter: https://github.com/cookiecutter/cookiecutter
|
||||
|
||||
Contents:
|
||||
|
||||
|
|
|
@ -70,6 +70,25 @@ 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. Mailgun_
|
||||
2. `Amazon SES`_
|
||||
3. Mailjet_
|
||||
4. Mandrill_
|
||||
5. Postmark_
|
||||
6. SendGrid_
|
||||
7. SendinBlue_
|
||||
8. SparkPost_
|
||||
9. `Other SMTP`_
|
||||
|
||||
use_async:
|
||||
Indicates whether the project should use web sockets with Uvicorn + Gunicorn.
|
||||
|
||||
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
|
||||
|
@ -98,8 +117,8 @@ ci_tool:
|
|||
Select a CI tool for running tests. The choices are:
|
||||
|
||||
1. None
|
||||
2. Travis_
|
||||
3. Gitlab_
|
||||
2. `Travis CI`_
|
||||
3. `Gitlab CI`_
|
||||
|
||||
keep_local_envs_in_vcs:
|
||||
Indicates whether the project's ``.envs/.local/`` should be kept in VCS
|
||||
|
@ -129,6 +148,18 @@ debug:
|
|||
.. _AWS: https://aws.amazon.com/s3/
|
||||
.. _GCP: https://cloud.google.com/storage/
|
||||
|
||||
.. _Amazon 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
|
||||
.. _Other SMTP: 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
|
||||
|
|
|
@ -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"
|
||||
======================================= =========================== ============================================== ======================================================================
|
||||
|
||||
--------------------------
|
||||
|
|
|
@ -101,6 +101,15 @@ def remove_celery_files():
|
|||
os.remove(file_name)
|
||||
|
||||
|
||||
def remove_async_files():
|
||||
file_names = [
|
||||
os.path.join("config", "asgi.py"),
|
||||
os.path.join("config", "websocket.py"),
|
||||
]
|
||||
for file_name in file_names:
|
||||
os.remove(file_name)
|
||||
|
||||
|
||||
def remove_dottravisyml_file():
|
||||
os.remove(".travis.yml")
|
||||
|
||||
|
@ -292,6 +301,10 @@ def remove_drf_starter_files():
|
|||
shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "users", "api"))
|
||||
|
||||
|
||||
def remove_storages_module():
|
||||
os.remove(os.path.join("{{cookiecutter.project_slug}}", "utils", "storages.py"))
|
||||
|
||||
|
||||
def main():
|
||||
debug = "{{ cookiecutter.debug }}".lower() == "y"
|
||||
|
||||
|
@ -352,6 +365,7 @@ def main():
|
|||
WARNING + "You chose not to use a cloud provider, "
|
||||
"media files won't be served in production." + TERMINATOR
|
||||
)
|
||||
remove_storages_module()
|
||||
|
||||
if "{{ cookiecutter.use_celery }}".lower() == "n":
|
||||
remove_celery_files()
|
||||
|
@ -367,6 +381,9 @@ def main():
|
|||
if "{{ cookiecutter.use_drf }}".lower() == "n":
|
||||
remove_drf_starter_files()
|
||||
|
||||
if "{{ cookiecutter.use_async }}".lower() == "n":
|
||||
remove_async_files()
|
||||
|
||||
print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR)
|
||||
|
||||
|
||||
|
|
|
@ -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.7+. Do you want to proceed (y/n)? " + TERMINATOR
|
||||
"project requires Python 3.8+. Do you want to proceed (y/n)? " + TERMINATOR
|
||||
)
|
||||
yes_options, no_options = frozenset(["y"]), frozenset(["n"])
|
||||
while True:
|
||||
|
@ -68,3 +68,15 @@ if (
|
|||
"You should either use Whitenoise or select a Cloud Provider to serve static files"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if (
|
||||
"{{ cookiecutter.cloud_provider }}" == "GCP"
|
||||
and "{{ cookiecutter.mail_service }}" == "Amazon SES"
|
||||
) or (
|
||||
"{{ cookiecutter.cloud_provider }}" == "None"
|
||||
and "{{ cookiecutter.mail_service }}" == "Amazon SES"
|
||||
):
|
||||
print(
|
||||
"You should either use AWS or select a different Mail Service for sending emails."
|
||||
)
|
||||
sys.exit(1)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cookiecutter==1.7.0
|
||||
cookiecutter==1.7.2
|
||||
sh==1.12.14
|
||||
binaryornot==0.4.4
|
||||
|
||||
|
@ -6,12 +6,12 @@ binaryornot==0.4.4
|
|||
# ------------------------------------------------------------------------------
|
||||
black==19.10b0
|
||||
flake8==3.7.9
|
||||
flake8-isort==3.0.0
|
||||
|
||||
# Testing
|
||||
# ------------------------------------------------------------------------------
|
||||
tox==3.14.3
|
||||
pytest==5.3.5
|
||||
pytest_cases==1.12.1
|
||||
pytest-cookies==0.4.0
|
||||
pytest-xdist==1.31.0
|
||||
pyyaml==5.3
|
||||
tox==3.14.6
|
||||
pytest==5.4.1
|
||||
pytest-cookies==0.5.1
|
||||
pytest-instafail==0.4.1.post0
|
||||
pyyaml==5.3.1
|
||||
|
|
6
setup.py
6
setup.py
|
@ -10,7 +10,7 @@ except ImportError:
|
|||
|
||||
# Our version ALWAYS matches the version of Django we support
|
||||
# If Django has a new release, we branch, tag, then update this setting after the tag.
|
||||
version = "2.2.1"
|
||||
version = "3.0.5-01"
|
||||
|
||||
if sys.argv[-1] == "tag":
|
||||
os.system(f'git tag -a {version} -m "version {version}"')
|
||||
|
@ -34,13 +34,13 @@ setup(
|
|||
classifiers=[
|
||||
"Development Status :: 4 - Beta",
|
||||
"Environment :: Console",
|
||||
"Framework :: Django :: 2.2",
|
||||
"Framework :: Django :: 3.0",
|
||||
"Intended Audience :: Developers",
|
||||
"Natural Language :: English",
|
||||
"License :: OSI Approved :: BSD License",
|
||||
"Programming Language :: Python",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Topic :: Software Development",
|
||||
],
|
||||
|
|
|
@ -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
|
||||
|
@ -26,49 +25,93 @@ def context():
|
|||
}
|
||||
|
||||
|
||||
@pytest_fixture_plus
|
||||
@pytest.mark.parametrize("windows", ["y", "n"], ids=lambda yn: f"win:{yn}")
|
||||
@pytest.mark.parametrize("use_docker", ["y", "n"], ids=lambda yn: f"docker:{yn}")
|
||||
@pytest.mark.parametrize("use_celery", ["y", "n"], ids=lambda yn: f"celery:{yn}")
|
||||
@pytest.mark.parametrize("use_mailhog", ["y", "n"], ids=lambda yn: f"mailhog:{yn}")
|
||||
@pytest.mark.parametrize("use_sentry", ["y", "n"], ids=lambda yn: f"sentry:{yn}")
|
||||
@pytest.mark.parametrize("use_compressor", ["y", "n"], ids=lambda yn: f"cmpr:{yn}")
|
||||
@pytest.mark.parametrize("use_drf", ["y", "n"], ids=lambda yn: f"drf:{yn}")
|
||||
@pytest.mark.parametrize(
|
||||
"use_whitenoise,cloud_provider",
|
||||
[
|
||||
("y", "AWS"),
|
||||
("y", "GCP"),
|
||||
("y", "None"),
|
||||
("n", "AWS"),
|
||||
("n", "GCP"),
|
||||
# no whitenoise + no cloud provider is not supported
|
||||
],
|
||||
ids=lambda id: f"wnoise:{id[0]}-cloud:{id[1]}",
|
||||
)
|
||||
def context_combination(
|
||||
windows,
|
||||
use_docker,
|
||||
use_celery,
|
||||
use_mailhog,
|
||||
use_sentry,
|
||||
use_compressor,
|
||||
use_whitenoise,
|
||||
use_drf,
|
||||
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,
|
||||
"use_drf": use_drf,
|
||||
"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", "mail_service": "Mailgun"},
|
||||
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailjet"},
|
||||
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mandrill"},
|
||||
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Postmark"},
|
||||
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Sendgrid"},
|
||||
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "SendinBlue"},
|
||||
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "SparkPost"},
|
||||
{"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Other SMTP"},
|
||||
# Note: cloud_provider=None AND use_whitenoise=n is not supported
|
||||
{"cloud_provider": "AWS", "mail_service": "Mailgun"},
|
||||
{"cloud_provider": "AWS", "mail_service": "Amazon SES"},
|
||||
{"cloud_provider": "AWS", "mail_service": "Mailjet"},
|
||||
{"cloud_provider": "AWS", "mail_service": "Mandrill"},
|
||||
{"cloud_provider": "AWS", "mail_service": "Postmark"},
|
||||
{"cloud_provider": "AWS", "mail_service": "Sendgrid"},
|
||||
{"cloud_provider": "AWS", "mail_service": "SendinBlue"},
|
||||
{"cloud_provider": "AWS", "mail_service": "SparkPost"},
|
||||
{"cloud_provider": "AWS", "mail_service": "Other SMTP"},
|
||||
{"cloud_provider": "GCP", "mail_service": "Mailgun"},
|
||||
{"cloud_provider": "GCP", "mail_service": "Mailjet"},
|
||||
{"cloud_provider": "GCP", "mail_service": "Mandrill"},
|
||||
{"cloud_provider": "GCP", "mail_service": "Postmark"},
|
||||
{"cloud_provider": "GCP", "mail_service": "Sendgrid"},
|
||||
{"cloud_provider": "GCP", "mail_service": "SendinBlue"},
|
||||
{"cloud_provider": "GCP", "mail_service": "SparkPost"},
|
||||
{"cloud_provider": "GCP", "mail_service": "Other SMTP"},
|
||||
# Note: cloud_providers GCP and None with mail_service Amazon SES is not supported
|
||||
{"use_async": "y"},
|
||||
{"use_async": "n"},
|
||||
{"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"},
|
||||
{"cloud_provider": "GCP", "mail_service": "Amazon SES"},
|
||||
{"cloud_provider": "None", "mail_service": "Amazon SES"},
|
||||
]
|
||||
|
||||
|
||||
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):
|
||||
|
@ -81,9 +124,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):
|
||||
|
@ -95,13 +136,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"]
|
||||
|
@ -112,38 +150,34 @@ 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({"ci_tool": "Travis"})
|
||||
@pytest.mark.parametrize(
|
||||
["use_docker", "expected_test_script"],
|
||||
[("n", "pytest"), ("y", "docker-compose -f local.yml run django pytest"),],
|
||||
)
|
||||
def test_travis_invokes_pytest(cookies, context, use_docker, expected_test_script):
|
||||
context.update({"ci_tool": "Travis", "use_docker": use_docker})
|
||||
result = cookies.bake(extra_context=context)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
@ -153,13 +187,21 @@ def test_travis_invokes_pytest(cookies, context):
|
|||
|
||||
with open(f"{result.project}/.travis.yml", "r") as travis_yml:
|
||||
try:
|
||||
assert yaml.load(travis_yml)["script"] == ["pytest"]
|
||||
yml = yaml.load(travis_yml, Loader=yaml.FullLoader)["jobs"]["include"]
|
||||
assert yml[0]["script"] == ["flake8"]
|
||||
assert yml[1]["script"] == [expected_test_script]
|
||||
except yaml.YAMLError as e:
|
||||
pytest.fail(e)
|
||||
pytest.fail(str(e))
|
||||
|
||||
|
||||
def test_gitlab_invokes_flake8_and_pytest(cookies, context):
|
||||
context.update({"ci_tool": "Gitlab"})
|
||||
@pytest.mark.parametrize(
|
||||
["use_docker", "expected_test_script"],
|
||||
[("n", "pytest"), ("y", "docker-compose -f local.yml run django pytest"),],
|
||||
)
|
||||
def test_gitlab_invokes_flake8_and_pytest(
|
||||
cookies, context, use_docker, expected_test_script
|
||||
):
|
||||
context.update({"ci_tool": "Gitlab", "use_docker": use_docker})
|
||||
result = cookies.bake(extra_context=context)
|
||||
|
||||
assert result.exit_code == 0
|
||||
|
@ -169,9 +211,9 @@ def test_gitlab_invokes_flake8_and_pytest(cookies, context):
|
|||
|
||||
with open(f"{result.project}/.gitlab-ci.yml", "r") as gitlab_yml:
|
||||
try:
|
||||
gitlab_config = yaml.load(gitlab_yml)
|
||||
gitlab_config = yaml.load(gitlab_yml, Loader=yaml.FullLoader)
|
||||
assert gitlab_config["flake8"]["script"] == ["flake8"]
|
||||
assert gitlab_config["pytest"]["script"] == ["pytest"]
|
||||
assert gitlab_config["pytest"]["script"] == [expected_test_script]
|
||||
except yaml.YAMLError as e:
|
||||
pytest.fail(e)
|
||||
|
||||
|
@ -187,9 +229,10 @@ def test_invalid_slug(cookies, context, slug):
|
|||
assert isinstance(result.exception, FailedHookException)
|
||||
|
||||
|
||||
def test_no_whitenoise_and_no_cloud_provider(cookies, context):
|
||||
"""It should not generate project if neither whitenoise or cloud provider are set"""
|
||||
context.update({"use_whitenoise": "n", "cloud_provider": "None"})
|
||||
@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
|
||||
|
|
12
tox.ini
12
tox.ini
|
@ -1,18 +1,10 @@
|
|||
[tox]
|
||||
skipsdist = true
|
||||
envlist = py37,flake8,black,black-template
|
||||
envlist = py38,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
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
[run]
|
||||
include = {{cookiecutter.project_slug}}/*
|
||||
omit = *migrations*, *tests*
|
||||
plugins =
|
||||
django_coverage_plugin
|
|
@ -13,8 +13,8 @@ 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,6 +6,10 @@ variables:
|
|||
POSTGRES_USER: '{{ cookiecutter.project_slug }}'
|
||||
POSTGRES_PASSWORD: ''
|
||||
POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}'
|
||||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
{% if cookiecutter.use_celery == 'y' -%}
|
||||
CELERY_BROKER_URL: 'redis://redis:6379/0'
|
||||
{%- endif %}
|
||||
|
||||
flake8:
|
||||
stage: lint
|
||||
|
@ -18,8 +22,21 @@ flake8:
|
|||
pytest:
|
||||
stage: test
|
||||
image: python:3.7
|
||||
{% if cookiecutter.use_docker == 'y' -%}
|
||||
tags:
|
||||
- docker
|
||||
services:
|
||||
- docker
|
||||
before_script:
|
||||
- docker-compose -f local.yml build
|
||||
# Ensure celerybeat does not crash due to non-existent tables
|
||||
- docker-compose -f local.yml run --rm django python manage.py migrate
|
||||
- docker-compose -f local.yml up -d
|
||||
script:
|
||||
- docker-compose -f local.yml run django pytest
|
||||
{%- else %}
|
||||
tags:
|
||||
- python
|
||||
services:
|
||||
- postgres:11
|
||||
variables:
|
||||
|
@ -30,4 +47,5 @@ pytest:
|
|||
|
||||
script:
|
||||
- pytest
|
||||
{%- endif %}
|
||||
|
||||
|
|
|
@ -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)) import django; print('Django %s' % django.get_version()) import os sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) if 'setup' in dir(django): django.setup() 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)) import django; print('Django %s' % django.get_version()) import os os.environ.setdefault("DATABASE_URL","postgres://{}:{}@{}:{}/{}".format(os.environ['POSTGRES_USER'], os.environ['POSTGRES_PASSWORD'], os.environ['POSTGRES_HOST'], os.environ['POSTGRES_PORT'], os.environ['POSTGRES_DB'])) os.environ.setdefault("CELERY_BROKER_URL", os.environ['REDIS_URL']) sys.path.extend([WORKING_DIR_AND_PYTHON_PATHS]) if 'setup' in dir(django): django.setup() import django_manage_shell; django_manage_shell.run(PROJECT_ROOT)"
|
||||
module-name="{{ cookiecutter.project_slug }}" is-module-sdk="true">
|
||||
|
|
|
@ -7,7 +7,7 @@ repos:
|
|||
rev: master
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
files: (^|/)a/.+\.(py|html|sh|css|js)$
|
||||
files: (^|/).+\.(py|html|sh|css|js)$
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
|
@ -16,4 +16,5 @@ repos:
|
|||
entry: flake8
|
||||
language: python
|
||||
types: [python]
|
||||
args: ['--config=setup.cfg']
|
||||
|
||||
|
|
|
@ -1,17 +1,45 @@
|
|||
dist: xenial
|
||||
services:
|
||||
- postgresql
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq build-essential gettext python-dev zlib1g-dev libpq-dev xvfb
|
||||
- sudo apt-get install -qq libjpeg8-dev libfreetype6-dev libwebp-dev
|
||||
- sudo apt-get install -qq graphviz-dev python-setuptools python3-dev python-virtualenv python-pip
|
||||
- sudo apt-get install -qq firefox automake libtool libreadline6 libreadline6-dev libreadline-dev
|
||||
- sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm
|
||||
|
||||
language: python
|
||||
python:
|
||||
- "3.7"
|
||||
install:
|
||||
- pip install -r requirements/local.txt
|
||||
script:
|
||||
- "pytest"
|
||||
- "3.8"
|
||||
|
||||
services:
|
||||
- {% if cookiecutter.use_docker == 'y' %}docker{% else %}postgresql{% endif %}
|
||||
jobs:
|
||||
include:
|
||||
- name: "Linter"
|
||||
before_script:
|
||||
- pip install -q flake8
|
||||
script:
|
||||
- "flake8"
|
||||
|
||||
- name: "Django Test"
|
||||
{%- if cookiecutter.use_docker == 'y' %}
|
||||
before_script:
|
||||
- docker-compose -v
|
||||
- docker -v
|
||||
- docker-compose -f local.yml build
|
||||
# Ensure celerybeat does not crash due to non-existent tables
|
||||
- docker-compose -f local.yml run --rm django python manage.py migrate
|
||||
- docker-compose -f local.yml up -d
|
||||
script:
|
||||
- "docker-compose -f local.yml run django pytest"
|
||||
after_failure:
|
||||
- docker-compose -f local.yml logs
|
||||
{%- else %}
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq build-essential gettext python-dev zlib1g-dev libpq-dev xvfb
|
||||
- sudo apt-get install -qq libjpeg8-dev libfreetype6-dev libwebp-dev
|
||||
- sudo apt-get install -qq graphviz-dev python-setuptools python3-dev python-virtualenv python-pip
|
||||
- sudo apt-get install -qq firefox automake libtool libreadline6 libreadline6-dev libreadline-dev
|
||||
- sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm
|
||||
language: python
|
||||
python:
|
||||
- "3.8"
|
||||
install:
|
||||
- pip install -r requirements/local.txt
|
||||
script:
|
||||
- "pytest"
|
||||
{%- endif %}
|
||||
|
|
|
@ -50,4 +50,196 @@ GNU General Public License for more details.
|
|||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
{% elif cookiecutter.open_source_license == 'Apache Software License 2.0' %}
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright {% now 'utc', '%Y' %} {{ cookiecutter.author_name }}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
{% endif %}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
release: python manage.py migrate
|
||||
{% if cookiecutter.use_async == "y" -%}
|
||||
web: gunicorn config.asgi:application -k uvicorn.workers.UvicornWorker
|
||||
{%- else %}
|
||||
web: gunicorn config.wsgi:application
|
||||
{%- endif %}
|
||||
{% if cookiecutter.use_celery == "y" -%}
|
||||
worker: celery worker --app=config.celery_app --loglevel=info
|
||||
beat: celery beat --app=config.celery_app --loglevel=info
|
||||
{%- endif %}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
FROM python:3.7-slim-buster
|
||||
FROM python:3.8-slim-buster
|
||||
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
|
||||
RUN apt-get update \
|
||||
# dependencies for building Python packages
|
||||
|
|
|
@ -6,4 +6,8 @@ set -o nounset
|
|||
|
||||
|
||||
python manage.py migrate
|
||||
{%- if cookiecutter.use_async == 'y' %}
|
||||
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:8000 --chdir=/app -k uvicorn.workers.UvicornWorker --reload
|
||||
{%- else %}
|
||||
python manage.py runserver_plus 0.0.0.0:8000
|
||||
{% endif %}
|
||||
|
|
|
@ -9,7 +9,7 @@ RUN npm run build
|
|||
|
||||
# Python build stage
|
||||
{%- endif %}
|
||||
FROM python:3.7-slim-buster
|
||||
FROM python:3.8-slim-buster
|
||||
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
|
|
|
@ -6,4 +6,29 @@ 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 %}
|
||||
{% if cookiecutter.use_async == 'y' %}
|
||||
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:5000 --chdir=/app -k uvicorn.workers.UvicornWorker
|
||||
{% else %}
|
||||
/usr/local/bin/gunicorn config.wsgi --bind 0.0.0.0:5000 --chdir=/app
|
||||
{%- endif %}
|
||||
|
|
|
@ -9,6 +9,11 @@ entryPoints:
|
|||
web-secure:
|
||||
# https
|
||||
address: ":443"
|
||||
{%- if cookiecutter.use_celery == 'y' %}
|
||||
|
||||
flower:
|
||||
address: ":5555"
|
||||
{%- endif %}
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
|
@ -23,7 +28,7 @@ certificatesResolvers:
|
|||
http:
|
||||
routers:
|
||||
web-router:
|
||||
rule: "Host(`{{ cookiecutter.domain_name }}`)"
|
||||
rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)"
|
||||
entryPoints:
|
||||
- web
|
||||
middlewares:
|
||||
|
@ -32,7 +37,7 @@ http:
|
|||
service: django
|
||||
|
||||
web-secure-router:
|
||||
rule: "Host(`{{ cookiecutter.domain_name }}`)"
|
||||
rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)"
|
||||
entryPoints:
|
||||
- web-secure
|
||||
middlewares:
|
||||
|
@ -41,6 +46,17 @@ http:
|
|||
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:
|
||||
|
@ -52,13 +68,20 @@ http:
|
|||
# https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders
|
||||
# https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
|
||||
headers:
|
||||
hostsProxyHeaders: ['X-CSRFToken']
|
||||
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/
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from rest_framework.routers import DefaultRouter, SimpleRouter
|
||||
from django.conf import settings
|
||||
from rest_framework.routers import DefaultRouter, SimpleRouter
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet
|
||||
|
||||
if settings.DEBUG:
|
||||
|
|
40
{{cookiecutter.project_slug}}/config/asgi.py
Normal file
40
{{cookiecutter.project_slug}}/config/asgi.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
"""
|
||||
ASGI config for {{ cookiecutter.project_name }} project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/dev/howto/deployment/asgi/
|
||||
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
# This allows easy placement of apps within the interior
|
||||
# {{ cookiecutter.project_slug }} directory.
|
||||
app_path = Path(__file__).parents[1].resolve()
|
||||
sys.path.append(str(app_path / "{{ cookiecutter.project_slug }}"))
|
||||
|
||||
# If DJANGO_SETTINGS_MODULE is unset, default to the local settings
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
|
||||
|
||||
# This application object is used by any ASGI server configured to use this file.
|
||||
django_application = get_asgi_application()
|
||||
# Apply ASGI middleware here.
|
||||
# from helloworld.asgi import HelloWorldApplication
|
||||
# application = HelloWorldApplication(application)
|
||||
|
||||
# Import websocket application here, so apps from django_application are loaded first
|
||||
from config.websocket import websocket_application # noqa isort:skip
|
||||
|
||||
|
||||
async def application(scope, receive, send):
|
||||
if scope["type"] == "http":
|
||||
await django_application(scope, receive, send)
|
||||
elif scope["type"] == "websocket":
|
||||
await websocket_application(scope, receive, send)
|
||||
else:
|
||||
raise NotImplementedError(f"Unknown scope type {scope['type']}")
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
|
||||
from celery import Celery
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
"""
|
||||
Base settings to build other settings files upon.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
import environ
|
||||
|
||||
ROOT_DIR = (
|
||||
environ.Path(__file__) - 3
|
||||
) # ({{ cookiecutter.project_slug }}/config/settings/base.py - 3 = {{ cookiecutter.project_slug }}/)
|
||||
APPS_DIR = ROOT_DIR.path("{{ cookiecutter.project_slug }}")
|
||||
|
||||
ROOT_DIR = Path(__file__).parents[2]
|
||||
# {{ cookiecutter.project_slug }}/)
|
||||
APPS_DIR = ROOT_DIR / "{{ cookiecutter.project_slug }}"
|
||||
env = environ.Env()
|
||||
|
||||
READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False)
|
||||
if READ_DOT_ENV_FILE:
|
||||
# OS environment variables take precedence over variables from .env
|
||||
env.read_env(str(ROOT_DIR.path(".env")))
|
||||
env.read_env(str(ROOT_DIR / ".env"))
|
||||
|
||||
# GENERAL
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -36,7 +35,7 @@ USE_L10N = True
|
|||
# https://docs.djangoproject.com/en/dev/ref/settings/#use-tz
|
||||
USE_TZ = True
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths
|
||||
LOCALE_PATHS = [ROOT_DIR.path("locale")]
|
||||
LOCALE_PATHS = [str(ROOT_DIR / "locale")]
|
||||
|
||||
# DATABASES
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -75,10 +74,13 @@ THIRD_PARTY_APPS = [
|
|||
"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 = [
|
||||
|
@ -148,11 +150,11 @@ MIDDLEWARE = [
|
|||
# STATIC
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#static-root
|
||||
STATIC_ROOT = str(ROOT_DIR("staticfiles"))
|
||||
STATIC_ROOT = str(ROOT_DIR / "staticfiles")
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#static-url
|
||||
STATIC_URL = "/static/"
|
||||
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
|
||||
STATICFILES_DIRS = [str(APPS_DIR.path("static"))]
|
||||
STATICFILES_DIRS = [str(APPS_DIR / "static")]
|
||||
# https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
|
||||
STATICFILES_FINDERS = [
|
||||
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||
|
@ -162,7 +164,7 @@ STATICFILES_FINDERS = [
|
|||
# MEDIA
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#media-root
|
||||
MEDIA_ROOT = str(APPS_DIR("media"))
|
||||
MEDIA_ROOT = str(APPS_DIR / "media")
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#media-url
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
|
@ -174,7 +176,7 @@ TEMPLATES = [
|
|||
# https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
|
||||
"DIRS": [str(APPS_DIR.path("templates"))],
|
||||
"DIRS": [str(APPS_DIR / "templates")],
|
||||
"OPTIONS": {
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
|
||||
# https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
|
||||
|
@ -207,7 +209,7 @@ CRISPY_TEMPLATE_PACK = "bootstrap4"
|
|||
# FIXTURES
|
||||
# ------------------------------------------------------------------------------
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs
|
||||
FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),)
|
||||
FIXTURE_DIRS = (str(APPS_DIR / "fixtures"),)
|
||||
|
||||
# SECURITY
|
||||
# ------------------------------------------------------------------------------
|
||||
|
@ -226,7 +228,7 @@ X_FRAME_OPTIONS = "DENY"
|
|||
EMAIL_BACKEND = env(
|
||||
"DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend"
|
||||
)
|
||||
# https://docs.djangoproject.com/en/2.2/ref/settings/#email-timeout
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#email-timeout
|
||||
EMAIL_TIMEOUT = 5
|
||||
|
||||
# ADMIN
|
||||
|
@ -309,7 +311,7 @@ INSTALLED_APPS += ["compressor"]
|
|||
STATICFILES_FINDERS += ["compressor.finders.CompressorFinder"]
|
||||
{%- endif %}
|
||||
{% if cookiecutter.use_drf == "y" -%}
|
||||
# django-reset-framework
|
||||
# django-rest-framework
|
||||
# -------------------------------------------------------------------------------
|
||||
# django-rest-framework - https://www.django-rest-framework.org/api-guide/settings/
|
||||
REST_FRAMEWORK = {
|
||||
|
|
|
@ -68,7 +68,7 @@ if env("USE_DOCKER") == "yes":
|
|||
import socket
|
||||
|
||||
hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
|
||||
INTERNAL_IPS += [ip[:-1] + "1" for ip in ips]
|
||||
INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips]
|
||||
{%- endif %}
|
||||
|
||||
# django-extensions
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import logging
|
||||
|
||||
import sentry_sdk
|
||||
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.logging import LoggingIntegration
|
||||
{%- if cookiecutter.use_celery == 'y' %}
|
||||
|
@ -103,11 +102,11 @@ GS_DEFAULT_ACL = "publicRead"
|
|||
{% if cookiecutter.use_whitenoise == 'y' -%}
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
{% elif cookiecutter.cloud_provider == 'AWS' -%}
|
||||
STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage"
|
||||
STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.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"
|
||||
STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootGoogleCloudStorage"
|
||||
COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy"
|
||||
STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/"
|
||||
{% endif -%}
|
||||
|
@ -115,39 +114,10 @@ STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/"
|
|||
# MEDIA
|
||||
# ------------------------------------------------------------------------------
|
||||
{%- if cookiecutter.cloud_provider == 'AWS' %}
|
||||
# region http://stackoverflow.com/questions/10390244/
|
||||
# Full-fledge class: https://stackoverflow.com/a/18046120/104731
|
||||
from storages.backends.s3boto3 import S3Boto3Storage # noqa E402
|
||||
|
||||
|
||||
class StaticRootS3Boto3Storage(S3Boto3Storage):
|
||||
location = "static"
|
||||
default_acl = "public-read"
|
||||
|
||||
|
||||
class MediaRootS3Boto3Storage(S3Boto3Storage):
|
||||
location = "media"
|
||||
file_overwrite = False
|
||||
|
||||
|
||||
# endregion
|
||||
DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage"
|
||||
DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.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"
|
||||
DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootGoogleCloudStorage"
|
||||
MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"
|
||||
{%- endif %}
|
||||
|
||||
|
@ -182,17 +152,80 @@ 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_compressor == 'y' -%}
|
||||
# django-compressor
|
||||
|
@ -200,9 +233,27 @@ ANYMAIL = {
|
|||
# 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
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
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
|
||||
{%- if cookiecutter.use_async == 'y' %}
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
{%- endif %}
|
||||
from django.urls import include, path
|
||||
from django.views import defaults as default_views
|
||||
{% if cookiecutter.use_drf == 'y' -%}
|
||||
from django.views.generic import TemplateView
|
||||
{%- if cookiecutter.use_drf == 'y' %}
|
||||
from rest_framework.authtoken.views import obtain_auth_token
|
||||
{%- endif %}
|
||||
|
||||
|
@ -20,7 +23,12 @@ 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' -%}
|
||||
{%- if cookiecutter.use_async == 'y' %}
|
||||
if settings.DEBUG:
|
||||
# Static file serving when using Gunicorn + Uvicorn for local web socket development
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
{%- endif %}
|
||||
{% if cookiecutter.use_drf == 'y' %}
|
||||
# API URLS
|
||||
urlpatterns += [
|
||||
# API base url
|
||||
|
|
13
{{cookiecutter.project_slug}}/config/websocket.py
Normal file
13
{{cookiecutter.project_slug}}/config/websocket.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
async def websocket_application(scope, receive, send):
|
||||
while True:
|
||||
event = await receive()
|
||||
|
||||
if event["type"] == "websocket.connect":
|
||||
await send({"type": "websocket.accept"})
|
||||
|
||||
if event["type"] == "websocket.disconnect":
|
||||
break
|
||||
|
||||
if event["type"] == "websocket.receive":
|
||||
if event["text"] == "ping":
|
||||
await send({"type": "websocket.send", "text": "pong!"})
|
|
@ -15,15 +15,14 @@ framework.
|
|||
"""
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
# This allows easy placement of apps within the interior
|
||||
# {{ cookiecutter.project_slug }} directory.
|
||||
app_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
|
||||
)
|
||||
sys.path.append(os.path.join(app_path, "{{ cookiecutter.project_slug }}"))
|
||||
app_path = Path(__file__).parents[1].resolve()
|
||||
sys.path.append(str(app_path / "{{ cookiecutter.project_slug }}"))
|
||||
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
|
||||
# if running multiple sites in the same mod_wsgi process. To fix this, use
|
||||
# mod_wsgi daemon mode with each site in its own daemon process, or use
|
||||
|
|
|
@ -14,7 +14,7 @@ This repository comes with already prepared "Run/Debug Configurations" for docke
|
|||
|
||||
.. image:: images/2.png
|
||||
|
||||
But as you can see, at the beginning there is something wrong with them. They have red X on django icon, and they cannot be used, without configuring remote python interpteter. To do that, you have to go to *Settings > Build, Execution, Deployment* first.
|
||||
But as you can see, at the beginning there is something wrong with them. They have red X on django icon, and they cannot be used, without configuring remote python interpreter. To do that, you have to go to *Settings > Build, Execution, Deployment* first.
|
||||
|
||||
|
||||
Next, you have to add new remote python interpreter, based on already tested deployment settings. Go to *Settings > Project > Project Interpreter*. Click on the cog icon, and click *Add Remote*.
|
||||
|
|
|
@ -110,6 +110,18 @@ function imgCompression() {
|
|||
.pipe(dest(paths.images))
|
||||
}
|
||||
|
||||
{% if cookiecutter.use_async == 'y' -%}
|
||||
// Run django server
|
||||
function asyncRunServer() {
|
||||
var cmd = spawn('gunicorn', [
|
||||
'config.asgi', '-k', 'uvicorn.workers.UvicornWorker', '--reload'
|
||||
], {stdio: 'inherit'}
|
||||
)
|
||||
cmd.on('close', function(code) {
|
||||
console.log('gunicorn exited with code ' + code)
|
||||
})
|
||||
}
|
||||
{%- else %}
|
||||
// Run django server
|
||||
function runServer(cb) {
|
||||
var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'})
|
||||
|
@ -118,6 +130,7 @@ function runServer(cb) {
|
|||
cb(code)
|
||||
})
|
||||
}
|
||||
{%- endif %}
|
||||
|
||||
// Browser sync server for live reload
|
||||
function initBrowserSync() {
|
||||
|
@ -166,8 +179,12 @@ const generateAssets = parallel(
|
|||
// Set up dev environment
|
||||
const dev = parallel(
|
||||
{%- if cookiecutter.use_docker == 'n' %}
|
||||
{%- if cookiecutter.use_async == 'y' %}
|
||||
asyncRunServer,
|
||||
{%- else %}
|
||||
runServer,
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
initBrowserSync,
|
||||
watchPaths
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
|
||||
|
@ -24,7 +25,7 @@ if __name__ == "__main__":
|
|||
|
||||
# This allows easy placement of apps within the interior
|
||||
# {{ cookiecutter.project_slug }} directory.
|
||||
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(os.path.join(current_path, "{{ cookiecutter.project_slug }}"))
|
||||
current_path = Path(__file__).parent.resolve()
|
||||
sys.path.append(str(current_path / "{{ cookiecutter.project_slug }}"))
|
||||
|
||||
execute_from_command_line(sys.argv)
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import os
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT_DIR_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
PRODUCTION_DOTENVS_DIR_PATH = os.path.join(ROOT_DIR_PATH, ".envs", ".production")
|
||||
ROOT_DIR_PATH = Path(__file__).parent.resolve()
|
||||
PRODUCTION_DOTENVS_DIR_PATH = ROOT_DIR_PATH / ".envs" / ".production"
|
||||
PRODUCTION_DOTENV_FILE_PATHS = [
|
||||
os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".django"),
|
||||
os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".postgres"),
|
||||
PRODUCTION_DOTENVS_DIR_PATH / ".django",
|
||||
PRODUCTION_DOTENVS_DIR_PATH / ".postgres",
|
||||
]
|
||||
DOTENV_FILE_PATH = os.path.join(ROOT_DIR_PATH, ".env")
|
||||
DOTENV_FILE_PATH = ROOT_DIR_PATH / ".env"
|
||||
|
||||
|
||||
def merge(
|
||||
|
@ -31,9 +32,9 @@ def main():
|
|||
@pytest.mark.parametrize("merged_file_count", range(3))
|
||||
@pytest.mark.parametrize("append_linesep", [True, False])
|
||||
def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool):
|
||||
tmp_dir_path = str(tmpdir_factory.getbasetemp())
|
||||
tmp_dir_path = Path(str(tmpdir_factory.getbasetemp()))
|
||||
|
||||
output_file_path = os.path.join(tmp_dir_path, ".env")
|
||||
output_file_path = tmp_dir_path / ".env"
|
||||
|
||||
expected_output_file_content = ""
|
||||
merged_file_paths = []
|
||||
|
@ -41,7 +42,7 @@ def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool):
|
|||
merged_file_ord = i + 1
|
||||
|
||||
merged_filename = ".service{}".format(merged_file_ord)
|
||||
merged_file_path = os.path.join(tmp_dir_path, merged_filename)
|
||||
merged_file_path = tmp_dir_path / merged_filename
|
||||
|
||||
merged_file_content = merged_filename * merged_file_ord
|
||||
|
||||
|
|
|
@ -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,8 +63,6 @@ services:
|
|||
flower:
|
||||
<<: *django
|
||||
image: {{ cookiecutter.project_slug }}_production_flower
|
||||
ports:
|
||||
- "5555:5555"
|
||||
command: /start-flower
|
||||
|
||||
{%- endif %}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
Pillow==7.1.1 # 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 %}
|
||||
|
@ -8,27 +8,31 @@ argon2-cffi==19.2.0 # https://github.com/hynek/argon2_cffi
|
|||
{%- if cookiecutter.use_whitenoise == 'y' %}
|
||||
whitenoise==5.0.1 # https://github.com/evansd/whitenoise
|
||||
{%- endif %}
|
||||
redis==3.3.11 # pyup: != 3.4.0 # https://github.com/andymccurdy/redis-py
|
||||
redis==3.4.1 # https://github.com/andymccurdy/redis-py
|
||||
{%- if cookiecutter.use_celery == "y" %}
|
||||
celery==4.4.0 # pyup: < 5.0 # https://github.com/celery/celery
|
||||
django-celery-beat==1.6.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
|
||||
flower==0.9.4 # https://github.com/mher/flower
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- if cookiecutter.use_async == 'y' %}
|
||||
uvicorn==0.11.3 # https://github.com/encode/uvicorn
|
||||
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
|
||||
{%- endif %}
|
||||
|
||||
# Django
|
||||
# ------------------------------------------------------------------------------
|
||||
django==2.2.9 # pyup: < 3.0 # https://www.djangoproject.com/
|
||||
django==3.0.5 # pyup: < 3.1 # https://www.djangoproject.com/
|
||||
django-environ==0.4.5 # https://github.com/joke2k/django-environ
|
||||
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.8.1 # https://github.com/django-crispy-forms/django-crispy-forms
|
||||
django-crispy-forms==1.9.0 # https://github.com/django-crispy-forms/django-crispy-forms
|
||||
{%- if cookiecutter.use_compressor == "y" %}
|
||||
django-compressor==2.4 # https://github.com/django-compressor/django-compressor
|
||||
{%- endif %}
|
||||
django-redis==4.11.0 # https://github.com/niwinz/django-redis
|
||||
|
||||
{%- if cookiecutter.use_drf == "y" %}
|
||||
# Django REST Framework
|
||||
djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework
|
||||
coreapi==2.3.3 # https://github.com/core-api/python-client
|
||||
{%- endif %}
|
||||
|
|
|
@ -1,37 +1,38 @@
|
|||
-r ./base.txt
|
||||
|
||||
Werkzeug==0.16.1 # https://github.com/pallets/werkzeug
|
||||
ipdb==0.12.3 # https://github.com/gotcha/ipdb
|
||||
Sphinx==2.3.1 # https://github.com/sphinx-doc/sphinx
|
||||
Werkzeug==1.0.1 # https://github.com/pallets/werkzeug
|
||||
ipdb==0.13.2 # https://github.com/gotcha/ipdb
|
||||
Sphinx==3.0.2 # https://github.com/sphinx-doc/sphinx
|
||||
{%- if cookiecutter.use_docker == 'y' %}
|
||||
psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||
psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||
{%- else %}
|
||||
psycopg2-binary==2.8.4 # https://github.com/psycopg/psycopg2
|
||||
psycopg2-binary==2.8.5 # https://github.com/psycopg/psycopg2
|
||||
{%- endif %}
|
||||
|
||||
# Testing
|
||||
# ------------------------------------------------------------------------------
|
||||
mypy==0.761 # https://github.com/python/mypy
|
||||
django-stubs==1.4.0 # https://github.com/typeddjango/django-stubs
|
||||
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.9 # https://github.com/PyCQA/flake8
|
||||
coverage==5.0.3 # https://github.com/nedbat/coveragepy
|
||||
flake8-isort==3.0.0 # https://github.com/gforcada/flake8-isort
|
||||
coverage==5.1 # https://github.com/nedbat/coveragepy
|
||||
black==19.10b0 # https://github.com/ambv/black
|
||||
pylint-django==2.0.13 # https://github.com/PyCQA/pylint-django
|
||||
pylint-django==2.0.15 # 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.0.1 # https://github.com/pre-commit/pre-commit
|
||||
pre-commit==2.3.0 # https://github.com/pre-commit/pre-commit
|
||||
|
||||
# Django
|
||||
# ------------------------------------------------------------------------------
|
||||
factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy
|
||||
|
||||
django-debug-toolbar==2.2 # https://github.com/jazzband/django-debug-toolbar
|
||||
django-extensions==2.2.6 # https://github.com/django-extensions/django-extensions
|
||||
django-extensions==2.2.9 # 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
|
||||
pytest-django==3.9.0 # https://github.com/pytest-dev/pytest-django
|
||||
|
|
|
@ -2,20 +2,40 @@
|
|||
|
||||
-r ./base.txt
|
||||
|
||||
{%- if cookiecutter.use_async == 'n' %}
|
||||
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
|
||||
psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||
{%- endif %}
|
||||
psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
|
||||
{%- if cookiecutter.use_whitenoise == 'n' %}
|
||||
Collectfast==1.3.1 # https://github.com/antonagestam/collectfast
|
||||
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast
|
||||
{%- endif %}
|
||||
{%- if cookiecutter.use_sentry == "y" %}
|
||||
sentry-sdk==0.14.1 # https://github.com/getsentry/sentry-python
|
||||
sentry-sdk==0.14.3 # https://github.com/getsentry/sentry-python
|
||||
{%- endif %}
|
||||
|
||||
# Django
|
||||
# ------------------------------------------------------------------------------
|
||||
{%- if cookiecutter.cloud_provider == 'AWS' %}
|
||||
django-storages[boto3]==1.8 # 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.8 # 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.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- elif cookiecutter.mail_service == 'Amazon SES' %}
|
||||
django-anymail[amazon_ses]==7.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- elif cookiecutter.mail_service == 'Mailjet' %}
|
||||
django-anymail[mailjet]==7.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- elif cookiecutter.mail_service == 'Mandrill' %}
|
||||
django-anymail[mandrill]==7.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- elif cookiecutter.mail_service == 'Postmark' %}
|
||||
django-anymail[postmark]==7.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- elif cookiecutter.mail_service == 'Sendgrid' %}
|
||||
django-anymail[sendgrid]==7.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- elif cookiecutter.mail_service == 'SendinBlue' %}
|
||||
django-anymail[sendinblue]==7.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- elif cookiecutter.mail_service == 'SparkPost' %}
|
||||
django-anymail[sparkpost]==7.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- elif cookiecutter.mail_service == 'Other SMTP' %}
|
||||
django-anymail==7.1.0 # https://github.com/anymail/django-anymail
|
||||
{%- endif %}
|
||||
django-anymail[mailgun]==7.0.0 # https://github.com/anymail/django-anymail
|
||||
|
|
|
@ -1 +1 @@
|
|||
python-3.7.6
|
||||
python-3.8.2
|
||||
|
|
|
@ -7,7 +7,7 @@ max-line-length = 120
|
|||
exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules
|
||||
|
||||
[mypy]
|
||||
python_version = 3.7
|
||||
python_version = 3.8
|
||||
check_untyped_defs = True
|
||||
ignore_missing_imports = True
|
||||
warn_unused_ignores = True
|
||||
|
@ -21,3 +21,9 @@ django_settings_module = config.settings.test
|
|||
[mypy-*.migrations.*]
|
||||
# Django migrations should not produce any errors:
|
||||
ignore_errors = True
|
||||
|
||||
[coverage:run]
|
||||
include = {{cookiecutter.project_slug}}/*
|
||||
omit = *migrations*, *tests*
|
||||
plugins =
|
||||
django_coverage_plugin
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import pytest
|
||||
from django.test import RequestFactory
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.models import User
|
||||
from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
|
||||
|
@ -13,8 +12,3 @@ def media_storage(settings, tmpdir):
|
|||
@pytest.fixture
|
||||
def user() -> User:
|
||||
return UserFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def request_factory() -> RequestFactory:
|
||||
return RequestFactory()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from rest_framework import serializers
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.models import User
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.mixins import RetrieveModelMixin, ListModelMixin, UpdateModelMixin
|
||||
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.viewsets import GenericViewSet
|
||||
|
||||
|
|
|
@ -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 _
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
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
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_user_count(settings):
|
||||
"""A basic test to execute the get_users_count Celery task."""
|
||||
UserFactory.create_batch(3)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import pytest
|
||||
from django.urls import reverse, resolve
|
||||
from django.urls import resolve, reverse
|
||||
|
||||
from {{ cookiecutter.project_slug }}.users.models import User
|
||||
|
||||
|
|
|
@ -16,18 +16,18 @@ class TestUserUpdateView:
|
|||
https://github.com/pytest-dev/pytest-django/pull/258
|
||||
"""
|
||||
|
||||
def test_get_success_url(self, user: User, request_factory: RequestFactory):
|
||||
def test_get_success_url(self, user: User, rf: RequestFactory):
|
||||
view = UserUpdateView()
|
||||
request = request_factory.get("/fake-url/")
|
||||
request = rf.get("/fake-url/")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
||||
assert view.get_success_url() == f"/users/{user.username}/"
|
||||
|
||||
def test_get_object(self, user: User, request_factory: RequestFactory):
|
||||
def test_get_object(self, user: User, rf: RequestFactory):
|
||||
view = UserUpdateView()
|
||||
request = request_factory.get("/fake-url/")
|
||||
request = rf.get("/fake-url/")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
@ -36,9 +36,9 @@ class TestUserUpdateView:
|
|||
|
||||
|
||||
class TestUserRedirectView:
|
||||
def test_get_redirect_url(self, user: User, request_factory: RequestFactory):
|
||||
def test_get_redirect_url(self, user: User, rf: RequestFactory):
|
||||
view = UserRedirectView()
|
||||
request = request_factory.get("/fake-url")
|
||||
request = rf.get("/fake-url")
|
||||
request.user = user
|
||||
|
||||
view.request = request
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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.views.generic import DetailView, RedirectView, UpdateView
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.views.generic import DetailView, RedirectView, UpdateView
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{% if cookiecutter.cloud_provider == 'AWS' -%}
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
|
||||
|
||||
class StaticRootS3Boto3Storage(S3Boto3Storage):
|
||||
location = "static"
|
||||
default_acl = "public-read"
|
||||
|
||||
|
||||
class MediaRootS3Boto3Storage(S3Boto3Storage):
|
||||
location = "media"
|
||||
file_overwrite = False
|
||||
{%- elif cookiecutter.cloud_provider == 'GCP' -%}
|
||||
from storages.backends.gcloud import GoogleCloudStorage
|
||||
|
||||
|
||||
class StaticRootGoogleCloudStorage(GoogleCloudStorage):
|
||||
location = "static"
|
||||
default_acl = "publicRead"
|
||||
|
||||
|
||||
class MediaRootGoogleCloudStorage(GoogleCloudStorage):
|
||||
location = "media"
|
||||
file_overwrite = False
|
||||
{%- endif %}
|
Loading…
Reference in New Issue
Block a user