diff --git a/.travis.yml b/.travis.yml index a74090b2..1be0b743 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -sudo: required +dist: xenial services: - docker @@ -13,10 +13,14 @@ before_install: matrix: include: - - name: Tox Test + - name: Test results script: tox -e py36 - - name: Black template + - name: Run flake8 on result + script: tox -e flake8 + - name: Run black on result script: tox -e black + - name: Black template + script: tox -e black-template - name: Basic Docker script: sh tests/test_docker.sh - name: Docker with Celery diff --git a/CHANGELOG.md b/CHANGELOG.md index 304732b9..f013aec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,196 @@ All enhancements and patches to Cookiecutter Django will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [2019-05-27] +### Changed +- Made cloud provider optional (@tanoabeleyra) +- Updated to Django 2.2.1 (@browniebroke) + +### Fixed +- Celery worker-related setting names (@browniebroke) + +## [2019-05-18] +### Removed +- Remove the user list view (@browniebroke) + +### Fixed +- Static storage default ACL (@browniebroke) + +## [2019-05-17] +### Fixed +- Added `LocaleMiddleware` to the list of middlewares (@tanoabeleyra) +- Added `LOCALE_PATH` to settings (@tanoabeleyra) + +## [2019-05-16] +### Changed +- Users app to have a translated verbose name (@tanoabeleyra) +- Logging configuration for local (@browniebroke) + +## [2019-05-08] +### Changed +- Upgraded to Django 2.1 (@browniebroke) + +## [2019-04-07] +### Added +- Support for Google Cloud Storage (@ahhda) + +## [2019-04-03] +### Added +- Command to backup Db to AWS S3 (@foarsitter) + +## [2019-03-25] +### Added +- Node image to run Gulp with Docker (@browniebroke) + +## [2019-03-19] +### Changed +- Replaced Caddy with Traefik (@demestav) + +## [2019-03-11] +### Changed +- Sentry integration from Raven to Sentry-SDK (@gfabricio) +- Made Redis config conditional on Celery locally (@demestav) + +## [2019-03-11] +### Added +- Automatic migrations on Heroku (@yunti) + +## [2019-03-06] +### Fixed +- Missing script tag in Travis config (@btknu) + +## [2019-03-02] +### Changed +- Celery eager setting in local setting with Docker (@keithjeb) + +## [2019-03-01] +### Updated +- All NPM dependencies (@takkaria) + +## [2018-11-13] +### Changed +- Security settings in Dev (@carlmjohnson) + +## [2018-11-20] +### Fixed +- Passing the CSRF header from the reverse proxy to Django server for DRF (@hpbruna) + +## [2018-11-12] +### Fixed +- Initialisation of Celery app (@glasslion) + +## [2018-10-24] +### Fixed +- Persisting of iPython history between sessions (@davitovmasyan) + +### Added +- Postgres 10.5 option (@jleclanche) + +## [2018-09-18] +### Added +- Included `mypy` in dependencies and run it in tests (@apirobot) + +## [2018-09-18] +### Fixed +- Avoid `$` in environment variables to workaround a bug from django-environ (@browniebroke) + +## [2018-09-16] +### Fixed +- Bug in ordering of Middleware for production config (@ChrisPappalardo) + +## [2018-09-12] +### Fixed +- URLs for Static and Media for S3 buckets in regions other than N. Virginia (@umrashrf) + +## [2018-09-09] +### Changed +- Name of static and media storage classes (@sfdye) + +## [2018-09-01] +### Changed +- Make static and media storage fully-fledged classes (@erfaan) + +## [2018-08-28] +### Fixed +- Running tests in docker test script (@apirobot) + +## [2018-07-23] +### Changed +- Test commands to use pytest (@jcass77) + +### Removed +- Some hacks leftovers from Bootstrap v4 beta in `project.js` (@hendrikschneider) + +## [2018-07-12] +### Changed +- Upgraded to Bootstrap 4.1.1 (@mostaszewski) + +## [2018-06-25] +### Added +- Flower integration with Docker (@webyneter) + +## [2018-06-25] +### Changed +- Rewrite user app test to use a pytest style (@webyneter) + +## [2018-06-21] +### Added +- Extend & update Celery config (@webyneter & @apirobot) + +## [2018-05-25] +### Fixed +- Build issues due to incompatibility between libressl & openssl (@SassanoM) + +## [2018-05-21] +### Changed +- Updated Caddy to 0.11 and pin its version (@webyneter) + +## [2018-05-14] +### Changed +- Replace `awesome-slugify` by `python-slugify` (@hongquan) +- Migrate to Django 2.0+ URL style (@saschalalala) + +## [2018-05-05] +### Fixed +- Postgres backup & restore commands (@webyneter) + +## [2018-04-10] +### Changed +- Simplify configuration (@danidee10) + +## [2018-04-08] +### Added +- Adopt Black code style (@pydanny) + +## [2018-03-27] +### Fixed +- Simplified extra Celery config generated when opted out (@webyneter) + +## [2018-03-21] +### Removed +- Remove Opbeat support (@sfdye) + +## [2018-03-16] +### Fixed +- Install `psycopg2-binary` when using Docker locally (@browniebroke) + +## [2018-03-14] +### Fixed +- Fixed and improved Postgres backup & restore scripts (@webyneter) + +## [2018-03-10] +### Changed +- Simplify Mailgun setting (@browniebroke) + +## [2018-03-06] +### Changed +- Convert string formatting to f-strings (@sfdye) + +## [2018-03-01] +### Changed +- Celery to use JSON serialization by default (@adammsteele) +- Use Docker version from Travis to run tests (@browniebroke) + ## [2018-02-16] ### Changed - Upgraded to Django 2.0 (@epicwhale) diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 6fa9e1c0..7a9dbf79 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -7,19 +7,19 @@ Core Developers These contributors have commit flags for the repository, and are able to accept and merge pull requests. -=========================== ================ =========== -Name Github Twitter -=========================== ================ =========== -Daniel Roy Greenfeld `@pydanny`_ @pydanny -Audrey Roy Greenfeld* `@audreyr`_ @audreyr -Fábio C. Barrionuevo da Luz `@luzfcb`_ @luzfcb -Saurabh Kumar `@theskumar`_ @_theskumar +=========================== ================= =========== +Name Github Twitter +=========================== ================= =========== +Daniel Roy Greenfeld `@pydanny`_ @pydanny +Audrey Roy Greenfeld* `@audreyr`_ @audreyr +Fábio C. Barrionuevo da Luz `@luzfcb`_ @luzfcb +Saurabh Kumar `@theskumar`_ @_theskumar Jannis Gebauer `@jayfk`_ -Burhan Khalid `@burhan`_ @burhan -Nikita Shupeyko `@webyneter`_ @webyneter -Bruno Alla               `@browniebroke`_ @_BrunoAlla -Wan Liuyang `@sfdye`_ @sfdye -=========================== ================ =========== +Burhan Khalid `@burhan`_ @burhan +Nikita Shupeyko `@webyneter`_ @webyneter +Bruno Alla               `@browniebroke`_ @_BrunoAlla +Wan Liuyang `@sfdye`_ @sfdye +=========================== ================= =========== *Audrey is also the creator of Cookiecutter. Audrey and Daniel are on the Cookiecutter core team.* @@ -64,6 +64,7 @@ Listed in alphabetical order. Areski Belaid `@areski`_ Ashley Camba Barclay Gauld `@yunti`_ + Bartek `@btknu`_ Ben Warren `@bwarren2`_ Ben Lopatin Benjamin Abel @@ -71,7 +72,6 @@ Listed in alphabetical order. Bo Lopker `@blopker`_ Bouke Haarsma Brent Payne `@brentpayne`_ @brentpayne - Bartek `@btknu`_ Burhan Khalid            `@burhan`_                   @burhan Carl Johnson `@carlmjohnson`_ @carlmjohnson Catherine Devlin `@catherinedevlin`_ @@ -87,6 +87,7 @@ Listed in alphabetical order. Craig Margieson `@cmargieson`_ Cristian Vargas `@cdvv7788`_ Cullen Rhodes `@c-rhodes`_ + Curtis St Pierre `@curtisstpierre`_ @cstpierre1388 Dan Shultz `@shultz`_ Daniel Hepper `@dhepper`_ @danielhepper Daniele Tricoli `@eriol`_ @@ -95,6 +96,7 @@ Listed in alphabetical order. Davur Clementsen `@dsclementsen`_ @davur Delio Castillo `@jangeador`_ @jangeador Demetris Stavrou `@demestav`_ + Denis Bobrov `@delneg`_ Denis Orehovsky `@apirobot`_ Dónal Adams `@epileptic-fish`_ Diane Chen `@purplediane`_ @purplediane88 @@ -114,8 +116,8 @@ Listed in alphabetical order. Ian Lee `@IanLee1521`_ Irfan Ahmad `@erfaan`_ @erfaan Jan Van Bruggen `@jvanbrug`_ - Jens Nilsson `@phiberjenz`_ Jelmer Draaijer `@foarsitter`_ + Jens Nilsson `@phiberjenz`_ Jerome Leclanche `@jleclanche`_ @Adys Jimmy Gitonga `@afrowave`_ @afrowave John Cass `@jcass77`_ @cass_john @@ -124,9 +126,10 @@ Listed in alphabetical order. Kaido Kert `@kaidokert`_ kappataumu `@kappataumu`_ @kappataumu Kaveh `@ka7eh`_ + Keith Bailey `@keithjeb`_ + Keith Webber `@townie`_ Kevin A. Stone Kevin Ndung'u `@kevgathuku`_ - Keith Webber `@townie`_ Krzysztof Szumny `@noisy`_ Krzysztof Żuraw `@krzysztofzuraw`_ Leonardo Jimenez `@xpostudio4`_ @@ -141,6 +144,7 @@ Listed in alphabetical order. Mateusz Ostaszewski `@mostaszewski`_ Mathijs Hoogland `@MathijsHoogland`_ Matt Braymer-Hayes `@mattayes`_ @mattayes + Matt Knapper `@mknapper1`_ Matt Linares Matt Menzenski `@menzenski`_ Matt Warren `@mfwarren`_ @@ -154,22 +158,24 @@ Listed in alphabetical order. Parbhat Puri `@parbhat`_ Peter Bittner `@bittner`_ Peter Coles `@mrcoles`_ + Philipp Matthies `@canonnervio`_ Pierre Chiquet `@pchiquet`_ - Raphael Pierzina `@hackebrot`_ Raony Guimarães Corrêa `@raonyguimaraes`_ + Raphael Pierzina `@hackebrot`_ Reggie Riser `@reggieriser`_ René Muhl `@rm--`_ Roman Afanaskin `@siauPatrick`_ Roman Osipenko `@romanosipenko`_ Russell Davies - Sascha `@saschalalala`_ @saschalalala Sam Collins `@MightySCollins`_ + Sascha `@saschalalala`_ @saschalalala Shupeyko Nikita `@webyneter`_ Sławek Ehlert `@slafs`_ Srinivas Nyayapati `@shireenrao`_ stepmr `@stepmr`_ Steve Steiner `@ssteinerX`_ Sule Marshall `@suledev`_ + Tano Abeleyra `@tanoabeleyra`_ Taylor Baldwin Théo Segonds `@show0k`_ Tim Freund `@timfreund`_ @@ -178,16 +184,13 @@ Listed in alphabetical order. Travis McNeill `@Travistock`_ @tavistock_esq Tubo Shi `@Tubo`_ Umair Ashraf `@umrashrf`_ @fabumair - Vlad Doster `@vladdoster`_ + Vadim Iskuchekov `@Egregors`_ @egregors Vitaly Babiy Vivian Guillen `@viviangb`_ + Vlad Doster `@vladdoster`_ Will Farley `@goldhand`_ @g01dhand William Archinal `@archinal`_ Yaroslav Halchenko - Denis Bobrov `@delneg`_ - Philipp Matthies `@canonnervio`_ - Vadim Iskuchekov `@Egregors`_ @egregors - Keith Bailey `@keithjeb`_ ========================== ============================ ============== .. _@a7p: https://github.com/a7p @@ -220,6 +223,7 @@ Listed in alphabetical order. .. _@chuckus: https://github.com/chuckus .. _@cmackenzie1: https://github.com/cmackenzie1 .. _@Collederas: https://github.com/Collederas +.. _@curtisstpierre: https://github.com/curtisstpierre .. _@davitovmasyan: https://github.com/davitovmasyan .. _@ddiazpinto: https://github.com/ddiazpinto .. _@demestav: https://github.com/demestav @@ -259,6 +263,7 @@ Listed in alphabetical order. .. _@msaizar: https://github.com/msaizar .. _@MathijsHoogland: https://github.com/MathijsHoogland .. _@mattayes: https://github.com/mattayes +.. _@mknapper1: https://github.com/mknapper1 .. _@menzenski: https://github.com/menzenski .. _@mostaszewski: https://github.com/mostaszewski .. _@mfwarren: https://github.com/mfwarren @@ -319,6 +324,7 @@ Listed in alphabetical order. .. _@hanaquadara: https://github.com/hanaquadara .. _@vladdoster: https://github.com/vladdoster .. _@cmargieson: https://github.com/cmargieson +.. _@tanoabeleyra: https://github.com/tanoabeleyra Special Thanks ~~~~~~~~~~~~~~ diff --git a/README.rst b/README.rst index 8a92edda..30a3a2db 100644 --- a/README.rst +++ b/README.rst @@ -36,7 +36,7 @@ production-ready Django projects quickly. Features --------- -* For Django 2.1 +* For Django 2.2 * Works with Python 3.6 * Renders Django projects with 100% starting test coverage * Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available) @@ -89,7 +89,7 @@ Constraints ----------- * Only maintained 3rd party libraries are used. -* Uses PostgreSQL everywhere (9.4 - 10.5) +* Uses PostgreSQL everywhere (9.4 - 11.3) * Environment variables for configuration (This won't work with Apache/mod_wsgi). Support this Project! @@ -169,23 +169,21 @@ Answer the prompts with your own desired options_. For example:: use_heroku [n]: y use_compressor [n]: y Select postgresql_version: - 1 - 10.5 - 2 - 10.4 - 3 - 10.3 - 4 - 10.2 - 5 - 10.1 - 6 - 9.6 - 7 - 9.5 - 8 - 9.4 - Choose from 1, 2, 3, 4, 5, 6, 7, 8 [1]: 1 + 1 - 11.3 + 2 - 10.8 + 3 - 9.6 + 4 - 9.5 + 5 - 9.4 + Choose from 1, 2, 3, 4, 5 [1]: 1 Select js_task_runner: 1 - None 2 - Gulp Choose from 1, 2 [1]: 1 Select cloud_provider: 1 - AWS - 2 - GCS - Choose from 1, 2 [1]: 1 + 2 - GCP + 3 - None + Choose from 1, 2, 3 [1]: 1 custom_bootstrap_compilation [n]: n Select open_source_license: 1 - MIT diff --git a/cookiecutter.json b/cookiecutter.json index a66bb732..d6d217ca 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -18,11 +18,8 @@ "use_pycharm": "n", "use_docker": "n", "postgresql_version": [ - "10.5", - "10.4", - "10.3", - "10.2", - "10.1", + "11.3", + "10.8", "9.6", "9.5", "9.4" @@ -33,7 +30,8 @@ ], "cloud_provider": [ "AWS", - "GCE" + "GCP", + "None" ], "custom_bootstrap_compilation": "n", "use_compressor": "n", diff --git a/docs/conf.py b/docs/conf.py index e3ddae9a..469aa12d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,7 +42,7 @@ master_doc = "index" # General information about the project. project = "Cookiecutter Django" -copyright = "2013-2018, Daniel Roy Greenfeld".format(now.year) +copyright = "2013-{}, Daniel Roy Greenfeld".format(now.year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst index aad54932..038778cf 100644 --- a/docs/deployment-with-docker.rst +++ b/docs/deployment-with-docker.rst @@ -35,7 +35,15 @@ Configuring the Stack The majority of services above are configured through the use of environment variables. Just check out :ref:`envs` and you will know the drill. -To obtain logs and information about crashes in a production setup, make sure that you have access to an external Sentry instance (e.g. by creating an account with `sentry.io`_), and set the ``SENTRY_DSN`` variable. +To obtain logs and information about crashes in a production setup, make sure that you have access to an external Sentry instance (e.g. by creating an account with `sentry.io`_), and set the ``SENTRY_DSN`` variable. Logs of level `logging.ERROR` are sent as Sentry events. Therefore, in order to send a Sentry event use: + +.. code-block:: python + + import logging + logging.error("This event is sent to Sentry", extra={"": ""}) + +The `extra` parameter allows you to send additional information about the context of this error. + You will probably also need to setup the Mail backend, for example by adding a `Mailgun`_ API key and a `Mailgun`_ sender domain, otherwise, the account creation view will crash and result in a 500 error when the backend attempts to send an email to the account owner. diff --git a/docs/developing-locally-docker.rst b/docs/developing-locally-docker.rst index da4e67aa..09e68498 100644 --- a/docs/developing-locally-docker.rst +++ b/docs/developing-locally-docker.rst @@ -6,6 +6,12 @@ Getting Up and Running Locally With Docker The steps below will get you up and running with a local development environment. All of these commands assume you are in the root of your generated project. +.. note:: + + If you're new to Docker, please be aware that some resources are cached system-wide + and might reappear if you generate a project multiple times with the same name (e.g. + :ref:`this issue with Postgres `). + Prerequisites ------------- diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index c3c4d3a2..afa9b8af 100644 --- a/docs/project-generation-options.rst +++ b/docs/project-generation-options.rst @@ -49,14 +49,11 @@ use_docker: postgresql_version: Select a PostgreSQL_ version to use. The choices are: - 1. 10.5 - 2. 10.4 - 3. 10.3 - 4. 10.2 - 5. 10.1 - 6. 9.6 - 7. 9.5 - 8. 9.4 + 1. 11.3 + 2. 10.8 + 3. 9.6 + 4. 9.5 + 5. 9.4 js_task_runner: Select a JavaScript task runner. The choices are: @@ -68,7 +65,10 @@ cloud_provider: Select a cloud provider for static & media files. The choices are: 1. AWS_ - 2. GCS_ + 2. GCP_ + 3. None + + Note that if you choose no cloud provider, media files won't work. custom_bootstrap_compilation: Indicates whether the project should support Bootstrap recompilation @@ -100,7 +100,7 @@ use_travisci: keep_local_envs_in_vcs: Indicates whether the project's ``.envs/.local/`` should be kept in VCS (comes in handy when working in teams where local environment reproducibility - is strongly encouraged). + is strongly encouraged). Note: .env(s) are only utilized when Docker Compose and/or Heroku support is enabled. debug: @@ -123,7 +123,7 @@ debug: .. _Gulp: https://github.com/gulpjs/gulp .. _AWS: https://aws.amazon.com/s3/ -.. _GCS: https://cloud.google.com/storage/ +.. _GCP: https://cloud.google.com/storage/ .. _Django Compressor: https://github.com/django-compressor/django-compressor diff --git a/docs/settings.rst b/docs/settings.rst index 1830a47c..e586c963 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -45,12 +45,13 @@ 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_STORAGE_BUCKET_NAME AWS_STORAGE_BUCKET_NAME n/a raises error DJANGO_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None -DJANGO_GCE_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 SENTRY_DSN SENTRY_DSN n/a raises error DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO -MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error +MAILGUN_API_KEY MAILGUN_API_KEY n/a raises error MAILGUN_DOMAIN MAILGUN_SENDER_DOMAIN n/a raises error +MAILGUN_API_URL n/a n/a "https://api.mailgun.net/v3" ======================================= =========================== ============================================== ====================================================================== -------------------------- diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 68db2fb0..8aa1b1f9 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -3,15 +3,49 @@ Troubleshooting This page contains some advice about errors and problems commonly encountered during the development of Cookiecutter Django applications. +Server Error on sign-up/log-in +------------------------------ + +Make sure you have configured the mail backend (e.g. Mailgun) by adding the API key and sender domain + +.. include:: mailgun.rst + +.. _docker-postgres-auth-failed: + +Docker: Postgres authentication failed +-------------------------------------- + +Examples of logs:: + + postgres_1 | 2018-06-07 19:11:23.963 UTC [81] FATAL: password authentication failed for user "pydanny" + postgres_1 | 2018-06-07 19:11:23.963 UTC [81] DETAIL: Password does not match for user "pydanny". + postgres_1 | Connection matched pg_hba.conf line 95: "host all all all md5" + +If you recreate the project multiple times with the same name, Docker would preserve the volumes for the postgres container between projects. Here is what happens: + +#. You generate the project the first time. The .env postgres file is populated with the random password +#. You run the docker-compose and the containers are created. The postgres container creates the database based on the .env file credentials +#. You "regenerate" the project with the same name, so the postgres .env file is populated with a new random password +#. You run docker-compose. Since the names of the containers are the same, docker will try to start them (not create them from scratch i.e. it won't execute the Dockerfile to recreate the database). When this happens, it tries to start the database based on the new credentials which do not match the ones that the database was created with, and you get the error message above. + +To fix this, you can either: + +- Clear your project-related Docker cache with ``docker-compose -f local.yml down --volumes --rmi all``. +- Use the Docker volume sub-commands to find volumes (`ls`_) and remove them (`rm`_). +- Use the `prune`_ command to clear system-wide (use with care!). + +.. _ls: https://docs.docker.com/engine/reference/commandline/volume_ls/ +.. _rm: https://docs.docker.com/engine/reference/commandline/volume_rm/ +.. _prune: https://docs.docker.com/v17.09/engine/reference/commandline/system_prune/ + +Others +------ + #. ``project_slug`` must be a valid Python module name or you will have issues on imports. #. ``jinja2.exceptions.TemplateSyntaxError: Encountered unknown tag 'now'.``: please upgrade your cookiecutter version to >= 1.4 (see `#528`_) -#. Internal server error on user registration: make sure you have configured the mail backend (e.g. Mailgun) by adding the API key and sender domain - #. New apps not getting created in project root: This is the expected behavior, because cookiecutter-django does not change the way that django startapp works, you'll have to fix this manually (see `#1725`_) -#. .. include:: mailgun.rst - .. _#528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373 .. _#1725: https://github.com/pydanny/cookiecutter-django/issues/1725#issuecomment-407493176 diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index ab05b375..ff84f180 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -89,8 +89,16 @@ def remove_packagejson_file(): os.remove(file_name) -def remove_celery_app(): - shutil.rmtree(os.path.join("{{ cookiecutter.project_slug }}", "taskapp")) +def remove_celery_files(): + file_names = [ + os.path.join("config", "celery_app.py"), + os.path.join("{{ cookiecutter.project_slug }}", "users", "tasks.py"), + os.path.join( + "{{ cookiecutter.project_slug }}", "users", "tests", "test_tasks.py" + ), + ] + for file_name in file_names: + os.remove(file_name) def remove_dottravisyml_file(): @@ -320,8 +328,14 @@ def main(): if "{{ cookiecutter.use_docker }}".lower() == "y": remove_node_dockerfile() + if "{{ cookiecutter.cloud_provider}}".lower() == "none": + print( + WARNING + "You chose not to use a cloud provider, " + "media files won't be served in production." + TERMINATOR + ) + if "{{ cookiecutter.use_celery }}".lower() == "n": - remove_celery_app() + remove_celery_files() if "{{ cookiecutter.use_docker }}".lower() == "y": remove_celery_compose_dirs() diff --git a/pytest.ini b/pytest.ini index c5b30199..89aeb302 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,7 @@ [pytest] +addopts = -x --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 diff --git a/requirements.txt b/requirements.txt index 3644de16..d6049ecb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,12 +5,13 @@ binaryornot==0.4.4 # Code quality # ------------------------------------------------------------------------------ black==19.3b0 -flake8==3.7.6 +flake8==3.7.7 # Testing # ------------------------------------------------------------------------------ -tox==3.9.0 -pytest==4.4.2 -pytest_cases==1.6.2 +tox==3.12.1 +pytest==4.6.0 +pytest_cases==1.6.3 pytest-cookies==0.3.0 +pytest-xdist==1.28.0 pyyaml==5.1 diff --git a/setup.py b/setup.py index 65bcd8fc..46c84b71 100644 --- a/setup.py +++ b/setup.py @@ -10,10 +10,10 @@ 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.0.2" +version = "2.2.1" if sys.argv[-1] == "tag": - os.system('git tag -a %s -m "version %s"' % (version, version)) + os.system(f'git tag -a {version} -m "version {version}"') os.system("git push --tags") sys.exit() @@ -34,7 +34,7 @@ setup( classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", - "Framework :: Django :: 2.0", + "Framework :: Django :: 2.2", "Intended Audience :: Developers", "Natural Language :: English", "License :: OSI Approved :: BSD License", diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index 17375b1c..4292f989 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -7,11 +7,11 @@ import sh import yaml from binaryornot.check import is_binary -PATTERN = "{{(\s?cookiecutter)[.](.*?)}}" +PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}" RE_OBJ = re.compile(PATTERN) YN_CHOICES = ["y", "n"] -CLOUD_CHOICES = ["AWS", "GCE"] +CLOUD_CHOICES = ["AWS", "GCE", "None"] @pytest.fixture @@ -101,9 +101,10 @@ def test_project_generation(cookies, context, context_combination): check_paths(paths) -def test_linting_passes(cookies, context_combination): +@pytest.mark.flake8 +def test_flake8_passes(cookies, context_combination): """ - Generated project should pass flake8 & black. + Generated project should pass flake8. This is parametrized for each combination from ``context_combination`` fixture """ @@ -114,6 +115,16 @@ def test_linting_passes(cookies, context_combination): except sh.ErrorReturnCode as e: pytest.fail(e) + +@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) + try: sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/") except sh.ErrorReturnCode as e: diff --git a/tox.ini b/tox.ini index cef3efc7..7ee93915 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,19 @@ [tox] skipsdist = true -envlist = py36,black +envlist = py36,flake8,black,black-template [testenv] deps = -rrequirements.txt -commands = pytest {posargs:./tests} +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} + +[testenv:black-template] deps = black commands = black --check hooks tests setup.py docs diff --git a/{{cookiecutter.project_slug}}/.envs/.production/.django b/{{cookiecutter.project_slug}}/.envs/.production/.django index a938ada6..2c2e94f2 100644 --- a/{{cookiecutter.project_slug}}/.envs/.production/.django +++ b/{{cookiecutter.project_slug}}/.envs/.production/.django @@ -22,11 +22,11 @@ MAILGUN_DOMAIN= DJANGO_AWS_ACCESS_KEY_ID= DJANGO_AWS_SECRET_ACCESS_KEY= DJANGO_AWS_STORAGE_BUCKET_NAME= -{% elif cookiecutter.cloud_provider == 'GCE' %} -# GCE +{% elif cookiecutter.cloud_provider == 'GCP' %} +# GCP # ------------------------------------------------------------------------------ GOOGLE_APPLICATION_CREDENTIALS= -DJANGO_GCE_STORAGE_BUCKET_NAME= +DJANGO_GCP_STORAGE_BUCKET_NAME= {% endif %} # django-allauth # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/Procfile b/{{cookiecutter.project_slug}}/Procfile index d9319f5c..5b8e9eaf 100644 --- a/{{cookiecutter.project_slug}}/Procfile +++ b/{{cookiecutter.project_slug}}/Procfile @@ -1,5 +1,5 @@ release: python manage.py migrate web: gunicorn config.wsgi:application {% if cookiecutter.use_celery == "y" -%} -worker: celery worker --app={{cookiecutter.project_slug}}.taskapp --loglevel=info +worker: celery worker --app=config.celery_app --loglevel=info {%- endif %} diff --git a/{{cookiecutter.project_slug}}/README.rst b/{{cookiecutter.project_slug}}/README.rst index b9dbf218..1deeafbe 100644 --- a/{{cookiecutter.project_slug}}/README.rst +++ b/{{cookiecutter.project_slug}}/README.rst @@ -79,7 +79,7 @@ To run a celery worker: .. code-block:: bash cd {{cookiecutter.project_slug}} - celery -A {{cookiecutter.project_slug}}.taskapp worker -l info + celery -A config.celery_app worker -l info Please note: For Celery's import magic to work, it is important *where* the celery commands are run. If you are in the same folder with *manage.py*, you should be right. diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start index 4e2493f3..389e2baf 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/beat/start @@ -5,4 +5,4 @@ set -o nounset rm -f './celerybeat.pid' -celery -A {{cookiecutter.project_slug}}.taskapp beat -l INFO +celery -A config.celery_app beat -l INFO diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start index f0abae7e..be67050d 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/flower/start @@ -5,6 +5,6 @@ set -o nounset celery flower \ - --app={{cookiecutter.project_slug}}.taskapp \ + --app=config.celery_app \ --broker="${CELERY_BROKER_URL}" \ --basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}" diff --git a/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start b/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start index c8bc31d3..072c6ae6 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start +++ b/{{cookiecutter.project_slug}}/compose/local/django/celery/worker/start @@ -4,4 +4,4 @@ set -o errexit set -o nounset -celery -A {{cookiecutter.project_slug}}.taskapp worker -l INFO +celery -A config.celery_app worker -l INFO diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start index def83076..0e793e38 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/beat/start @@ -5,4 +5,4 @@ set -o pipefail set -o nounset -celery -A {{cookiecutter.project_slug}}.taskapp beat -l INFO +celery -A config.celery_app beat -l INFO diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start index f0abae7e..be67050d 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/flower/start @@ -5,6 +5,6 @@ set -o nounset celery flower \ - --app={{cookiecutter.project_slug}}.taskapp \ + --app=config.celery_app \ --broker="${CELERY_BROKER_URL}" \ --basic_auth="${CELERY_FLOWER_USER}:${CELERY_FLOWER_PASSWORD}" diff --git a/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start b/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start index 10f0d20c..4e519c3f 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start +++ b/{{cookiecutter.project_slug}}/compose/production/django/celery/worker/start @@ -5,4 +5,4 @@ set -o pipefail set -o nounset -celery -A {{cookiecutter.project_slug}}.taskapp worker -l INFO +celery -A config.celery_app worker -l INFO diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.toml b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.toml index ad1f20e9..0f2abe8a 100644 --- a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.toml +++ b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.toml @@ -17,7 +17,7 @@ defaultEntryPoints = ["http", "https"] [acme] # Email address used for registration email = "{{ cookiecutter.email }}" -storageFile = "/etc/traefik/acme/acme.json" +storage = "/etc/traefik/acme/acme.json" entryPoint = "https" onDemand = false OnHostRule = true diff --git a/{{cookiecutter.project_slug}}/config/__init__.py b/{{cookiecutter.project_slug}}/config/__init__.py index e69de29b..480655af 100644 --- a/{{cookiecutter.project_slug}}/config/__init__.py +++ b/{{cookiecutter.project_slug}}/config/__init__.py @@ -0,0 +1,7 @@ +{% if cookiecutter.use_celery == 'y' -%} +# This will make sure the app is always imported when +# Django starts so that shared_task will use this app. +from .celery_app import app as celery_app + +__all__ = ("celery_app",) +{% endif -%} diff --git a/{{cookiecutter.project_slug}}/config/celery_app.py b/{{cookiecutter.project_slug}}/config/celery_app.py new file mode 100644 index 00000000..e275f054 --- /dev/null +++ b/{{cookiecutter.project_slug}}/config/celery_app.py @@ -0,0 +1,16 @@ +import os +from celery import Celery + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +app = Celery("{{cookiecutter.project_slug}}") + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object("django.conf:settings", namespace="CELERY") + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index 026c88cf..4a522fdd 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -35,6 +35,8 @@ USE_I18N = True 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")] # DATABASES # ------------------------------------------------------------------------------ @@ -75,7 +77,7 @@ THIRD_PARTY_APPS = [ "rest_framework", ] LOCAL_APPS = [ - "{{ cookiecutter.project_slug }}.users.apps.UsersAppConfig", + "{{ cookiecutter.project_slug }}.users.apps.UsersConfig", # Your stuff: custom apps go here ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps @@ -126,6 +128,7 @@ AUTH_PASSWORD_VALIDATORS = [ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", @@ -221,10 +224,33 @@ ADMINS = [("""{{cookiecutter.author_name}}""", "{{cookiecutter.email}}")] # https://docs.djangoproject.com/en/dev/ref/settings/#managers MANAGERS = ADMINS +# LOGGING +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#logging +# See https://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "verbose": { + "format": "%(levelname)s %(asctime)s %(module)s " + "%(process)d %(thread)d %(message)s" + } + }, + "handlers": { + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "verbose", + } + }, + "root": {"level": "INFO", "handlers": ["console"]}, +} + {% if cookiecutter.use_celery == 'y' -%} # Celery # ------------------------------------------------------------------------------ -INSTALLED_APPS += ["{{cookiecutter.project_slug}}.taskapp.celery.CeleryAppConfig"] if USE_TZ: # http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-timezone CELERY_TIMEZONE = TIME_ZONE @@ -240,10 +266,10 @@ CELERY_TASK_SERIALIZER = "json" CELERY_RESULT_SERIALIZER = "json" # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-time-limit # TODO: set to whatever value is adequate in your circumstances -CELERYD_TASK_TIME_LIMIT = 5 * 60 +CELERY_TASK_TIME_LIMIT = 5 * 60 # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-soft-time-limit # TODO: set to whatever value is adequate in your circumstances -CELERYD_TASK_SOFT_TIME_LIMIT = 60 +CELERY_TASK_SOFT_TIME_LIMIT = 60 {%- endif %} # django-allauth diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index 63225bee..d838dc4f 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -66,10 +66,12 @@ SECURE_CONTENT_TYPE_NOSNIFF = env.bool( "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True ) +{% if cookiecutter.cloud_provider != 'None' -%} # STORAGES # ------------------------------------------------------------------------------ # https://django-storages.readthedocs.io/en/latest/#installation INSTALLED_APPS += ["storages"] # noqa F405 +{%- endif -%} {% if cookiecutter.cloud_provider == 'AWS' %} # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings AWS_ACCESS_KEY_ID = env("DJANGO_AWS_ACCESS_KEY_ID") @@ -89,23 +91,24 @@ AWS_S3_OBJECT_PARAMETERS = { AWS_DEFAULT_ACL = None # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) -{% elif cookiecutter.cloud_provider == 'GCE' %} +{% elif cookiecutter.cloud_provider == 'GCP' %} DEFAULT_FILE_STORAGE = "storages.backends.gcloud.GoogleCloudStorage" -GS_BUCKET_NAME = env("DJANGO_GCE_STORAGE_BUCKET_NAME") +GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME") GS_DEFAULT_ACL = "publicRead" -{% endif %} +{% endif -%} +{% if cookiecutter.cloud_provider != 'None' or cookiecutter.use_whitenoise == 'y' -%} # STATIC # ------------------------ +{% endif -%} {% if cookiecutter.use_whitenoise == 'y' -%} STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" -{%- endif -%} -{%- if cookiecutter.cloud_provider == 'AWS' %} +{% elif cookiecutter.cloud_provider == 'AWS' -%} STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage" STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/" -{%- elif cookiecutter.cloud_provider == 'GCE' %} -STATIC_URL = "https://storage.googleapis.com/{}/static/".format(GS_BUCKET_NAME) -{%- endif %} +{% elif cookiecutter.cloud_provider == 'GCP' -%} +STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/" +{% endif -%} # MEDIA # ------------------------------------------------------------------------------ @@ -117,6 +120,7 @@ from storages.backends.s3boto3 import S3Boto3Storage # noqa E402 class StaticRootS3Boto3Storage(S3Boto3Storage): location = "static" + default_acl = "public-read" class MediaRootS3Boto3Storage(S3Boto3Storage): @@ -127,9 +131,9 @@ class MediaRootS3Boto3Storage(S3Boto3Storage): # endregion DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage" MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/" -{%- elif cookiecutter.cloud_provider == 'GCE' %} -MEDIA_URL = "https://storage.googleapis.com/{}/media/".format(GS_BUCKET_NAME) -MEDIA_ROOT = "https://storage.googleapis.com/{}/media/".format(GS_BUCKET_NAME) +{%- elif cookiecutter.cloud_provider == 'GCP' %} +MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" +MEDIA_ROOT = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" {%- endif %} # TEMPLATES @@ -172,6 +176,7 @@ 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"), } # Gunicorn @@ -193,7 +198,7 @@ COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True) # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE COMPRESS_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL -COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' %} # noqa F405{% endif %} +COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' or cookiecutter.cloud_provider == 'None' %} # noqa F405{% endif %} {% endif %} {%- if cookiecutter.use_whitenoise == 'n' -%} # Collectfast @@ -233,6 +238,7 @@ LOGGING = { "formatter": "verbose", }, }, + "root": {"level": "INFO", "handlers": ["console"]}, "loggers": { "django.request": { "handlers": ["mail_admins"], @@ -263,6 +269,7 @@ LOGGING = { "formatter": "verbose", } }, + "root": {"level": "INFO", "handlers": ["console"]}, "loggers": { "django.db.backends": { "level": "ERROR", @@ -286,7 +293,7 @@ SENTRY_LOG_LEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO) sentry_logging = LoggingIntegration( level=SENTRY_LOG_LEVEL, # Capture info and above as breadcrumbs - event_level=None, # Send no events from log messages + event_level=logging.ERROR, # Send errors as events ) {%- if cookiecutter.use_celery == 'y' %} diff --git a/{{cookiecutter.project_slug}}/local.yml b/{{cookiecutter.project_slug}}/local.yml index c6dd654e..60f8f922 100644 --- a/{{cookiecutter.project_slug}}/local.yml +++ b/{{cookiecutter.project_slug}}/local.yml @@ -45,7 +45,7 @@ services: {%- if cookiecutter.use_celery == 'y' %} redis: - image: redis:3.2 + image: redis:5.0 celeryworker: <<: *django diff --git a/{{cookiecutter.project_slug}}/production.yml b/{{cookiecutter.project_slug}}/production.yml index a24ba829..331cbba6 100644 --- a/{{cookiecutter.project_slug}}/production.yml +++ b/{{cookiecutter.project_slug}}/production.yml @@ -44,7 +44,7 @@ services: - "0.0.0.0:443:443" redis: - image: redis:3.2 + image: redis:5.0 {%- if cookiecutter.use_celery == 'y' %} celeryworker: diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index 29e7f2b9..00845cc2 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -18,13 +18,13 @@ flower==0.9.3 # https://github.com/mher/flower # Django # ------------------------------------------------------------------------------ -django==2.1.8 # pyup: < 2.2 # https://www.djangoproject.com/ +django==2.2.2 # pyup: < 3.0 # https://www.djangoproject.com/ django-environ==0.4.5 # https://github.com/joke2k/django-environ django-model-utils==3.1.2 # https://github.com/jazzband/django-model-utils django-allauth==0.39.1 # https://github.com/pennersr/django-allauth django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-crispy-forms {%- if cookiecutter.use_compressor == "y" %} -django-compressor==2.2 # https://github.com/django-compressor/django-compressor +django-compressor==2.3 # https://github.com/django-compressor/django-compressor {%- endif %} django-redis==4.10.0 # https://github.com/niwinz/django-redis diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index 3ef434e9..c5cd6d9f 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -1,10 +1,10 @@ -r ./base.txt -Werkzeug==0.15.2 # https://github.com/pallets/werkzeug +Werkzeug==0.14.1 # pyup: < 0.15 # https://github.com/pallets/werkzeug ipdb==0.12 # https://github.com/gotcha/ipdb -Sphinx==2.0.1 # https://github.com/sphinx-doc/sphinx +Sphinx==2.1.0 # https://github.com/sphinx-doc/sphinx {%- if cookiecutter.use_docker == 'y' %} -psycopg2==2.8 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 +psycopg2==2.8.2 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- else %} psycopg2-binary==2.8.2 # https://github.com/psycopg/psycopg2 {%- endif %} @@ -12,12 +12,12 @@ psycopg2-binary==2.8.2 # https://github.com/psycopg/psycopg2 # Testing # ------------------------------------------------------------------------------ mypy==0.701 # https://github.com/python/mypy -pytest==4.4.2 # https://github.com/pytest-dev/pytest +pytest==4.6.0 # https://github.com/pytest-dev/pytest pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar # Code quality # ------------------------------------------------------------------------------ -flake8==3.7.5 # https://github.com/PyCQA/flake8 +flake8==3.7.7 # https://github.com/PyCQA/flake8 coverage==4.5.3 # https://github.com/nedbat/coveragepy black==19.3b0 # https://github.com/ambv/black pylint-django==2.0.9 # https://github.com/PyCQA/pylint-django @@ -27,9 +27,9 @@ pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery # Django # ------------------------------------------------------------------------------ -factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy +factory-boy==2.12.0 # https://github.com/FactoryBoy/factory_boy django-debug-toolbar==1.11 # https://github.com/jazzband/django-debug-toolbar -django-extensions==2.1.6 # https://github.com/django-extensions/django-extensions +django-extensions==2.1.7 # https://github.com/django-extensions/django-extensions django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin pytest-django==3.4.8 # https://github.com/pytest-dev/pytest-django diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index 4ae99be3..8cf1f1c4 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -3,19 +3,19 @@ -r ./base.txt gunicorn==19.9.0 # https://github.com/benoitc/gunicorn -psycopg2==2.8 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 +psycopg2==2.8.2 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- if cookiecutter.use_whitenoise == 'n' %} Collectfast==0.6.2 # https://github.com/antonagestam/collectfast {%- endif %} {%- if cookiecutter.use_sentry == "y" %} -sentry-sdk==0.7.14 # https://github.com/getsentry/sentry-python +sentry-sdk==0.9.0 # https://github.com/getsentry/sentry-python {%- endif %} # Django # ------------------------------------------------------------------------------ {%- if cookiecutter.cloud_provider == 'AWS' %} django-storages[boto3]==1.7.1 # https://github.com/jschneier/django-storages -{%- elif cookiecutter.cloud_provider == 'GCE' %} +{%- elif cookiecutter.cloud_provider == 'GCP' %} django-storages[google]==1.7.1 # https://github.com/jschneier/django-storages {%- endif %} -django-anymail[mailgun]==6.0 # https://github.com/anymail/django-anymail +django-anymail[mailgun]==6.0.1 # https://github.com/anymail/django-anymail diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py deleted file mode 100644 index 529da1ae..00000000 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py +++ /dev/null @@ -1,38 +0,0 @@ -{% if cookiecutter.use_celery == 'y' -%} -import os -from celery import Celery -from django.apps import apps, AppConfig -from django.conf import settings - - -if not settings.configured: - # set the default Django settings module for the 'celery' program. - os.environ.setdefault( - "DJANGO_SETTINGS_MODULE", "config.settings.local" - ) # pragma: no cover - - -app = Celery("{{cookiecutter.project_slug}}") -# Using a string here means the worker will not have to -# pickle the object when using Windows. -# - namespace='CELERY' means all celery-related configuration keys -# should have a `CELERY_` prefix. -app.config_from_object("django.conf:settings", namespace="CELERY") - - -class CeleryAppConfig(AppConfig): - name = "{{cookiecutter.project_slug}}.taskapp" - verbose_name = "Celery Config" - - def ready(self): - installed_apps = [app_config.name for app_config in apps.get_app_configs()] - app.autodiscover_tasks(lambda: installed_apps, force=True) - - -@app.task(bind=True) -def debug_task(self): - print(f"Request: {self.request!r}") # pragma: no cover -{% else %} -# Use this as a starting point for your project with celery. -# If you are not using celery, you can remove this app -{% endif -%} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py index ded2072f..2241e5eb 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py @@ -1,10 +1,10 @@ from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ -class UsersAppConfig(AppConfig): - +class UsersConfig(AppConfig): name = "{{ cookiecutter.project_slug }}.users" - verbose_name = "Users" + verbose_name = _("Users") def ready(self): try: diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py new file mode 100644 index 00000000..c99341c5 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tasks.py @@ -0,0 +1,11 @@ +from django.contrib.auth import get_user_model + +from config import celery_app + +User = get_user_model() + + +@celery_app.task() +def get_users_count(): + """A pointless Celery task to demonstrate usage.""" + return User.objects.count() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py new file mode 100644 index 00000000..addb091d --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py @@ -0,0 +1,16 @@ +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 + + +@pytest.mark.django_db +def test_user_count(settings): + """A basic test to execute the get_users_count Celery task.""" + UserFactory.create_batch(3) + settings.CELERY_TASK_ALWAYS_EAGER = True + task_result = get_users_count.delay() + assert isinstance(task_result, EagerResult) + assert task_result.result == 3 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 20bd3dba..c6361920 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 @@ -13,11 +13,6 @@ def test_detail(user: settings.AUTH_USER_MODEL): assert resolve(f"/users/{user.username}/").view_name == "users:detail" -def test_list(): - assert reverse("users:list") == "/users/" - assert resolve("/users/").view_name == "users:list" - - def test_update(): assert reverse("users:update") == "/users/~update/" assert resolve("/users/~update/").view_name == "users:update" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py index 2502a0c0..eff24dd0 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py @@ -1,7 +1,6 @@ from django.urls import path from {{ cookiecutter.project_slug }}.users.views import ( - user_list_view, user_redirect_view, user_update_view, user_detail_view, @@ -9,7 +8,6 @@ from {{ cookiecutter.project_slug }}.users.views import ( app_name = "users" urlpatterns = [ - path("", view=user_list_view, name="list"), path("~redirect/", view=user_redirect_view, name="redirect"), path("~update/", view=user_update_view, name="update"), path("/", view=user_detail_view, name="detail"), diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py index 35e26e94..a2442741 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py @@ -1,7 +1,7 @@ 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, ListView, RedirectView, UpdateView +from django.views.generic import DetailView, RedirectView, UpdateView User = get_user_model() @@ -16,16 +16,6 @@ class UserDetailView(LoginRequiredMixin, DetailView): user_detail_view = UserDetailView.as_view() -class UserListView(LoginRequiredMixin, ListView): - - model = User - slug_field = "username" - slug_url_kwarg = "username" - - -user_list_view = UserListView.as_view() - - class UserUpdateView(LoginRequiredMixin, UpdateView): model = User