diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e7d9220..b12ce03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All enhancements and patches to Cookiecutter Django will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [2020-01-12] +### Changed +- Fix mypy setup and added django-stubs +- Add Gitlab CI as option + +## [2020-01-11] +### Changed +- Speed up & reduce size for production Django image +- Bumped runtime version for Heroku +- Added Debian 10 (Buster) OS dependencies +- Update Traefik to v2 +- Switched Docker images from Alpine based to Debian based + ## [2019-10-06] ### Changed - Default Python version is now 3.7 (@nicolas471) diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 53edb28e..e4ef3181 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -71,6 +71,7 @@ Listed in alphabetical order. Benjamin Abel Bert de Miranda `@bertdemiranda`_ Bo Lopker `@blopker`_ + Bo Peng `@BoPeng`_ Bouke Haarsma Brent Payne `@brentpayne`_ @brentpayne Bruce Olivier `@bolivierjr`_ @@ -94,6 +95,7 @@ Listed in alphabetical order. Dan Shultz `@shultz`_ Dani Hodovic `@danihodovic`_ Daniel Hepper `@dhepper`_ @danielhepper + Daniel Hillier `@danifus`_ Daniele Tricoli `@eriol`_ David Díaz `@ddiazpinto`_ @DavidDiazPinto Davit Tovmasyan `@davitovmasyan`_ @@ -102,6 +104,7 @@ Listed in alphabetical order. Demetris Stavrou `@demestav`_ Denis Bobrov `@delneg`_ Denis Orehovsky `@apirobot`_ + Denis Savran `@blaxpy`_ Diane Chen `@purplediane`_ @purplediane88 Dónal Adams `@epileptic-fish`_ Dong Huynh `@trungdong`_ @@ -122,6 +125,8 @@ Listed in alphabetical order. Henrique G. G. Pereira `@ikkebr`_ Ian Lee `@IanLee1521`_ Irfan Ahmad `@erfaan`_ @erfaan + Isaac12x `@Isaac12x`_ + Ivan Khomutov `@ikhomutov`_ Jan Van Bruggen `@jvanbrug`_ Jelmer Draaijer `@foarsitter`_ Jens Nilsson `@phiberjenz`_ @@ -226,10 +231,12 @@ Listed in alphabetical order. .. _@arruda: https://github.com/arruda .. _@bertdemiranda: https://github.com/bertdemiranda .. _@bittner: https://github.com/bittner +.. _@blaxpy: https://github.com/blaxpy .. _@bloodpet: https://github.com/bloodpet .. _@blopker: https://github.com/blopker .. _@bogdal: https://github.com/bogdal .. _@bolivierjr: https://github.com/bolivierjr +.. _@BoPeng: https://github.com/BoPeng .. _@brentpayne: https://github.com/brentpayne .. _@btknu: https://github.com/btknu .. _@burhan: https://github.com/burhan @@ -252,6 +259,7 @@ Listed in alphabetical order. .. _@curtisstpierre: https://github.com/curtisstpierre .. _@dadokkio: https://github.com/dadokkio .. _@danihodovic: https://github.com/danihodovic +.. _@danifus: https://github.com/danifus .. _@davitovmasyan: https://github.com/davitovmasyan .. _@ddiazpinto: https://github.com/ddiazpinto .. _@delneg: https://github.com/delneg @@ -281,7 +289,9 @@ Listed in alphabetical order. .. _@hendrikschneider: https://github.com/hendrikschneider .. _@hjwp: https://github.com/hjwp .. _@IanLee1521: https://github.com/IanLee1521 +.. _@ikhomutov: https://github.com/ikhomutov .. _@ikkebr: https://github.com/ikkebr +.. _@Isaac12x: https://github.com/Isaac12x .. _@iynaix: https://github.com/iynaix .. _@jangeador: https://github.com/jangeador .. _@jazztpt: https://github.com/jazztpt diff --git a/README.rst b/README.rst index 2fba0979..6e534b0f 100644 --- a/README.rst +++ b/README.rst @@ -9,8 +9,8 @@ Cookiecutter Django :target: https://pyup.io/repos/github/pydanny/cookiecutter-django/ :alt: Updates -.. image:: https://badges.gitter.im/Join Chat.svg - :target: https://gitter.im/pydanny/cookiecutter-django?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +.. image:: https://img.shields.io/badge/cookiecutter-Join%20on%20Slack-green?style=flat&logo=slack + :target: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U .. image:: https://www.codetriage.com/pydanny/cookiecutter-django/badges/users.svg :target: https://www.codetriage.com/pydanny/cookiecutter-django @@ -226,11 +226,11 @@ Community * Have questions? **Before you ask questions anywhere else**, please post your question on `Stack Overflow`_ under the *cookiecutter-django* tag. We check there periodically for questions. * If you think you found a bug or want to request a feature, please open an issue_. -* For anything else, you can chat with us on `Gitter`_. +* For anything else, you can chat with us on `Slack`_. .. _`Stack Overflow`: http://stackoverflow.com/questions/tagged/cookiecutter-django .. _`issue`: https://github.com/pydanny/cookiecutter-django/issues -.. _`Gitter`: https://gitter.im/pydanny/cookiecutter-django?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge +.. _`Slack`: https://join.slack.com/t/cookie-cutter/shared_invite/enQtNzI0Mzg5NjE5Nzk5LTRlYWI2YTZhYmQ4YmU1Y2Q2NmE1ZjkwOGM0NDQyNTIwY2M4ZTgyNDVkNjMxMDdhZGI5ZGE5YmJjM2M3ODJlY2U For Readers of Two Scoops of Django -------------------------------------------- diff --git a/cookiecutter.json b/cookiecutter.json index d6d217ca..6d030be0 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -40,7 +40,11 @@ "use_sentry": "n", "use_whitenoise": "n", "use_heroku": "n", - "use_travisci": "n", + "ci_tool": [ + "None", + "Travis", + "Gitlab" + ], "keep_local_envs_in_vcs": "y", "debug": "n" diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index afa9b8af..ae47b097 100644 --- a/docs/project-generation-options.rst +++ b/docs/project-generation-options.rst @@ -94,8 +94,12 @@ use_heroku: Indicates whether the project should be configured so as to be deployable to Heroku_. -use_travisci: - Indicates whether the project should be configured to use `Travis CI`_. +ci_tool: + Select a CI tool for running tests. The choices are: + + 1. None + 2. Travis_ + 3. Gitlab_ keep_local_envs_in_vcs: Indicates whether the project's ``.envs/.local/`` should be kept in VCS @@ -138,3 +142,6 @@ debug: .. _Heroku: https://github.com/heroku/heroku-buildpack-python .. _Travis CI: https://travis-ci.org/ + +.. _GitLab CI: https://docs.gitlab.com/ee/ci/ + diff --git a/docs/testing.rst b/docs/testing.rst index cd323e5c..dd6fcb48 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -49,8 +49,8 @@ Once the tests are complete, in order to see the code coverage, run the followin Since this is a fresh install, and there are no tests built using the Python `unittest`_ library yet, you should get feedback that says there were no tests carried out. .. _Pytest: https://docs.pytest.org/en/latest/example/simple.html -.. _develop locally: ../developing-locally.rst -.. _develop locally with docker: ..../developing-locally-docker.rst +.. _develop locally: ./developing-locally.html +.. _develop locally with docker: ./developing-locally-docker.html .. _customize: https://docs.pytest.org/en/latest/customize.html .. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest -.. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html \ No newline at end of file +.. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 0544f14b..79c32f9b 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -70,7 +70,7 @@ def remove_heroku_files(): for file_name in file_names: if ( file_name == "requirements.txt" - and "{{ cookiecutter.use_travisci }}".lower() == "y" + and "{{ cookiecutter.ci_tool }}".lower() == "travis" ): # don't remove the file if we are using travisci but not using heroku continue @@ -105,6 +105,10 @@ def remove_dottravisyml_file(): os.remove(".travis.yml") +def remove_dotgitlabciyml_file(): + os.remove(".gitlab-ci.yml") + + def append_to_project_gitignore(path): gitignore_file_path = ".gitignore" with open(gitignore_file_path, "a") as gitignore_file: @@ -349,9 +353,12 @@ def main(): if "{{ cookiecutter.use_docker }}".lower() == "y": remove_celery_compose_dirs() - if "{{ cookiecutter.use_travisci }}".lower() == "n": + if "{{ cookiecutter.ci_tool }}".lower() != "travis": remove_dottravisyml_file() + if "{{ cookiecutter.ci_tool }}".lower() != "gitlab": + remove_dotgitlabciyml_file() + print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR) diff --git a/requirements.txt b/requirements.txt index eccb2d9d..8d9b1685 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -cookiecutter==1.6.0 +cookiecutter==1.7.0 sh==1.12.14 binaryornot==0.4.4 @@ -9,9 +9,9 @@ flake8==3.7.9 # Testing # ------------------------------------------------------------------------------ -tox==3.14.2 -pytest==5.3.2 +tox==3.14.3 +pytest==5.3.4 pytest_cases==1.12.0 pytest-cookies==0.4.0 pytest-xdist==1.31.0 -pyyaml==5.2 +pyyaml==5.3 diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index f931bce0..8c2f71fe 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -140,7 +140,7 @@ def test_black_passes(cookies, context_combination): def test_travis_invokes_pytest(cookies, context): - context.update({"use_travisci": "y"}) + context.update({"ci_tool": "Travis"}) result = cookies.bake(extra_context=context) assert result.exit_code == 0 @@ -155,6 +155,24 @@ def test_travis_invokes_pytest(cookies, context): pytest.fail(e) +def test_gitlab_invokes_flake8_and_pytest(cookies, context): + context.update({"ci_tool": "Gitlab"}) + result = cookies.bake(extra_context=context) + + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == context["project_slug"] + assert result.project.isdir() + + with open(f"{result.project}/.gitlab-ci.yml", "r") as gitlab_yml: + try: + gitlab_config = yaml.load(gitlab_yml) + assert gitlab_config["flake8"]["script"] == ["flake8"] + assert gitlab_config["pytest"]["script"] == ["pytest"] + except yaml.YAMLError as e: + pytest.fail(e) + + @pytest.mark.parametrize("slug", ["project slug", "Project_Slug"]) def test_invalid_slug(cookies, context, slug): """Invalid slug should failed pre-generation hook.""" diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml new file mode 100644 index 00000000..15ff73b1 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -0,0 +1,33 @@ +stages: + - lint + - test + +variables: + POSTGRES_USER: '{{ cookiecutter.project_slug }}' + POSTGRES_PASSWORD: '' + POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}' + +flake8: + stage: lint + image: python:3.7-alpine + before_script: + - pip install -q flake8 + script: + - flake8 + +pytest: + stage: test + image: python:3.7 + tags: + - docker + services: + - postgres:11 + variables: + DATABASE_URL: pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB + + before_script: + - pip install -r requirements/local.txt + + script: + - pytest + diff --git a/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile index a98547bd..94c79952 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile +++ b/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile @@ -1,19 +1,17 @@ -FROM python:3.7-alpine +FROM python:3.7-slim-buster ENV PYTHONUNBUFFERED 1 -RUN apk update \ +RUN apt-get update \ + # dependencies for building Python packages + && apt-get install -y build-essential \ # psycopg2 dependencies - && apk add --virtual build-deps gcc python3-dev musl-dev \ - && apk add postgresql-dev \ - # Pillow dependencies - && apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \ - # CFFI dependencies - && apk add libffi-dev py-cffi \ + && apt-get install -y libpq-dev \ # Translations dependencies - && apk add gettext \ - # https://docs.djangoproject.com/en/dev/ref/django-admin/#dbshell - && apk add postgresql-client + && apt-get install -y gettext \ + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* # Requirements are installed here to ensure they will be cached. COPY ./requirements /requirements diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start index 389e2baf..c04a7365 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o nounset diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start index be67050d..5bcaa816 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o nounset diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start index 072c6ae6..acd6f157 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o nounset diff --git a/{{cookiecutter.project_slug}}/compose/local/django/start b/{{cookiecutter.project_slug}}/compose/local/django/start index 921604dc..f076ee51 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/start +++ b/{{cookiecutter.project_slug}}/compose/local/django/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o pipefail diff --git a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile index b0861d60..42ecbeaf 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile +++ b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile @@ -9,21 +9,23 @@ RUN npm run build # Python build stage {%- endif %} -FROM python:3.7-alpine +FROM python:3.7-slim-buster ENV PYTHONUNBUFFERED 1 -RUN apk update \ +RUN apt-get update \ + # dependencies for building Python packages + && apt-get install -y build-essential \ # psycopg2 dependencies - && apk add --virtual build-deps gcc python3-dev musl-dev \ - && apk add postgresql-dev \ - # Pillow dependencies - && apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \ - # CFFI dependencies - && apk add libffi-dev py-cffi + && apt-get install -y libpq-dev \ + # Translations dependencies + && apt-get install -y gettext \ + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* -RUN addgroup -S django \ - && adduser -S -G django django +RUN addgroup --system django \ + && adduser --system --ingroup django django # Requirements are installed here to ensure they will be cached. COPY ./requirements /requirements @@ -57,13 +59,11 @@ RUN chmod +x /start-flower {%- endif %} {%- if cookiecutter.js_task_runner == 'Gulp' %} -COPY --from=client-builder /app /app +COPY --from=client-builder --chown=django:django /app /app {% else %} -COPY . /app +COPY --chown=django:django . /app {%- endif %} -RUN chown -R django /app - USER django WORKDIR /app diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start index 0e793e38..20b93123 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o pipefail diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start index be67050d..5bcaa816 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o nounset diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start index 4e519c3f..38fb77f3 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o pipefail diff --git a/{{cookiecutter.project_slug}}/compose/production/django/entrypoint b/{{cookiecutter.project_slug}}/compose/production/django/entrypoint index 0a76b310..95ab8297 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/entrypoint +++ b/{{cookiecutter.project_slug}}/compose/production/django/entrypoint @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o pipefail diff --git a/{{cookiecutter.project_slug}}/compose/production/django/start b/{{cookiecutter.project_slug}}/compose/production/django/start index 0ad39dfa..709c1dd0 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/start +++ b/{{cookiecutter.project_slug}}/compose/production/django/start @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -o errexit set -o pipefail diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile index d7363a1a..746aa2b4 100644 --- a/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile +++ b/{{cookiecutter.project_slug}}/compose/production/traefik/Dockerfile @@ -1,5 +1,5 @@ -FROM traefik:1.7-alpine +FROM traefik:v2.0 RUN mkdir -p /etc/traefik/acme RUN touch /etc/traefik/acme/acme.json RUN chmod 600 /etc/traefik/acme/acme.json -COPY ./compose/production/traefik/traefik.toml /etc/traefik +COPY ./compose/production/traefik/traefik.yml /etc/traefik diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.toml b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.toml deleted file mode 100644 index 0f2abe8a..00000000 --- a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.toml +++ /dev/null @@ -1,41 +0,0 @@ -logLevel = "INFO" -defaultEntryPoints = ["http", "https"] - -# Entrypoints, http and https -[entryPoints] - # http should be redirected to https - [entryPoints.http] - address = ":80" - [entryPoints.http.redirect] - entryPoint = "https" - # https is the default - [entryPoints.https] - address = ":443" - [entryPoints.https.tls] - -# Enable ACME (Let's Encrypt): automatic SSL -[acme] -# Email address used for registration -email = "{{ cookiecutter.email }}" -storage = "/etc/traefik/acme/acme.json" -entryPoint = "https" -onDemand = false -OnHostRule = true - # Use a HTTP-01 acme challenge rather than TLS-SNI-01 challenge - [acme.httpChallenge] - entryPoint = "http" - -[file] -[backends] - [backends.django] - [backends.django.servers.server1] - url = "http://django:5000" - -[frontends] - [frontends.django] - backend = "django" - passHostHeader = true - [frontends.django.headers] - HostsProxyHeaders = ['X-CSRFToken'] - [frontends.django.routes.dr1] - rule = "Host:{{ cookiecutter.domain_name }}" diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml new file mode 100644 index 00000000..324c62af --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml @@ -0,0 +1,67 @@ +log: + level: INFO + +entryPoints: + web: + # http + address: ":80" + + web-secure: + # https + address: ":443" + +certificatesResolvers: + letsencrypt: + # https://docs.traefik.io/master/https/acme/#lets-encrypt + acme: + email: "{{ cookiecutter.email }}" + storage: /etc/traefik/acme/acme.json + # https://docs.traefik.io/master/https/acme/#httpchallenge + httpChallenge: + entryPoint: web + +http: + routers: + web-router: + rule: "Host(`{{ cookiecutter.domain_name }}`)" + entryPoints: + - web + middlewares: + - redirect + - csrf + service: django + + web-secure-router: + rule: "Host(`{{ cookiecutter.domain_name }}`)" + entryPoints: + - web-secure + middlewares: + - csrf + service: django + tls: + # https://docs.traefik.io/master/routing/routers/#certresolver + certResolver: letsencrypt + + middlewares: + redirect: + # https://docs.traefik.io/master/middlewares/redirectscheme/ + redirectScheme: + scheme: https + permanent: true + csrf: + # https://docs.traefik.io/master/middlewares/headers/#hostsproxyheaders + # https://docs.djangoproject.com/en/dev/ref/csrf/#ajax + headers: + hostsProxyHeaders: ['X-CSRFToken'] + + services: + django: + loadBalancer: + servers: + - url: http://django:5000 + +providers: + # https://docs.traefik.io/master/providers/file/ + file: + filename: /etc/traefik/traefik.yml + watch: true diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index 935a0d12..ecfeed7a 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -140,6 +140,7 @@ MIDDLEWARE = [ "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.common.BrokenLinkEmailsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index 22a2acae..36667b33 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -154,7 +154,7 @@ MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" # TEMPLATES # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#templates -TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405 +TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405 ( "django.template.loaders.cached.Loader", [ diff --git a/{{cookiecutter.project_slug}}/config/settings/test.py b/{{cookiecutter.project_slug}}/config/settings/test.py index fad41afd..667bb20d 100644 --- a/{{cookiecutter.project_slug}}/config/settings/test.py +++ b/{{cookiecutter.project_slug}}/config/settings/test.py @@ -32,7 +32,7 @@ PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] # TEMPLATES # ------------------------------------------------------------------------------ -TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa F405 +TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa F405 ( "django.template.loaders.cached.Loader", [ diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index c31481a3..9ea6b912 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -1,6 +1,6 @@ pytz==2019.3 # https://github.com/stub42/pytz python-slugify==4.0.0 # https://github.com/un33k/python-slugify -Pillow==6.2.1 # https://github.com/python-pillow/Pillow +Pillow==7.0.0 # https://github.com/python-pillow/Pillow {%- if cookiecutter.use_compressor == "y" %} rcssmin==1.0.6{% if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin {%- endif %} @@ -25,7 +25,7 @@ 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 {%- if cookiecutter.use_compressor == "y" %} -django-compressor==2.3 # https://github.com/django-compressor/django-compressor +django-compressor==2.4 # https://github.com/django-compressor/django-compressor {%- endif %} django-redis==4.11.0 # https://github.com/niwinz/django-redis diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index 070a0b2d..4acb1d9d 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -2,9 +2,9 @@ Werkzeug==0.16.0 # https://github.com/pallets/werkzeug ipdb==0.12.3 # https://github.com/gotcha/ipdb -Sphinx==2.3.0 # https://github.com/sphinx-doc/sphinx +Sphinx==2.3.1 # https://github.com/sphinx-doc/sphinx {%- if cookiecutter.use_docker == 'y' %} -psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 +psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- else %} psycopg2-binary==2.8.4 # https://github.com/psycopg/psycopg2 {%- endif %} @@ -12,25 +12,26 @@ psycopg2-binary==2.8.4 # https://github.com/psycopg/psycopg2 # Testing # ------------------------------------------------------------------------------ mypy==0.761 # https://github.com/python/mypy -pytest==5.3.1 # https://github.com/pytest-dev/pytest +django-stubs==1.4.0 # https://github.com/typeddjango/django-stubs +pytest==5.3.4 # 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 # https://github.com/nedbat/coveragepy +coverage==5.0.3 # https://github.com/nedbat/coveragepy black==19.10b0 # https://github.com/ambv/black pylint-django==2.0.13 # https://github.com/PyCQA/pylint-django {%- if cookiecutter.use_celery == 'y' %} pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery {%- endif %} -pre-commit==1.20.0 # https://github.com/pre-commit/pre-commit +pre-commit==1.21.0 # https://github.com/pre-commit/pre-commit # Django # ------------------------------------------------------------------------------ factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy django-debug-toolbar==2.1 # https://github.com/jazzband/django-debug-toolbar -django-extensions==2.2.5 # https://github.com/django-extensions/django-extensions -django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin -pytest-django==3.7.0 # https://github.com/pytest-dev/pytest-django +django-extensions==2.2.6 # https://github.com/django-extensions/django-extensions +django-coverage-plugin==1.7.0 # https://github.com/nedbat/django_coverage_plugin +pytest-django==3.8.0 # https://github.com/pytest-dev/pytest-django diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index 438ccca6..38420a38 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -3,12 +3,12 @@ -r ./base.txt gunicorn==20.0.4 # https://github.com/benoitc/gunicorn -psycopg2==2.8.3 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 +psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- if cookiecutter.use_whitenoise == 'n' %} Collectfast==1.3.1 # https://github.com/antonagestam/collectfast {%- endif %} {%- if cookiecutter.use_sentry == "y" %} -sentry-sdk==0.13.5 # https://github.com/getsentry/sentry-python +sentry-sdk==0.14.1 # https://github.com/getsentry/sentry-python {%- endif %} # Django diff --git a/{{cookiecutter.project_slug}}/runtime.txt b/{{cookiecutter.project_slug}}/runtime.txt index 42731f2f..6919bf9e 100644 --- a/{{cookiecutter.project_slug}}/runtime.txt +++ b/{{cookiecutter.project_slug}}/runtime.txt @@ -1 +1 @@ -python-3.7.4 +python-3.7.6 diff --git a/{{cookiecutter.project_slug}}/setup.cfg b/{{cookiecutter.project_slug}}/setup.cfg index f7cd01cb..5f7d9b65 100644 --- a/{{cookiecutter.project_slug}}/setup.cfg +++ b/{{cookiecutter.project_slug}}/setup.cfg @@ -13,6 +13,7 @@ ignore_missing_imports = True warn_unused_ignores = True warn_redundant_casts = True warn_unused_configs = True +plugins = mypy_django_plugin.main [mypy.plugins.django-stubs] django_settings_module = config.settings.test diff --git a/{{cookiecutter.project_slug}}/utility/requirements-buster.apt b/{{cookiecutter.project_slug}}/utility/requirements-buster.apt new file mode 100644 index 00000000..75957f40 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-buster.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Debian Jessie 10.x +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg62-turbo-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +libgraphviz-dev diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py index aae11d26..8cd51d7f 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py @@ -1,7 +1,7 @@ import pytest -from django.conf import settings from django.test import RequestFactory +from {{ cookiecutter.project_slug }}.users.models import User from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory @@ -11,7 +11,7 @@ def media_storage(settings, tmpdir): @pytest.fixture -def user() -> settings.AUTH_USER_MODEL: +def user() -> User: return UserFactory() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py index b5371366..8917c5ae 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py @@ -12,14 +12,18 @@ class UserFactory(DjangoModelFactory): @post_generation def password(self, create: bool, extracted: Sequence[Any], **kwargs): - password = Faker( - "password", - length=42, - special_chars=True, - digits=True, - upper_case=True, - lower_case=True, - ).generate(extra_kwargs={}) + password = ( + extracted + if extracted + else Faker( + "password", + length=42, + special_chars=True, + digits=True, + upper_case=True, + lower_case=True, + ).generate(extra_kwargs={}) + ) self.set_password(password) class Meta: diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py index 54863632..3194be1f 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py @@ -1,8 +1,9 @@ import pytest -from django.conf import settings + +from {{ cookiecutter.project_slug }}.users.models import User pytestmark = pytest.mark.django_db -def test_user_get_absolute_url(user: settings.AUTH_USER_MODEL): +def test_user_get_absolute_url(user: User): assert user.get_absolute_url() == f"/users/{user.username}/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py index c6361920..933396ba 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py @@ -1,11 +1,12 @@ import pytest -from django.conf import settings from django.urls import reverse, resolve +from {{ cookiecutter.project_slug }}.users.models import User + pytestmark = pytest.mark.django_db -def test_detail(user: settings.AUTH_USER_MODEL): +def test_detail(user: User): assert ( reverse("users:detail", kwargs={"username": user.username}) == f"/users/{user.username}/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py index 76cb80aa..9e868899 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py @@ -1,7 +1,7 @@ import pytest -from django.conf import settings from django.test import RequestFactory +from {{ cookiecutter.project_slug }}.users.models import User from {{ cookiecutter.project_slug }}.users.views import UserRedirectView, UserUpdateView pytestmark = pytest.mark.django_db @@ -16,9 +16,7 @@ class TestUserUpdateView: https://github.com/pytest-dev/pytest-django/pull/258 """ - def test_get_success_url( - self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory - ): + def test_get_success_url(self, user: User, request_factory: RequestFactory): view = UserUpdateView() request = request_factory.get("/fake-url/") request.user = user @@ -27,9 +25,7 @@ class TestUserUpdateView: assert view.get_success_url() == f"/users/{user.username}/" - def test_get_object( - self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory - ): + def test_get_object(self, user: User, request_factory: RequestFactory): view = UserUpdateView() request = request_factory.get("/fake-url/") request.user = user @@ -40,9 +36,7 @@ class TestUserUpdateView: class TestUserRedirectView: - def test_get_redirect_url( - self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory - ): + def test_get_redirect_url(self, user: User, request_factory: RequestFactory): view = UserRedirectView() request = request_factory.get("/fake-url") request.user = user