Merge branch 'master' into docs

# Conflicts:
#	{{cookiecutter.project_slug}}/requirements/local.txt
This commit is contained in:
Bruno Alla 2020-06-29 21:48:17 +01:00
commit 196167490d
32 changed files with 238 additions and 94 deletions

11
.github/labeler.yml vendored
View File

@ -1,11 +0,0 @@
# 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'

View File

@ -1,20 +0,0 @@
# 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 }}"

View File

@ -13,8 +13,6 @@ pin: True
# default: empty # default: empty
label_prs: update label_prs: update
# Specify requirement files by hand, pyup seems to struggle to
# find the ones in the project_slug folder
requirements: requirements:
- "requirements.txt" - "requirements.txt"
- "{{cookiecutter.project_slug}}/requirements/base.txt" - "{{cookiecutter.project_slug}}/requirements/base.txt"

View File

@ -1,5 +1,3 @@
dist: xenial
services: services:
- docker - docker
@ -19,8 +17,8 @@ matrix:
script: tox -e black-template script: tox -e black-template
- name: Basic Docker - name: Basic Docker
script: sh tests/test_docker.sh script: sh tests/test_docker.sh
- name: Docker with Celery - name: Extended Docker
script: sh tests/test_docker.sh use_celery=y script: sh tests/test_docker.sh use_celery=y use_drf=y
- name: Bare metal - name: Bare metal
script: sh tests/test_bare.sh use_celery=y use_compressor=y script: sh tests/test_bare.sh use_celery=y use_compressor=y
services: services:

View File

@ -65,6 +65,7 @@ Listed in alphabetical order.
Anuj Bansal `@ahhda`_ Anuj Bansal `@ahhda`_
Arcuri Davide `@dadokkio`_ Arcuri Davide `@dadokkio`_
Areski Belaid `@areski`_ Areski Belaid `@areski`_
AsheKR `@ashekr`_
Ashley Camba Ashley Camba
Barclay Gauld `@yunti`_ Barclay Gauld `@yunti`_
Bartek `@btknu`_ Bartek `@btknu`_
@ -146,6 +147,7 @@ Listed in alphabetical order.
Jerome Leclanche `@jleclanche`_ @Adys Jerome Leclanche `@jleclanche`_ @Adys
Jimmy Gitonga `@afrowave`_ @afrowave Jimmy Gitonga `@afrowave`_ @afrowave
John Cass `@jcass77`_ @cass_john John Cass `@jcass77`_ @cass_john
Jonathan Thompson `@nojanath`_
Jules Cheron `@jules-ch`_ Jules Cheron `@jules-ch`_
Julien Almarcha `@sladinji`_ Julien Almarcha `@sladinji`_
Julio Castillo `@juliocc`_ Julio Castillo `@juliocc`_
@ -161,6 +163,7 @@ Listed in alphabetical order.
Krzysztof Żuraw `@krzysztofzuraw`_ Krzysztof Żuraw `@krzysztofzuraw`_
Leo won `@leollon`_ Leo won `@leollon`_
Leo Zhou `@glasslion`_ Leo Zhou `@glasslion`_
Leon Kim `@PilhwanKim`_
Leonardo Jimenez `@xpostudio4`_ Leonardo Jimenez `@xpostudio4`_
Lin Xianyi `@iynaix`_ Lin Xianyi `@iynaix`_
Luis Nell `@originell`_ Luis Nell `@originell`_
@ -249,6 +252,7 @@ Listed in alphabetical order.
.. _@archinal: https://github.com/archinal .. _@archinal: https://github.com/archinal
.. _@areski: https://github.com/areski .. _@areski: https://github.com/areski
.. _@arruda: https://github.com/arruda .. _@arruda: https://github.com/arruda
.. _@ashekr: https://github.com/ashekr
.. _@bertdemiranda: https://github.com/bertdemiranda .. _@bertdemiranda: https://github.com/bertdemiranda
.. _@bittner: https://github.com/bittner .. _@bittner: https://github.com/bittner
.. _@blaxpy: https://github.com/blaxpy .. _@blaxpy: https://github.com/blaxpy
@ -312,6 +316,7 @@ Listed in alphabetical order.
.. _@hackebrot: https://github.com/hackebrot .. _@hackebrot: https://github.com/hackebrot
.. _@hairychris: https://github.com/hairychris .. _@hairychris: https://github.com/hairychris
.. _@hanaquadara: https://github.com/hanaquadara .. _@hanaquadara: https://github.com/hanaquadara
.. _@hanhanhan: https://github.com/hanhanhan
.. _@hendrikschneider: https://github.com/hendrikschneider .. _@hendrikschneider: https://github.com/hendrikschneider
.. _@highpost: https://github.com/highpost .. _@highpost: https://github.com/highpost
.. _@hjwp: https://github.com/hjwp .. _@hjwp: https://github.com/hjwp
@ -358,12 +363,14 @@ Listed in alphabetical order.
.. _@myilmaz: https://github.com/myilmaz .. _@myilmaz: https://github.com/myilmaz
.. _@nicolas471: https://github.com/nicolas471 .. _@nicolas471: https://github.com/nicolas471
.. _@noisy: https://github.com/noisy .. _@noisy: https://github.com/noisy
.. _@nojanath: https://github.com/nojanath
.. _@originell: https://github.com/originell .. _@originell: https://github.com/originell
.. _@oubiga: https://github.com/oubiga .. _@oubiga: https://github.com/oubiga
.. _@parbhat: https://github.com/parbhat .. _@parbhat: https://github.com/parbhat
.. _@rjsnh1522: https://github.com/rjsnh1522 .. _@rjsnh1522: https://github.com/rjsnh1522
.. _@pchiquet: https://github.com/pchiquet .. _@pchiquet: https://github.com/pchiquet
.. _@phiberjenz: https://github.com/phiberjenz .. _@phiberjenz: https://github.com/phiberjenz
.. _@PilhwanKim: https://github.com/PilhwanKim
.. _@purplediane: https://github.com/purplediane .. _@purplediane: https://github.com/purplediane
.. _@raonyguimaraes: https://github.com/raonyguimaraes .. _@raonyguimaraes: https://github.com/raonyguimaraes
.. _@reggieriser: https://github.com/reggieriser .. _@reggieriser: https://github.com/reggieriser

View File

@ -5,6 +5,10 @@ Cookiecutter Django
:target: https://travis-ci.org/pydanny/cookiecutter-django?branch=master :target: https://travis-ci.org/pydanny/cookiecutter-django?branch=master
:alt: Build Status :alt: Build Status
.. image:: https://readthedocs.org/projects/cookiecutter-django/badge/?version=latest
:target: https://cookiecutter-django.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
.. image:: https://pyup.io/repos/github/pydanny/cookiecutter-django/shield.svg .. image:: https://pyup.io/repos/github/pydanny/cookiecutter-django/shield.svg
:target: https://pyup.io/repos/github/pydanny/cookiecutter-django/ :target: https://pyup.io/repos/github/pydanny/cookiecutter-django/
:alt: Updates :alt: Updates

View File

@ -154,6 +154,15 @@ django-debug-toolbar
In order for ``django-debug-toolbar`` to work designate your Docker Machine IP with ``INTERNAL_IPS`` in ``local.py``. In order for ``django-debug-toolbar`` to work designate your Docker Machine IP with ``INTERNAL_IPS`` in ``local.py``.
docker
""""""
The ``container_name`` from the yml file can be used to check on containers with docker commands, for example: ::
$ docker logs worker
$ docker top worker
Mailhog Mailhog
~~~~~~~ ~~~~~~~

View File

@ -12,6 +12,7 @@ Make sure to have the following on your host:
* Python 3.8 * Python 3.8
* PostgreSQL_. * PostgreSQL_.
* Redis_, if using Celery * Redis_, if using Celery
* Cookiecutter_
First things first. First things first.
@ -23,9 +24,14 @@ First things first.
$ source <virtual env path>/bin/activate $ source <virtual env path>/bin/activate
#. Install cookiecutter-django
$ cookiecutter gh:pydanny/cookiecutter-django ::
#. Install development requirements: :: #. Install development requirements: ::
$ pip install -r requirements/local.txt $ pip install -r requirements/local.txt
$ git init # A git repo is required for pre-commit to install
$ pre-commit install $ pre-commit install
.. note:: .. note::
@ -74,10 +80,11 @@ First things first.
or if you're running asynchronously: :: or if you're running asynchronously: ::
$ gunicorn config.asgi --bind 0.0.0.0:8000 -k uvicorn.workers.UvicornWorker --reload $ uvicorn config.asgi:application --host 0.0.0.0 --reload
.. _PostgreSQL: https://www.postgresql.org/download/ .. _PostgreSQL: https://www.postgresql.org/download/
.. _Redis: https://redis.io/download .. _Redis: https://redis.io/download
.. _CookieCutter: https://github.com/cookiecutter/cookiecutter
.. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html .. _createdb: https://www.postgresql.org/docs/current/static/app-createdb.html
.. _initial PostgreSQL set up: http://suite.opengeo.org/docs/latest/dataadmin/pgGettingStarted/firstconnect.html .. _initial PostgreSQL set up: http://suite.opengeo.org/docs/latest/dataadmin/pgGettingStarted/firstconnect.html
.. _postgres documentation: https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html .. _postgres documentation: https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html

View File

@ -23,6 +23,7 @@ Contents:
deployment-on-heroku deployment-on-heroku
deployment-with-docker deployment-with-docker
docker-postgres-backups docker-postgres-backups
websocket
faq faq
troubleshooting troubleshooting

View File

@ -45,6 +45,7 @@ DJANGO_AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID n/a
DJANGO_AWS_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY n/a raises error DJANGO_AWS_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY n/a raises error
DJANGO_AWS_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a raises error DJANGO_AWS_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a raises error
DJANGO_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None DJANGO_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None
DJANGO_AWS_S3_CUSTOM_DOMAIN AWS_S3_CUSTOM_DOMAIN n/a None
DJANGO_GCP_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error DJANGO_GCP_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error
GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error
SENTRY_DSN SENTRY_DSN n/a raises error SENTRY_DSN SENTRY_DSN n/a raises error

25
docs/websocket.rst Normal file
View File

@ -0,0 +1,25 @@
.. _websocket:
=========
Websocket
=========
You can enable web sockets if you select ``use_async`` option when creating a project. That indicates whether the project can use web sockets with Uvicorn + Gunicorn.
Usage
-----
JavaScript example: ::
> ws = new WebSocket('ws://localhost:8000/') // or 'wss://<mydomain.com>/' in prod
WebSocket {url: "ws://localhost:8000/", readyState: 0, bufferedAmount: 0, onopen: null, onerror: null, …}
> ws.onmessage = event => console.log(event.data)
event => console.log(event.data)
> ws.send("ping")
undefined
pong!
If you don't use Traefik, you might have to configure your reverse proxy accordingly (example with Nginx_).
.. _Nginx: https://www.nginx.com/blog/websocket-nginx/

View File

@ -299,6 +299,16 @@ def remove_aws_dockerfile():
def remove_drf_starter_files(): def remove_drf_starter_files():
os.remove(os.path.join("config", "api_router.py")) os.remove(os.path.join("config", "api_router.py"))
shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "users", "api")) shutil.rmtree(os.path.join("{{cookiecutter.project_slug}}", "users", "api"))
os.remove(
os.path.join(
"{{cookiecutter.project_slug}}", "users", "tests", "test_drf_urls.py"
)
)
os.remove(
os.path.join(
"{{cookiecutter.project_slug}}", "users", "tests", "test_drf_views.py"
)
)
def remove_storages_module(): def remove_storages_module():

View File

@ -1,17 +1,17 @@
cookiecutter==1.7.2 cookiecutter==1.7.2
sh==1.12.14 sh==1.13.1
binaryornot==0.4.4 binaryornot==0.4.4
# Code quality # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
black==19.10b0 black==19.10b0
flake8==3.7.9 flake8==3.8.3
flake8-isort==3.0.0 flake8-isort==3.0.0
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
tox==3.14.6 tox==3.16.0
pytest==5.4.1 pytest==5.4.3
pytest-cookies==0.5.1 pytest-cookies==0.5.1
pytest-instafail==0.4.1.post0 pytest-instafail==0.4.2
pyyaml==5.3.1 pyyaml==5.3.1

View File

@ -10,7 +10,7 @@ except ImportError:
# Our version ALWAYS matches the version of Django we support # Our version ALWAYS matches the version of Django we support
# If Django has a new release, we branch, tag, then update this setting after the tag. # If Django has a new release, we branch, tag, then update this setting after the tag.
version = "3.0.5-01" version = "3.0.7"
if sys.argv[-1] == "tag": if sys.argv[-1] == "tag":
os.system(f'git tag -a {version} -m "version {version}"') os.system(f'git tag -a {version} -m "version {version}"')

View File

@ -156,7 +156,7 @@ def test_flake8_passes(cookies, context_override):
result = cookies.bake(extra_context=context_override) result = cookies.bake(extra_context=context_override)
try: try:
sh.flake8(str(result.project)) sh.flake8(_cwd=str(result.project))
except sh.ErrorReturnCode as e: except sh.ErrorReturnCode as e:
pytest.fail(e.stdout.decode()) pytest.fail(e.stdout.decode())
@ -167,7 +167,9 @@ def test_black_passes(cookies, context_override):
result = cookies.bake(extra_context=context_override) result = cookies.bake(extra_context=context_override)
try: try:
sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/") sh.black(
"--check", "--diff", "--exclude", "migrations", _cwd=str(result.project)
)
except sh.ErrorReturnCode as e: except sh.ErrorReturnCode as e:
pytest.fail(e.stdout.decode()) pytest.fail(e.stdout.decode())

View File

@ -23,10 +23,11 @@ pytest:
stage: test stage: test
image: python:3.7 image: python:3.7
{% if cookiecutter.use_docker == 'y' -%} {% if cookiecutter.use_docker == 'y' -%}
image: docker/compose:latest
tags: tags:
- docker - docker
services: services:
- docker - docker:dind
before_script: before_script:
- docker-compose -f local.yml build - docker-compose -f local.yml build
# Ensure celerybeat does not crash due to non-existent tables # Ensure celerybeat does not crash due to non-existent tables

View File

@ -3,18 +3,22 @@ default_stages: [commit]
fail_fast: true fail_fast: true
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: master rev: master
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
files: (^|/).+\.(py|html|sh|css|js)$ - id: end-of-file-fixer
- id: check-yaml
- repo: local - repo: https://github.com/psf/black
rev: 19.10b0
hooks:
- id: black
- repo: https://gitlab.com/pycqa/flake8
rev: 3.8.3
hooks: hooks:
- id: flake8 - id: flake8
name: flake8
entry: flake8
language: python
types: [python]
args: ['--config=setup.cfg'] args: ['--config=setup.cfg']
additional_dependencies: [flake8-isort]

View File

@ -7,7 +7,7 @@ set -o nounset
python manage.py migrate python manage.py migrate
{%- if cookiecutter.use_async == 'y' %} {%- if cookiecutter.use_async == 'y' %}
/usr/local/bin/gunicorn config.asgi --bind 0.0.0.0:8000 --chdir=/app -k uvicorn.workers.UvicornWorker --reload uvicorn config.asgi:application --host 0.0.0.0 --reload
{%- else %} {%- else %}
python manage.py runserver_plus 0.0.0.0:8000 python manage.py runserver_plus 0.0.0.0:8000
{% endif %} {% endif %}

View File

@ -26,10 +26,9 @@ certificatesResolvers:
entryPoint: web entryPoint: web
http: http:
{%- set domain_dots = cookiecutter.domain_name.count('.') %}
routers: routers:
web-router: web-router:
{%- if domain_dots == 1 or (domain_dots == 2 and '.co.' in cookiecutter.domain_name) %} {%- if cookiecutter.domain_name.count('.') == 1 %}
rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)" rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)"
{% else %} {% else %}
rule: "Host(`{{ cookiecutter.domain_name }}`)" rule: "Host(`{{ cookiecutter.domain_name }}`)"
@ -42,7 +41,7 @@ http:
service: django service: django
web-secure-router: web-secure-router:
{%- if domain_dots == 1 or (domain_dots == 2 and '.co.' in cookiecutter.domain_name) %} {%- if cookiecutter.domain_name.count('.') == 1 %}
rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)" rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)"
{% else %} {% else %}
rule: "Host(`{{ cookiecutter.domain_name }}`)" rule: "Host(`{{ cookiecutter.domain_name }}`)"

View File

@ -15,8 +15,8 @@ from django.core.asgi import get_asgi_application
# This allows easy placement of apps within the interior # This allows easy placement of apps within the interior
# {{ cookiecutter.project_slug }} directory. # {{ cookiecutter.project_slug }} directory.
app_path = Path(__file__).parents[1].resolve() ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
sys.path.append(str(app_path / "{{ cookiecutter.project_slug }}")) sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}"))
# If DJANGO_SETTINGS_MODULE is unset, default to the local settings # If DJANGO_SETTINGS_MODULE is unset, default to the local settings
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")

View File

@ -5,8 +5,8 @@ from pathlib import Path
import environ import environ
ROOT_DIR = Path(__file__).parents[2] ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
# {{ cookiecutter.project_slug }}/) # {{ cookiecutter.project_slug }}/
APPS_DIR = ROOT_DIR / "{{ cookiecutter.project_slug }}" APPS_DIR = ROOT_DIR / "{{ cookiecutter.project_slug }}"
env = environ.Env() env = environ.Env()

View File

@ -34,7 +34,7 @@ CACHES = {
"OPTIONS": { "OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient", "CLIENT_CLASS": "django_redis.client.DefaultClient",
# Mimicing memcache behavior. # Mimicing memcache behavior.
# http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior # http://jazzband.github.io/django-redis/latest/#_memcached_exceptions_behavior
"IGNORE_EXCEPTIONS": True, "IGNORE_EXCEPTIONS": True,
}, },
} }
@ -90,6 +90,9 @@ AWS_S3_OBJECT_PARAMETERS = {
AWS_DEFAULT_ACL = None AWS_DEFAULT_ACL = None
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings
AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None)
# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#cloudfront
AWS_S3_CUSTOM_DOMAIN = env("DJANGO_AWS_S3_CUSTOM_DOMAIN", default=None)
aws_s3_domain = AWS_S3_CUSTOM_DOMAIN or f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com"
{% elif cookiecutter.cloud_provider == 'GCP' %} {% elif cookiecutter.cloud_provider == 'GCP' %}
GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME") GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME")
GS_DEFAULT_ACL = "publicRead" GS_DEFAULT_ACL = "publicRead"
@ -104,7 +107,7 @@ STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
{% elif cookiecutter.cloud_provider == 'AWS' -%} {% elif cookiecutter.cloud_provider == 'AWS' -%}
STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootS3Boto3Storage" STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootS3Boto3Storage"
COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy" COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy"
STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/" STATIC_URL = f"https://{aws_s3_domain}/static/"
{% elif cookiecutter.cloud_provider == 'GCP' -%} {% elif cookiecutter.cloud_provider == 'GCP' -%}
STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootGoogleCloudStorage" STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootGoogleCloudStorage"
COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy" COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy"
@ -115,7 +118,7 @@ STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/"
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
{%- if cookiecutter.cloud_provider == 'AWS' %} {%- if cookiecutter.cloud_provider == 'AWS' %}
DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootS3Boto3Storage" DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootS3Boto3Storage"
MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/" MEDIA_URL = f"https://{aws_s3_domain}/media/"
{%- elif cookiecutter.cloud_provider == 'GCP' %} {%- elif cookiecutter.cloud_provider == 'GCP' %}
DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootGoogleCloudStorage" DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootGoogleCloudStorage"
MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/"

View File

@ -21,8 +21,8 @@ from django.core.wsgi import get_wsgi_application
# This allows easy placement of apps within the interior # This allows easy placement of apps within the interior
# {{ cookiecutter.project_slug }} directory. # {{ cookiecutter.project_slug }} directory.
app_path = Path(__file__).parents[1].resolve() ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent
sys.path.append(str(app_path / "{{ cookiecutter.project_slug }}")) sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}"))
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks # 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 # 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 # mod_wsgi daemon mode with each site in its own daemon process, or use

View File

@ -10,6 +10,7 @@ services:
context: . context: .
dockerfile: ./compose/local/django/Dockerfile dockerfile: ./compose/local/django/Dockerfile
image: {{ cookiecutter.project_slug }}_local_django image: {{ cookiecutter.project_slug }}_local_django
container_name: django
depends_on: depends_on:
- postgres - postgres
{%- if cookiecutter.use_mailhog == 'y' %} {%- if cookiecutter.use_mailhog == 'y' %}
@ -29,6 +30,7 @@ services:
context: . context: .
dockerfile: ./compose/production/postgres/Dockerfile dockerfile: ./compose/production/postgres/Dockerfile
image: {{ cookiecutter.project_slug }}_production_postgres image: {{ cookiecutter.project_slug }}_production_postgres
container_name: postgres
volumes: volumes:
- local_postgres_data:/var/lib/postgresql/data - local_postgres_data:/var/lib/postgresql/data
- local_postgres_data_backups:/backups - local_postgres_data_backups:/backups
@ -54,6 +56,7 @@ services:
mailhog: mailhog:
image: mailhog/mailhog:v1.0.0 image: mailhog/mailhog:v1.0.0
container_name: mailhog
ports: ports:
- "8025:8025" - "8025:8025"
@ -62,10 +65,12 @@ services:
redis: redis:
image: redis:5.0 image: redis:5.0
container_name: redis
celeryworker: celeryworker:
<<: *django <<: *django
image: {{ cookiecutter.project_slug }}_local_celeryworker image: {{ cookiecutter.project_slug }}_local_celeryworker
container_name: celeryworker
depends_on: depends_on:
- redis - redis
- postgres - postgres
@ -78,6 +83,7 @@ services:
celerybeat: celerybeat:
<<: *django <<: *django
image: {{ cookiecutter.project_slug }}_local_celerybeat image: {{ cookiecutter.project_slug }}_local_celerybeat
container_name: celerybeat
depends_on: depends_on:
- redis - redis
- postgres - postgres
@ -90,6 +96,7 @@ services:
flower: flower:
<<: *django <<: *django
image: {{ cookiecutter.project_slug }}_local_flower image: {{ cookiecutter.project_slug }}_local_flower
container_name: flower
ports: ports:
- "5555:5555" - "5555:5555"
command: /start-flower command: /start-flower
@ -102,6 +109,7 @@ services:
context: . context: .
dockerfile: ./compose/local/node/Dockerfile dockerfile: ./compose/local/node/Dockerfile
image: {{ cookiecutter.project_slug }}_local_node image: {{ cookiecutter.project_slug }}_local_node
container_name: node
depends_on: depends_on:
- django - django
volumes: volumes:

View File

@ -1,37 +1,39 @@
pytz==2019.3 # https://github.com/stub42/pytz pytz==2020.1 # https://github.com/stub42/pytz
python-slugify==4.0.0 # https://github.com/un33k/python-slugify python-slugify==4.0.0 # https://github.com/un33k/python-slugify
Pillow==7.1.1 # https://github.com/python-pillow/Pillow Pillow==7.1.2 # https://github.com/python-pillow/Pillow
{%- if cookiecutter.use_compressor == "y" %} {%- if cookiecutter.use_compressor == "y" %}
rcssmin==1.0.6{% if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin rcssmin==1.0.6{% if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin
{%- endif %} {%- endif %}
argon2-cffi==19.2.0 # https://github.com/hynek/argon2_cffi argon2-cffi==20.1.0 # https://github.com/hynek/argon2_cffi
{%- if cookiecutter.use_whitenoise == 'y' %} {%- if cookiecutter.use_whitenoise == 'y' %}
whitenoise==5.0.1 # https://github.com/evansd/whitenoise whitenoise==5.1.0 # https://github.com/evansd/whitenoise
{%- endif %}
redis==3.5.0 # https://github.com/andymccurdy/redis-py
{%- if cookiecutter.use_docker == "y" or cookiecutter.windows == "n" %}
hiredis==1.0.1 # https://github.com/redis/hiredis-py
{%- endif %} {%- endif %}
redis==3.4.1 # https://github.com/andymccurdy/redis-py
{%- if cookiecutter.use_celery == "y" %} {%- if cookiecutter.use_celery == "y" %}
celery==4.4.2 # pyup: < 5.0 # https://github.com/celery/celery celery==4.4.5 # pyup: < 5.0 # https://github.com/celery/celery
django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
flower==0.9.4 # https://github.com/mher/flower flower==0.9.4 # https://github.com/mher/flower
{%- endif %} {%- endif %}
{%- endif %} {%- endif %}
{%- if cookiecutter.use_async == 'y' %} {%- if cookiecutter.use_async == 'y' %}
uvicorn==0.11.3 # https://github.com/encode/uvicorn uvicorn==0.11.5 # https://github.com/encode/uvicorn
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
{%- endif %} {%- endif %}
# Django # Django
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
django==3.0.5 # pyup: < 3.1 # https://www.djangoproject.com/ django==3.0.7 # pyup: < 3.1 # https://www.djangoproject.com/
django-environ==0.4.5 # https://github.com/joke2k/django-environ django-environ==0.4.5 # https://github.com/joke2k/django-environ
django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils 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-allauth==0.42.0 # https://github.com/pennersr/django-allauth
django-crispy-forms==1.9.0 # https://github.com/django-crispy-forms/django-crispy-forms django-crispy-forms==1.9.1 # https://github.com/django-crispy-forms/django-crispy-forms
{%- if cookiecutter.use_compressor == "y" %} {%- if cookiecutter.use_compressor == "y" %}
django-compressor==2.4 # https://github.com/django-compressor/django-compressor django-compressor==2.4 # https://github.com/django-compressor/django-compressor
{%- endif %} {%- endif %}
django-redis==4.11.0 # https://github.com/niwinz/django-redis django-redis==4.12.1 # https://github.com/jazzband/django-redis
{%- if cookiecutter.use_drf == "y" %} {%- if cookiecutter.use_drf == "y" %}
# Django REST Framework # Django REST Framework
djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework

View File

@ -1,28 +1,31 @@
-r ./base.txt -r ./base.txt
Werkzeug==1.0.1 # https://github.com/pallets/werkzeug Werkzeug==1.0.1 # https://github.com/pallets/werkzeug
ipdb==0.13.2 # https://github.com/gotcha/ipdb ipdb==0.13.3 # https://github.com/gotcha/ipdb
{%- if cookiecutter.use_docker == 'y' %} {%- if cookiecutter.use_docker == 'y' %}
psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- else %} {%- else %}
psycopg2-binary==2.8.5 # https://github.com/psycopg/psycopg2 psycopg2-binary==2.8.5 # https://github.com/psycopg/psycopg2
{%- endif %} {%- endif %}
{%- if cookiecutter.use_async == 'y' %}
watchgod==0.6 # https://github.com/samuelcolvin/watchgod
{%- endif %}
# Testing # Testing
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
mypy==0.770 # https://github.com/python/mypy mypy==0.782 # https://github.com/python/mypy
django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs
pytest==5.3.5 # https://github.com/pytest-dev/pytest pytest==5.4.3 # https://github.com/pytest-dev/pytest
pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar pytest-sugar==0.9.3 # https://github.com/Frozenball/pytest-sugar
# Documentation # Documentation
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
sphinx==3.0.2 # https://github.com/sphinx-doc/sphinx sphinx==3.1.1 # https://github.com/sphinx-doc/sphinx
sphinx-autobuild # https://github.com/GaretJax/sphinx-autobuild sphinx-autobuild # https://github.com/GaretJax/sphinx-autobuild
# Code quality # Code quality
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
flake8==3.7.9 # https://github.com/PyCQA/flake8 flake8==3.8.3 # https://github.com/PyCQA/flake8
flake8-isort==3.0.0 # https://github.com/gforcada/flake8-isort flake8-isort==3.0.0 # https://github.com/gforcada/flake8-isort
coverage==5.1 # https://github.com/nedbat/coveragepy coverage==5.1 # https://github.com/nedbat/coveragepy
black==19.10b0 # https://github.com/ambv/black black==19.10b0 # https://github.com/ambv/black
@ -30,13 +33,13 @@ pylint-django==2.0.15 # https://github.com/PyCQA/pylint-django
{%- if cookiecutter.use_celery == 'y' %} {%- if cookiecutter.use_celery == 'y' %}
pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery
{%- endif %} {%- endif %}
pre-commit==2.3.0 # https://github.com/pre-commit/pre-commit pre-commit==2.5.1 # https://github.com/pre-commit/pre-commit
# Django # Django
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy
django-debug-toolbar==2.2 # https://github.com/jazzband/django-debug-toolbar django-debug-toolbar==2.2 # https://github.com/jazzband/django-debug-toolbar
django-extensions==2.2.9 # https://github.com/django-extensions/django-extensions django-extensions==3.0.0 # https://github.com/django-extensions/django-extensions
django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin
pytest-django==3.9.0 # https://github.com/pytest-dev/pytest-django pytest-django==3.9.0 # https://github.com/pytest-dev/pytest-django

View File

@ -2,15 +2,16 @@
-r ./base.txt -r ./base.txt
{%- if cookiecutter.use_async == 'n' %}
gunicorn==20.0.4 # https://github.com/benoitc/gunicorn gunicorn==20.0.4 # https://github.com/benoitc/gunicorn
{%- endif %}
psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2
{%- if cookiecutter.use_whitenoise == 'n' %} {%- if cookiecutter.use_whitenoise == 'n' %}
Collectfast==2.1.0 # https://github.com/antonagestam/collectfast Collectfast==2.2.0 # https://github.com/antonagestam/collectfast
{%- endif %} {%- endif %}
{%- if cookiecutter.use_sentry == "y" %} {%- if cookiecutter.use_sentry == "y" %}
sentry-sdk==0.14.3 # https://github.com/getsentry/sentry-python sentry-sdk==0.15.1 # https://github.com/getsentry/sentry-python
{%- endif %}
{%- if cookiecutter.use_docker == "n" and cookiecutter.windows == "y" %}
hiredis==1.0.1 # https://github.com/redis/hiredis-py
{%- endif %} {%- endif %}
# Django # Django

View File

@ -1,6 +1,7 @@
from django.contrib.auth import get_user_model
from rest_framework import serializers from rest_framework import serializers
from {{ cookiecutter.project_slug }}.users.models import User User = get_user_model()
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):

View File

@ -1,6 +1,6 @@
from django.contrib.auth import forms, get_user_model from django.contrib.auth import forms, get_user_model
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
User = get_user_model() User = get_user_model()

View File

@ -0,0 +1,24 @@
import pytest
from django.urls import resolve, reverse
from {{ cookiecutter.project_slug }}.users.models import User
pytestmark = pytest.mark.django_db
def test_user_detail(user: User):
assert (
reverse("api:user-detail", kwargs={"username": user.username})
== f"/api/users/{user.username}/"
)
assert resolve(f"/api/users/{user.username}/").view_name == "api:user-detail"
def test_user_list():
assert reverse("api:user-list") == "/api/users/"
assert resolve("/api/users/").view_name == "api:user-list"
def test_user_me():
assert reverse("api:user-me") == "/api/users/me/"
assert resolve("/api/users/me/").view_name == "api:user-me"

View File

@ -0,0 +1,34 @@
import pytest
from django.test import RequestFactory
from {{ cookiecutter.project_slug }}.users.api.views import UserViewSet
from {{ cookiecutter.project_slug }}.users.models import User
pytestmark = pytest.mark.django_db
class TestUserViewSet:
def test_get_queryset(self, user: User, rf: RequestFactory):
view = UserViewSet()
request = rf.get("/fake-url/")
request.user = user
view.request = request
assert user in view.get_queryset()
def test_me(self, user: User, rf: RequestFactory):
view = UserViewSet()
request = rf.get("/fake-url/")
request.user = user
view.request = request
response = view.me(request)
assert response.data == {
"username": user.username,
"email": user.email,
"name": user.name,
"url": f"http://testserver/api/users/{user.username}/",
}

View File

@ -1,8 +1,15 @@
import pytest import pytest
from django.contrib.auth.models import AnonymousUser
from django.http.response import Http404
from django.test import RequestFactory from django.test import RequestFactory
from {{ cookiecutter.project_slug }}.users.models import User from {{ cookiecutter.project_slug }}.users.models import User
from {{ cookiecutter.project_slug }}.users.views import UserRedirectView, UserUpdateView from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory
from {{ cookiecutter.project_slug }}.users.views import (
UserRedirectView,
UserUpdateView,
user_detail_view,
)
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@ -44,3 +51,29 @@ class TestUserRedirectView:
view.request = request view.request = request
assert view.get_redirect_url() == f"/users/{user.username}/" assert view.get_redirect_url() == f"/users/{user.username}/"
class TestUserDetailView:
def test_authenticated(self, user: User, rf: RequestFactory):
request = rf.get("/fake-url/")
request.user = UserFactory()
response = user_detail_view(request, username=user.username)
assert response.status_code == 200
def test_not_authenticated(self, user: User, rf: RequestFactory):
request = rf.get("/fake-url/")
request.user = AnonymousUser() # type: ignore
response = user_detail_view(request, username=user.username)
assert response.status_code == 302
assert response.url == "/accounts/login/?next=/fake-url/"
def test_case_sensitivity(self, rf: RequestFactory):
request = rf.get("/fake-url/")
request.user = UserFactory(username="UserName")
with pytest.raises(Http404):
user_detail_view(request, username="username")