diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 75f6a19c..2a48c0c3 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -17,7 +17,5 @@ about: Report a bug [//]: # (Any or all of the following:) [//]: # (* Host system configuration: OS, Docker & friends' versions etc.) -[//]: # (* Project generation options) +[//]: # (* Replay file https://cookiecutter.readthedocs.io/en/latest/advanced/replay.html) [//]: # (* Logs) - - diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 6b3f0066..dc41463f 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -57,7 +57,9 @@ Listed in alphabetical order. Andrew Mikhnevich `@zcho`_ Andy Rose Anna Callahan `@jazztpt`_ + Anna Sidwell `@takkaria`_ Antonia Blair `@antoniablair`_ @antoniablairart + Anuj Bansal `@ahhda`_ Arcuri Davide `@dadokkio`_ Areski Belaid `@areski`_ Ashley Camba @@ -69,7 +71,9 @@ 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`_ Cédric Gaspoz `@cgaspoz`_ Charlie Smith `@chuckus`_ @@ -78,6 +82,7 @@ Listed in alphabetical order. Chris Franklin `@hairychris`_ Chris Pappalardo `@ChrisPappalardo`_ Christopher Clarke `@chrisdev`_ + Cole Mackenzie `@cmackenzie1`_ Collederas `@Collederas`_ Cristian Vargas `@cdvv7788`_ Cullen Rhodes `@c-rhodes`_ @@ -85,10 +90,12 @@ Listed in alphabetical order. Daniel Hepper `@dhepper`_ @danielhepper Daniele Tricoli `@eriol`_ David Díaz `@ddiazpinto`_ @DavidDiazPinto + Davit Tovmasyan `@davitovmasyan`_ Davur Clementsen `@dsclementsen`_ @davur Delio Castillo `@jangeador`_ @jangeador Denis Orehovsky `@apirobot`_ Dónal Adams `@epileptic-fish`_ + Diane Chen `@purplediane`_ @purplediane88 Dong Huynh `@trungdong`_ Emanuel Calso `@bloodpet`_ @bloodpet Eraldo Energy `@eraldo`_ @@ -99,11 +106,15 @@ Listed in alphabetical order. Garry Polley `@garrypolley`_ Hamish Durkin `@durkode`_ Harry Percival `@hjwp`_ + Hendrik Schneider `@hendrikschneider`_ Henrique G. G. Pereira `@ikkebr`_ Ian Lee `@IanLee1521`_ + Irfan Ahmad `@erfaan`_ @erfaan Jan Van Bruggen `@jvanbrug`_ Jens Nilsson `@phiberjenz`_ + Jerome Leclanche `@jleclanche`_ @Adys Jimmy Gitonga `@afrowave`_ @afrowave + John Cass `@jcass77`_ @cass_john Julien Almarcha `@sladinji`_ Julio Castillo `@juliocc`_ Kaido Kert `@kaidokert`_ @@ -115,6 +126,7 @@ Listed in alphabetical order. Krzysztof Szumny `@noisy`_ Krzysztof Żuraw `@krzysztofzuraw`_ Leonardo Jimenez `@xpostudio4`_ + Leo Zhou `@glasslion`_ Lin Xianyi `@iynaix`_ Luis Nell `@originell`_ Lukas Klein @@ -122,6 +134,7 @@ Listed in alphabetical order. Malik Sulaimanov `@flyudvik`_ @flyudvik Martin Blech Martin Saizar `@msaizar`_ + Mateusz Ostaszewski `@mostaszewski`_ Mathijs Hoogland `@MathijsHoogland`_ Matt Braymer-Hayes `@mattayes`_ @mattayes Matt Linares @@ -135,6 +148,7 @@ Listed in alphabetical order. Pablo `@oubiga`_ Parbhat Puri `@parbhat`_ Peter Bittner `@bittner`_ + Peter Coles `@mrcoles`_ Pierre Chiquet `@pchiquet`_ Raphael Pierzina `@hackebrot`_ Raony Guimarães Corrêa `@raonyguimaraes`_ @@ -157,12 +171,17 @@ Listed in alphabetical order. Tom Atkins `@knitatoms`_ Tom Offermann Travis McNeill `@Travistock`_ @tavistock_esq + Tubo Shi `@Tubo`_ + Umair Ashraf `@umrashrf`_ @fabumair Vitaly Babiy Vivian Guillen `@viviangb`_ Will Farley `@goldhand`_ @g01dhand William Archinal `@archinal`_ Yaroslav Halchenko - Denis Bobrov `@delneg`_ + Denis Bobrov `@delneg`_ + Philipp Matthies `@canonnervio`_ + Vadim Iskuchekov `@Egregors`_ @egregors + Keith Bailey `@keithjeb`_ ========================== ============================ ============== .. _@a7p: https://github.com/a7p @@ -185,6 +204,7 @@ Listed in alphabetical order. .. _@burhan: https://github.com/burhan .. _@c-rhodes: https://github.com/c-rhodes .. _@caffodian: https://github.com/caffodian +.. _@carlmjohnson: https://github.com/carlmjohnson .. _@catherinedevlin: https://github.com/catherinedevlin .. _@ccurvey: https://github.com/ccurvey .. _@cdvv7788: https://github.com/cdvv7788 @@ -192,7 +212,9 @@ Listed in alphabetical order. .. _@chrisdev: https://github.com/chrisdev .. _@ChrisPappalardo: https://github.com/ChrisPappalardo .. _@chuckus: https://github.com/chuckus +.. _@cmackenzie1: https://github.com/cmackenzie1 .. _@Collederas: https://github.com/Collederas +.. _@davitovmasyan: https://github.com/davitovmasyan .. _@ddiazpinto: https://github.com/ddiazpinto .. _@dezoito: https://github.com/dezoito .. _@dhepper: https://github.com/dhepper @@ -201,19 +223,23 @@ Listed in alphabetical order. .. _@durkode: https://github.com/durkode .. _@epileptic-fish: https://gihub.com/epileptic-fish .. _@eraldo: https://github.com/eraldo +.. _@erfaan: https://github.com/erfaan .. _@eriol: https://github.com/eriol .. _@eyadsibai: https://github.com/eyadsibai .. _@flyudvik: https://github.com/flyudvik .. _@garry-cairns: https://github.com/garry-cairns .. _@garrypolley: https://github.com/garrypolley .. _@goldhand: https://github.com/goldhand +.. _@glasslion: https://github.com/glasslion .. _@hackebrot: https://github.com/hackebrot .. _@hairychris: https://github.com/hairychris +.. _@hendrikschneider: https://github.com/hendrikschneider .. _@hjwp: https://github.com/hjwp .. _@IanLee1521: https://github.com/IanLee1521 .. _@ikkebr: https://github.com/ikkebr .. _@iynaix: https://github.com/iynaix .. _@jazztpt: https://github.com/jazztpt +.. _@jleclanche: https://github.com/jleclanche .. _@juliocc: https://github.com/juliocc .. _@jvanbrug: https://github.com/jvanbrug .. _@ka7eh: https://github.com/ka7eh @@ -226,6 +252,7 @@ Listed in alphabetical order. .. _@MathijsHoogland: https://github.com/MathijsHoogland .. _@mattayes: https://github.com/mattayes .. _@menzenski: https://github.com/menzenski +.. _@mostaszewski: https://github.com/mostaszewski .. _@mfwarren: https://github.com/mfwarren .. _@mimischi: https://github.com/mimischi .. _@mjsisley: https://github.com/mjsisley @@ -247,9 +274,11 @@ Listed in alphabetical order. .. _@ssteinerX: https://github.com/ssteinerx .. _@stepmr: https://github.com/stepmr .. _@suledev: https://github.com/suledev +.. _@takkaria: https://github.com/takkaria .. _@timfreund: https://github.com/timfreund .. _@Travistock: https://github.com/Tavistock .. _@trungdong: https://github.com/trungdong +.. _@Tubo: https://github.com/tubo .. _@viviangb: https://github.com/viviangb .. _@xpostudio4: https://github.com/xpostudio4 .. _@yunti: https://github.com/yunti @@ -267,6 +296,11 @@ Listed in alphabetical order. .. _@afrowave: https://github.com/afrowave .. _@pchiquet: https://github.com/pchiquet .. _@delneg: https://github.com/delneg +.. _@purplediane: https://github.com/purplediane +.. _@umrashrf: https://github.com/umrashrf +.. _@ahhda: https://github.com/ahhda +.. _@keithjeb: https://github.com/keithjeb +.. _@btknu: https://github.com/btknu Special Thanks ~~~~~~~~~~~~~~ diff --git a/README.rst b/README.rst index a69712d0..b9e71ace 100644 --- a/README.rst +++ b/README.rst @@ -28,8 +28,6 @@ production-ready Django projects quickly. * If you have problems with Cookiecutter Django, please open issues_ don't send emails to the maintainers. -.. _cookiecutter: https://github.com/audreyr/cookiecutter - .. _Troubleshooting: https://cookiecutter-django.readthedocs.io/en/latest/troubleshooting.html .. _528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373 @@ -41,13 +39,13 @@ Features * For Django 2.0 * Works with Python 3.6 * Renders Django projects with 100% starting test coverage -* Twitter Bootstrap_ v4.0.0 (`maintained Foundation fork`_ also available) +* Twitter Bootstrap_ v4.1.1 (`maintained Foundation fork`_ also available) * 12-Factor_ based settings via django-environ_ * Secure by default. We believe in SSL. * Optimized development and production settings * Registration via django-allauth_ * Comes with custom user model ready to go -* Grunt build for compass and livereload +* Optional custom static build using Gulp and livereload * Send emails via Anymail_ (using Mailgun_ by default, but switchable) * Media storage using Amazon S3 * Docker support using docker-compose_ for development and production (using Caddy_ with LetsEncrypt_ support) @@ -180,10 +178,9 @@ Answer the prompts with your own desired options_. For example:: 7 - 9.3 Choose from 1, 2, 3, 4 [1]: 1 Select js_task_runner: - 1 - Gulp - 2 - Grunt - 3 - None - Choose from 1, 2, 3, 4 [1]: 1 + 1 - None + 2 - Gulp + Choose from 1, 2 [1]: 1 custom_bootstrap_compilation [n]: n Select open_source_license: 1 - MIT diff --git a/cookiecutter.json b/cookiecutter.json index 21a639a3..b5dda0c7 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,6 +1,6 @@ { "project_name": "My Awesome Project", - "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_') }}", + "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}", "description": "Behold My Awesome Project!", "author_name": "Daniel Roy Greenfeld", "domain_name": "example.com", @@ -18,6 +18,7 @@ "use_pycharm": "n", "use_docker": "n", "postgresql_version": [ + "10.5", "10.4", "10.3", "10.2", diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/docs/deployment-on-heroku.rst b/docs/deployment-on-heroku.rst index 7006a287..f753aa5a 100644 --- a/docs/deployment-on-heroku.rst +++ b/docs/deployment-on-heroku.rst @@ -10,6 +10,8 @@ Run these commands to deploy the project to Heroku: heroku create --buildpack https://github.com/heroku/heroku-buildpack-python heroku addons:create heroku-postgresql:hobby-dev + # On Windows use double quotes for the time zone, e.g. + # heroku pg:backups schedule --at "02:00 America/Los_Angeles" DATABASE_URL heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL heroku pg:promote DATABASE_URL @@ -21,17 +23,27 @@ Run these commands to deploy the project to Heroku: heroku addons:create sentry:f1 heroku config:set PYTHONHASHSEED=random + heroku config:set WEB_CONCURRENCY=4 + heroku config:set DJANGO_DEBUG=False heroku config:set DJANGO_SETTINGS_MODULE=config.settings.production heroku config:set DJANGO_SECRET_KEY="$(openssl rand -base64 64)" + # Generating a 32 character-long random string without any of the visually similiar characters "IOl01": heroku config:set DJANGO_ADMIN_URL="$(openssl rand -base64 4096 | tr -dc 'A-HJ-NP-Za-km-z2-9' | head -c 32)/" - heroku config:set DJANGO_ALLOWED_HOSTS= # Set this to your Heroku app url, e.g. 'bionic-beaver-28392.herokuapp.com' - - heroku config:set DJANGO_AWS_ACCESS_KEY_ID= # Assign with AWS_ACCESS_KEY_ID - heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY= # Assign with AWS_SECRET_ACCESS_KEY - heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME= # Assign with AWS_STORAGE_BUCKET_NAME + + # Set this to your Heroku app url, e.g. 'bionic-beaver-28392.herokuapp.com' + heroku config:set DJANGO_ALLOWED_HOSTS= + + # Assign with AWS_ACCESS_KEY_ID + heroku config:set DJANGO_AWS_ACCESS_KEY_ID= + + # Assign with AWS_SECRET_ACCESS_KEY + heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY= + + # Assign with AWS_STORAGE_BUCKET_NAME + heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME= git push heroku master diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst index b2166824..f6e21e82 100644 --- a/docs/deployment-with-docker.rst +++ b/docs/deployment-with-docker.rst @@ -59,7 +59,7 @@ SSL (Secure Sockets Layer) is a standard security technology for establishing an It is always better to deploy a site behind HTTPS and will become crucial as the web services extend to the IoT (Internet of Things). For this reason, we have set up a number of security defaults to help make your website secure: -* If you are not using a subdomain of the domain name set in the project, then remember to put the your staging/production IP address in the ``DJANGO_ALLOWED_HOSTS`` environment variable (see :ref:`settings`) before you deploy your website. Failure to do this will mean you will not have access to your website through the HTTP protocol. +* If you are not using a subdomain of the domain name set in the project, then remember to put your staging/production IP address in the ``DJANGO_ALLOWED_HOSTS`` environment variable (see :ref:`settings`) before you deploy your website. Failure to do this will mean you will not have access to your website through the HTTP protocol. * Access to the Django admin is set up by default to require HTTPS in production or once *live*. diff --git a/docs/developing-locally-docker.rst b/docs/developing-locally-docker.rst index 08b25f3b..895140f9 100644 --- a/docs/developing-locally-docker.rst +++ b/docs/developing-locally-docker.rst @@ -171,6 +171,16 @@ When developing locally you can go with MailHog_ for email testing provided ``us .. _Mailhog: https://github.com/mailhog/MailHog/ +.. _`CeleryTasks`: + +Celery tasks in local development +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +When not using docker Celery tasks are set to run in Eager mode, so that a full stack is not needed. When using docker the task +scheduler will be used by default. + +If you need tasks to be executed on the main thread during development set CELERY_TASK_ALWAYS_EAGER = True in config/settings/local.py. + +Possible uses could be for testing, or ease of profiling with DJDT. .. _`CeleryFlower`: diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst index 7885f45e..3434f68b 100644 --- a/docs/developing-locally.rst +++ b/docs/developing-locally.rst @@ -9,23 +9,54 @@ Setting Up Development Environment Make sure to have the following on your host: -* virtualenv_; -* pip; -* PostgreSQL. +* Python 3.6 +* PostgreSQL_. +* Redis_, if using Celery First things first. -#. `Create a virtualenv`_. +#. Create a virtualenv: :: -#. Activate the virtualenv you have just created. + $ python3.6 -m venv + +#. Activate the virtualenv you have just created: :: + + $ source /bin/activate #. Install development requirements: :: $ pip install -r requirements/local.txt -#. Create a new PostgreSQL database (note: if this is the first time a database is created on your machine you might need to alter a localhost-related entry in your ``pg_hba.conf`` so as to utilize ``trust`` policy): :: +#. Create a new PostgreSQL database using createdb_: :: - $ createdb + $ createdb -U postgres --password + + .. note:: + + if this is the first time a database is created on your machine you might need an + `initial PostgreSQL set up`_ to allow local connections & set a password for + the ``postgres`` user. The `postgres documentation`_ explains the syntax of the config file + that you need to change. + + +#. Set the environment variables for your database(s): :: + + $ export DATABASE_URL=postgres://postgres:@127.0.0.1:5432/ + # Optional: set broker URL if using Celery + $ export CELERY_BROKER_URL=redis://localhost:6379/0 + + .. note:: + + Check out the :ref:`settings` page for a comprehensive list of the environments variables. + + .. seealso:: + + To help setting up your environment variables, you have a few options: + + * create an ``.env`` file in the root of your project and define all the variables you need in it. + Then you just need to have ``DJANGO_READ_DOT_ENV_FILE=True`` in your machine and all the variables + will be read. + * Use a local environment manager like `direnv`_ #. Apply migrations: :: @@ -35,8 +66,12 @@ First things first. $ python manage.py runserver 0.0.0.0:8000 -.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ -.. _`Create a virtualenv`: https://virtualenv.pypa.io/en/stable/userguide/ +.. _PostgreSQL: https://www.postgresql.org/download/ +.. _Redis: https://redis.io/download +.. _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 +.. _postgres documentation: https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html +.. _direnv: https://direnv.net/ Setup Email Backend @@ -69,9 +104,7 @@ For instance, one of the packages we depend upon, ``django-allauth`` sends verif Now you have your own mail server running locally, ready to receive whatever you send it. -.. _MailHog: https://github.com/mailhog/MailHog/ -.. _`properly configured`: https://docs.djangoproject.com/en/dev/topics/email/#smtp-backend - +.. _`Download the latest MailHog release`: https://github.com/mailhog/MailHog Console ~~~~~~~ @@ -85,14 +118,21 @@ In production, we have Mailgun_ configured to have your back! .. _Mailgun: https://www.mailgun.com/ +Celery +------ +If the project is configured to use Celery as a task scheduler then by default tasks are set to run on the main thread +when developing locally. If you have the appropriate setup on your local machine then set + +CELERY_TASK_ALWAYS_EAGER = False + +in /config/settings/local.py + + Sass Compilation & Live Reloading --------------------------------- -If you’d like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with a little bit of preparation_. - -.. _`Download the latest MailHog release`: https://github.com/mailhog/MailHog/releases -.. _preparation: https://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html - +If you’d like to take advantage of live reloading and Sass compilation you can do so with a little +bit of preparation, see :ref:`sass-compilation-live-reload`. Summary ------- diff --git a/docs/index.rst b/docs/index.rst index c9f70ab1..5cb07b4b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,7 +17,7 @@ Contents: developing-locally-docker settings linters - live-reloading-and-sass-compilation + testing deployment-on-pythonanywhere deployment-on-heroku deployment-with-docker diff --git a/docs/installing_postgres.rst b/docs/installing_postgres.rst deleted file mode 100644 index 3b37e819..00000000 --- a/docs/installing_postgres.rst +++ /dev/null @@ -1,17 +0,0 @@ -PostgreSQL Installation Basics -============================== - -.. index:: pip, virtualenv, PostgreSQL - -The steps below will get you up and running with PostgreSQL. This assumes you have pip and virtualenv_ installed. - -.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ - -On Mac - -Install PostgreSQLapp_ from the browser and move PostGresSQL into your applications folder. Then install PostgreSQL from HomeBrew_. - - $ brew install postgres - -.. _PostgreSQLapp: http://postgresapp.com/ -.. _HomeBrew: http://brew.sh/ \ No newline at end of file diff --git a/docs/linters.rst b/docs/linters.rst index feb59d03..e59ff0df 100644 --- a/docs/linters.rst +++ b/docs/linters.rst @@ -5,9 +5,9 @@ Linters flake8 -------- +------ -To run flake8: +To run flake8: :: $ flake8 @@ -19,7 +19,7 @@ The config for flake8 is located in setup.cfg. It specifies: pylint ------ -This is included in flake8's checks, but you can also run it separately to see a more detailed report: +This is included in flake8's checks, but you can also run it separately to see a more detailed report: :: $ pylint @@ -31,9 +31,9 @@ The config for pylint is located in .pylintrc. It specifies: * max-parents=13 pycodestyle ------ +----------- -This is included in flake8's checks, but you can also run it separately to see a more detailed report: +This is included in flake8's checks, but you can also run it separately to see a more detailed report: :: $ pycodestyle diff --git a/docs/live-reloading-and-sass-compilation.rst b/docs/live-reloading-and-sass-compilation.rst index e2007cb0..a55b4fd8 100644 --- a/docs/live-reloading-and-sass-compilation.rst +++ b/docs/live-reloading-and-sass-compilation.rst @@ -1,24 +1,24 @@ +.. _sass-compilation-live-reload: + Sass Compilation & Live Reloading ================================= -If you'd like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with a little bit of prep work. +If you'd like to take advantage of `live reload`_ and Sass compilation: -Make sure that nodejs_ is installed. Then in the project root run:: +- Make sure that nodejs_ is installed. Then in the project root run:: $ npm install .. _nodejs: http://nodejs.org/download/ -If you don't already have it, install `compass` (doesn't hurt if you run this command twice):: - - gem install compass - -Now you just need:: +- Now you just need:: $ npm run dev The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled. +When changing your Sass files, they will be automatically recompiled and change will be reflected in your browser without refreshing. To get live reloading to work you'll probably need to install an `appropriate browser extension`_ +.. _live reload: http://livereload.com/ .. _appropriate browser extension: http://livereload.com/extensions/ diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index 60453b87..a5483797 100644 --- a/docs/project-generation-options.rst +++ b/docs/project-generation-options.rst @@ -93,7 +93,8 @@ 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: Indicates whether the project should be configured for debugging. diff --git a/docs/settings.rst b/docs/settings.rst index 6e71a515..26b161a0 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -1,7 +1,7 @@ .. _settings: Settings -========== +======== This project relies extensively on environment settings which **will not work with Apache/mod_wsgi setups**. It has been deployed successfully with both Gunicorn/Nginx and even uWSGI/Nginx. @@ -18,11 +18,10 @@ DJANGO_READ_DOT_ENV_FILE READ_DOT_ENV_FILE False ======================================= =========================== ============================================== ====================================================================== Environment Variable Django Setting Development Default Production Default ======================================= =========================== ============================================== ====================================================================== +DATABASE_URL DATABASES auto w/ Docker; postgres://project_slug w/o raises error DJANGO_ADMIN_URL n/a 'admin/' raises error -DJANGO_CACHES CACHES (default) locmem redis -DJANGO_DATABASES DATABASES (default) See code See code DJANGO_DEBUG DEBUG True False -DJANGO_SECRET_KEY SECRET_KEY !!!SET DJANGO_SECRET_KEY!!! raises error +DJANGO_SECRET_KEY SECRET_KEY auto-generated raises error DJANGO_SECURE_BROWSER_XSS_FILTER SECURE_BROWSER_XSS_FILTER n/a True DJANGO_SECURE_SSL_REDIRECT SECURE_SSL_REDIRECT n/a True DJANGO_SECURE_CONTENT_TYPE_NOSNIFF SECURE_CONTENT_TYPE_NOSNIFF n/a True @@ -41,6 +40,7 @@ The following table lists settings and their defaults for third-party applicatio ======================================= =========================== ============================================== ====================================================================== Environment Variable Django Setting Development Default Production Default ======================================= =========================== ============================================== ====================================================================== +CELERY_BROKER_URL CELERY_BROKER_URL auto w/ Docker; raises error w/o raises error DJANGO_AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID 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 @@ -49,8 +49,6 @@ DJANGO_SENTRY_CLIENT SENTRY_CLIENT n/a DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error MAILGUN_DOMAIN MAILGUN_SENDER_DOMAIN n/a raises error -NEW_RELIC_APP_NAME NEW_RELIC_APP_NAME n/a raises error -NEW_RELIC_LICENSE_KEY NEW_RELIC_LICENSE_KEY n/a raises error ======================================= =========================== ============================================== ====================================================================== -------------------------- diff --git a/docs/testing.rst b/docs/testing.rst new file mode 100644 index 00000000..6ca21388 --- /dev/null +++ b/docs/testing.rst @@ -0,0 +1,56 @@ +.. _testing: + +Testing +======== + +We encourage users to build application tests. As best practice, this should be done immediately after documentation of the application being built, before starting on any coding. + +Pytest +------ + +This project uses the Pytest_, a framework for easily building simple and scalable tests. +After you have set up to `develop locally`_, run the following commands to make sure the testing environment is ready: :: + + $ pytest + +You will get a readout of the `users` app that has already been set up with tests. If you do not want to run the `pytest` on the entire project, you can target a particular app by typing in its location: :: + + $ pytest + +If you set up your project to `develop locally with docker`_, run the following command: :: + + $ docker-compose -f local.yml run django pytest + +Targetting particular apps for testing in ``docker`` follows a similar pattern as previously shown above. + +Coverage +-------- + +You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: :: + + $ docker-compose -f local.yml run django coverage run -m pytest + +Once the tests are complete, in order to see the code coverage, run the following command: :: + + $ docker-compose -f local.yml run django coverage report + +.. note:: + + At the root of the project folder, you will find the `pytest.ini` file. You can use this to customize_ the ``pytest`` to your liking. + + There is also the `.coveragerc`. This is the configuration file for the ``coverage`` tool. You can find out more about `configuring`_ ``coverage``. + +.. seealso:: + + For unit tests, run: :: + + $ python manage.py test + + 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 +.. _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 diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 0c827acf..d0c0ba43 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -9,4 +9,7 @@ This page contains some advice about errors and problems commonly encountered du #. 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`_) + .. _#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 9118f6c9..45435dd0 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -32,7 +32,10 @@ DEBUG_VALUE = "debug" def remove_open_source_files(): - file_names = ["CONTRIBUTORS.txt"] + file_names = [ + "CONTRIBUTORS.txt", + "LICENSE", + ] for file_name in file_names: os.remove(file_name) @@ -61,9 +64,16 @@ def remove_docker_files(): os.remove(file_name) +def remove_utility_files(): + shutil.rmtree("utility") + + def remove_heroku_files(): file_names = ["Procfile", "runtime.txt", "requirements.txt"] for file_name in file_names: + if file_name == "requirements.txt" and "{{ cookiecutter.use_travisci }}".lower() == "y": + # don't remove the file if we are using travisci but not using heroku + continue os.remove(file_name) @@ -111,9 +121,11 @@ def generate_random_string( if using_ascii_letters: symbols += string.ascii_letters if using_punctuation: - symbols += string.punctuation.replace('"', "").replace("'", "").replace( - "\\", "" - ) + all_punctuation = set(string.punctuation) + # These symbols can cause issues in environment variables + unsuitable = {"'", '"', "\\", "$"} + suitable = all_punctuation.difference(unsuitable) + symbols += "".join(suitable) return "".join([random.choice(symbols) for _ in range(length)]) @@ -275,7 +287,9 @@ def main(): if "{{ cookiecutter.use_pycharm }}".lower() == "n": remove_pycharm_files() - if "{{ cookiecutter.use_docker }}".lower() == "n": + if "{{ cookiecutter.use_docker }}".lower() == "y": + remove_utility_files() + else: remove_docker_files() if "{{ cookiecutter.use_heroku }}".lower() == "n": diff --git a/requirements.txt b/requirements.txt index 77770fa9..37a96913 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,11 @@ binaryornot==0.4.4 # Code quality # ------------------------------------------------------------------------------ -flake8==3.5.0 +flake8==3.7.6 # Testing # ------------------------------------------------------------------------------ -tox==3.0.0 -pytest==3.6.2 +tox==3.6.1 +pytest==4.3.0 pytest-cookies==0.3.0 +pyyaml==3.13 diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index cf173576..b2c235a8 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -1,6 +1,7 @@ import os import re import sh +import yaml import pytest from binaryornot.check import is_binary @@ -85,3 +86,19 @@ def test_flake8_compliance(cookies): sh.flake8(str(result.project)) except sh.ErrorReturnCode as e: pytest.fail(e) + + +def test_travis_invokes_pytest(cookies, context): + context.update({"use_travisci": "y"}) + 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}/.travis.yml', 'r') as travis_yml: + try: + assert yaml.load(travis_yml)['script'] == ['pytest'] + except yaml.YAMLError as e: + pytest.fail(e) diff --git a/tests/test_docker.sh b/tests/test_docker.sh index bebac148..eddfe98c 100755 --- a/tests/test_docker.sh +++ b/tests/test_docker.sh @@ -14,8 +14,11 @@ cd .cache/docker cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y cd my_awesome_project +# run the project's type checks +docker-compose -f local.yml run django mypy my_awesome_project + # run the project's tests -docker-compose -f local.yml run django python manage.py test +docker-compose -f local.yml run django pytest # return non-zero status code if there are migrations that have not been created docker-compose -f local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; } diff --git a/{{cookiecutter.project_slug}}/.envs/.local/.django b/{{cookiecutter.project_slug}}/.envs/.local/.django index 6f7a2b11..919f3118 100644 --- a/{{cookiecutter.project_slug}}/.envs/.local/.django +++ b/{{cookiecutter.project_slug}}/.envs/.local/.django @@ -1,6 +1,7 @@ # General # ------------------------------------------------------------------------------ USE_DOCKER=yes +IPYTHONDIR=/app/.ipython {%- if cookiecutter.use_celery == 'y' %} # Redis diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore index 679fb902..1874e9d9 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -333,6 +333,7 @@ tags [Ss]cripts pyvenv.cfg pip-selfcheck.json +.env {% endif %} ### Project template @@ -342,3 +343,7 @@ MailHog {{ cookiecutter.project_slug }}/media/ .pytest_cache/ + +{% if cookiecutter.use_docker == 'y' %} +.ipython/ +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/.travis.yml b/{{cookiecutter.project_slug}}/.travis.yml index 5ca54d00..3072f75f 100644 --- a/{{cookiecutter.project_slug}}/.travis.yml +++ b/{{cookiecutter.project_slug}}/.travis.yml @@ -9,3 +9,7 @@ before_install: language: python python: - "3.6" +install: + - pip install -r requirements/local.txt +script: + - "pytest" diff --git a/{{cookiecutter.project_slug}}/README.rst b/{{cookiecutter.project_slug}}/README.rst index 3386d985..49df7019 100644 --- a/{{cookiecutter.project_slug}}/README.rst +++ b/{{cookiecutter.project_slug}}/README.rst @@ -32,12 +32,21 @@ Setting Up Your Users For convenience, you can keep your normal user logged in on Chrome and your superuser logged in on Firefox (or similar), so that you can see how the site behaves for both kinds of users. +Type checks +^^^^^^^^^^^ + +Running type checks with mypy: + +:: + + $ mypy {{cookiecutter.project_slug}} + Test coverage ^^^^^^^^^^^^^ To run the tests, check your test coverage, and generate an HTML coverage report:: - $ coverage run manage.py test + $ coverage run -m pytest $ coverage html $ open htmlcov/index.html @@ -46,7 +55,7 @@ Running tests with py.test :: - $ py.test + $ pytest Live reloading and Sass CSS compilation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -86,16 +95,25 @@ With MailHog running, to view messages that are sent by your application, open y {% else %} In development, it is often nice to be able to see emails that are being sent from your application. If you choose to use `MailHog`_ when generating the project a local SMTP server with a web interface will be available. -To start the service, make sure you have nodejs installed, and then type the following:: +#. `Download the latest MailHog release`_ for your OS. - $ npm install - $ grunt serve +#. Rename the build to ``MailHog``. -(After the first run you only need to type ``grunt serve``) This will start an email server that listens on ``127.0.0.1:1025`` in addition to starting your Django project and a watch task for live reload. +#. Copy the file to the project root. -To view messages that are sent by your application, open your browser and go to ``http://127.0.0.1:8025`` +#. Make it executable: :: -The email server will exit when you exit the Grunt task on the CLI with Ctrl+C. + $ chmod +x MailHog + +#. Spin up another terminal window and start it there: :: + + ./MailHog + +#. Check out ``_ to see how it goes. + +Now you have your own mail server running locally, ready to receive whatever you send it. + +.. _`Download the latest MailHog release`: https://github.com/mailhog/MailHog/releases {% endif %} .. _mailhog: https://github.com/mailhog/MailHog {% endif %} @@ -138,7 +156,7 @@ Custom Bootstrap Compilation ^^^^^^ The generated CSS is set up with automatic Bootstrap recompilation with variables of your choice. -Bootstrap v4 is installed using npm and customised by tweaking your variables in ``static/sass/custom_bootstrap_vars``. +Bootstrap v4.1.1 is installed using npm and customised by tweaking your variables in ``static/sass/custom_bootstrap_vars``. You can find a list of available variables `in the bootstrap source`_, or get explanations on them in the `Bootstrap docs`_. @@ -147,6 +165,6 @@ Bootstrap's javascript as well as its dependencies is concatenated into a single {% endif %} .. _in the bootstrap source: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss -.. _Bootstrap docs: https://getbootstrap.com/docs/4.0/getting-started/theming/ +.. _Bootstrap docs: https://getbootstrap.com/docs/4.1/getting-started/theming/ {% endif %} diff --git a/{{cookiecutter.project_slug}}/compose/production/caddy/Caddyfile b/{{cookiecutter.project_slug}}/compose/production/caddy/Caddyfile index d36632e4..323e4392 100644 --- a/{{cookiecutter.project_slug}}/compose/production/caddy/Caddyfile +++ b/{{cookiecutter.project_slug}}/compose/production/caddy/Caddyfile @@ -1,5 +1,5 @@ www.{% raw %}{$DOMAIN_NAME}{% endraw %} { - redir https://{{cookiecutter.domain_name}} + redir https://{% raw %}{$DOMAIN_NAME}{% endraw %} } {% raw %}{$DOMAIN_NAME}{% endraw %} { @@ -7,6 +7,7 @@ www.{% raw %}{$DOMAIN_NAME}{% endraw %} { header_upstream Host {host} header_upstream X-Real-IP {remote} header_upstream X-Forwarded-Proto {scheme} + header_upstream X-CSRFToken {~csrftoken} } log stdout errors stdout diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index e4ab2884..950b9ed7 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -209,6 +209,17 @@ FIXTURE_DIRS = ( str(APPS_DIR.path('fixtures')), ) +# SECURITY +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly +SESSION_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly +CSRF_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter +SECURE_BROWSER_XSS_FILTER = True +# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options +X_FRAME_OPTIONS = 'DENY' + # EMAIL # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend diff --git a/{{cookiecutter.project_slug}}/config/settings/local.py b/{{cookiecutter.project_slug}}/config/settings/local.py index 741f324a..6667a265 100644 --- a/{{cookiecutter.project_slug}}/config/settings/local.py +++ b/{{cookiecutter.project_slug}}/config/settings/local.py @@ -76,8 +76,10 @@ INSTALLED_APPS += ['django_extensions'] # noqa F405 # Celery # ------------------------------------------------------------------------------ +{% if cookiecutter.use_docker == 'n' -%} # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-always-eager CELERY_TASK_ALWAYS_EAGER = True +{%- endif %} # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-eager-propagates CELERY_TASK_EAGER_PROPAGATES = True diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index ecd517dd..e77d4304 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -41,12 +41,8 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_SSL_REDIRECT = env.bool('DJANGO_SECURE_SSL_REDIRECT', default=True) # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure SESSION_COOKIE_SECURE = True -# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly -SESSION_COOKIE_HTTPONLY = True # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure CSRF_COOKIE_SECURE = True -# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly -CSRF_COOKIE_HTTPONLY = True # https://docs.djangoproject.com/en/dev/topics/security/#ssl-https # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds # TODO: set this to 60 seconds first and then to 518400 once you prove the former works @@ -57,10 +53,6 @@ SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool('DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS SECURE_HSTS_PRELOAD = env.bool('DJANGO_SECURE_HSTS_PRELOAD', default=True) # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff SECURE_CONTENT_TYPE_NOSNIFF = env.bool('DJANGO_SECURE_CONTENT_TYPE_NOSNIFF', default=True) -# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter -SECURE_BROWSER_XSS_FILTER = True -# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options -X_FRAME_OPTIONS = 'DENY' # STORAGES # ------------------------------------------------------------------------------ @@ -73,8 +65,6 @@ AWS_SECRET_ACCESS_KEY = env('DJANGO_AWS_SECRET_ACCESS_KEY') # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings AWS_STORAGE_BUCKET_NAME = env('DJANGO_AWS_STORAGE_BUCKET_NAME') # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings -AWS_AUTO_CREATE_BUCKET = True -# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings AWS_QUERYSTRING_AUTH = False # DO NOT change these unless you know what you're doing. _AWS_EXPIRY = 60 * 60 * 24 * 7 @@ -88,23 +78,33 @@ AWS_S3_OBJECT_PARAMETERS = { {% if cookiecutter.use_whitenoise == 'y' -%} STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' {%- else %} -STATICFILES_STORAGE = 'config.settings.production.StaticRootS3BotoStorage' -STATIC_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/static/' +STATICFILES_STORAGE = 'config.settings.production.StaticRootS3Boto3Storage' +STATIC_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/' {%- endif %} # MEDIA # ------------------------------------------------------------------------------ {% if cookiecutter.use_whitenoise == 'y' -%} DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' -MEDIA_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/' +MEDIA_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/' {%- else %} # region http://stackoverflow.com/questions/10390244/ +# Full-fledge class: https://stackoverflow.com/a/18046120/104731 from storages.backends.s3boto3 import S3Boto3Storage # noqa E402 -StaticRootS3BotoStorage = lambda: S3Boto3Storage(location='static') # noqa -MediaRootS3BotoStorage = lambda: S3Boto3Storage(location='media', file_overwrite=False) # noqa + + +class StaticRootS3Boto3Storage(S3Boto3Storage): + location = 'static' + + +class MediaRootS3Boto3Storage(S3Boto3Storage): + location = 'media' + file_overwrite = False + + # endregion -DEFAULT_FILE_STORAGE = 'config.settings.production.MediaRootS3BotoStorage' -MEDIA_URL = f'https://s3.amazonaws.com/{AWS_STORAGE_BUCKET_NAME}/media/' +DEFAULT_FILE_STORAGE = 'config.settings.production.MediaRootS3Boto3Storage' +MEDIA_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/' {%- endif %} # TEMPLATES @@ -156,7 +156,7 @@ INSTALLED_APPS += ['gunicorn'] # noqa F405 # WhiteNoise # ------------------------------------------------------------------------------ # http://whitenoise.evans.io/en/latest/django.html#enable-whitenoise -MIDDLEWARE = ['whitenoise.middleware.WhiteNoiseMiddleware'] + MIDDLEWARE # noqa F405 +MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware') # noqa F405 {% endif %} {%- if cookiecutter.use_compressor == 'y' -%} diff --git a/{{cookiecutter.project_slug}}/gulpfile.js b/{{cookiecutter.project_slug}}/gulpfile.js index bb18a535..d92678ed 100644 --- a/{{cookiecutter.project_slug}}/gulpfile.js +++ b/{{cookiecutter.project_slug}}/gulpfile.js @@ -1,63 +1,70 @@ +//////////////////////////////// +// Setup +//////////////////////////////// -//////////////////////////////// - //Setup// -//////////////////////////////// +// Gulp and package +const { src, dest, parallel, series, watch } = require('gulp') +const pjson = require('./package.json') // Plugins -var gulp = require('gulp'), - pjson = require('./package.json'), - gutil = require('gulp-util'), - sass = require('gulp-sass'), - autoprefixer = require('gulp-autoprefixer'), - cssnano = require('gulp-cssnano'), - {% if cookiecutter.custom_bootstrap_compilation == 'y' %} - concat = require('gulp-concat'), - {% endif %} - rename = require('gulp-rename'), - del = require('del'), - plumber = require('gulp-plumber'), - pixrem = require('gulp-pixrem'), - uglify = require('gulp-uglify'), - imagemin = require('gulp-imagemin'), - spawn = require('child_process').spawn, - runSequence = require('run-sequence'), - browserSync = require('browser-sync').create(), - reload = browserSync.reload; - +const autoprefixer = require('autoprefixer') +const browserSync = require('browser-sync').create() +{% if cookiecutter.custom_bootstrap_compilation == 'y' %} +const concat = require('gulp-concat') +{% endif %} +const cssnano = require ('cssnano') +const imagemin = require('gulp-imagemin') +const pixrem = require('pixrem') +const plumber = require('gulp-plumber') +const postcss = require('gulp-postcss') +const reload = browserSync.reload +const rename = require('gulp-rename') +const sass = require('gulp-sass') +const spawn = require('child_process').spawn +const uglify = require('gulp-uglify-es').default // Relative paths function -var pathsConfig = function (appName) { - this.app = "./" + (appName || pjson.name); - var vendorsRoot = 'node_modules/'; +function pathsConfig(appName) { + this.app = `./${pjson.name}` + const vendorsRoot = 'node_modules' return { {% if cookiecutter.custom_bootstrap_compilation == 'y' %} - bootstrapSass: vendorsRoot + '/bootstrap/scss', + bootstrapSass: `${vendorsRoot}/bootstrap/scss`, vendorsJs: [ - vendorsRoot + 'jquery/dist/jquery.slim.js', - vendorsRoot + 'popper.js/dist/umd/popper.js', - vendorsRoot + 'bootstrap/dist/js/bootstrap.js' + `${vendorsRoot}/jquery/dist/jquery.slim.js`, + `${vendorsRoot}/popper.js/dist/umd/popper.js`, + `${vendorsRoot}/bootstrap/dist/js/bootstrap.js`, ], {% endif %} app: this.app, - templates: this.app + '/templates', - css: this.app + '/static/css', - sass: this.app + '/static/sass', - fonts: this.app + '/static/fonts', - images: this.app + '/static/images', - js: this.app + '/static/js' + templates: `${this.app}/templates`, + css: `${this.app}/static/css`, + sass: `${this.app}/static/sass`, + fonts: `${this.app}/static/fonts`, + images: `${this.app}/static/images`, + js: `${this.app}/static/js`, } -}; +} -var paths = pathsConfig(); +var paths = pathsConfig() //////////////////////////////// - //Tasks// +// Tasks //////////////////////////////// // Styles autoprefixing and minification -gulp.task('styles', function() { - return gulp.src(paths.sass + '/project.scss') +function styles() { + var processCss = [ + autoprefixer(), // adds vendor prefixes + pixrem(), // add fallbacks for rem units + ] + + var minifyCss = [ + cssnano({ preset: 'default' }) // minify result + ] + + return src(`${paths.sass}/project.scss`) .pipe(sass({ includePaths: [ {% if cookiecutter.custom_bootstrap_compilation == 'y' %} @@ -67,72 +74,86 @@ gulp.task('styles', function() { ] }).on('error', sass.logError)) .pipe(plumber()) // Checks for errors - .pipe(autoprefixer({browsers: ['last 2 versions']})) // Adds vendor prefixes - .pipe(pixrem()) // add fallbacks for rem units - .pipe(gulp.dest(paths.css)) + .pipe(postcss(processCss)) + .pipe(dest(paths.css)) .pipe(rename({ suffix: '.min' })) - .pipe(cssnano()) // Minifies the result - .pipe(gulp.dest(paths.css)); -}); + .pipe(postcss(minifyCss)) // Minifies the result + .pipe(dest(paths.css)) +} // Javascript minification -gulp.task('scripts', function() { - return gulp.src(paths.js + '/project.js') +function scripts() { + return src(`${paths.js}/project.js`) .pipe(plumber()) // Checks for errors .pipe(uglify()) // Minifies the js .pipe(rename({ suffix: '.min' })) - .pipe(gulp.dest(paths.js)); -}); - + .pipe(dest(paths.js)) +} {% if cookiecutter.custom_bootstrap_compilation == 'y' %} // Vendor Javascript minification -gulp.task('vendor-scripts', function() { - return gulp.src(paths.vendorsJs) +function vendorScripts() { + return src(paths.vendorsJs) .pipe(concat('vendors.js')) - .pipe(gulp.dest(paths.js)) + .pipe(dest(paths.js)) .pipe(plumber()) // Checks for errors .pipe(uglify()) // Minifies the js .pipe(rename({ suffix: '.min' })) - .pipe(gulp.dest(paths.js)); -}); + .pipe(dest(paths.js)) +} {% endif %} // Image compression -gulp.task('imgCompression', function(){ - return gulp.src(paths.images + '/*') +function imgCompression() { + return src(`${paths.images}/*`) .pipe(imagemin()) // Compresses PNG, JPEG, GIF and SVG images - .pipe(gulp.dest(paths.images)) -}); + .pipe(dest(paths.images)) +} // Run django server -gulp.task('runServer', function(cb) { - var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'}); +function runServer(cb) { + var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'}) cmd.on('close', function(code) { - console.log('runServer exited with code ' + code); - cb(code); - }); -}); + console.log('runServer exited with code ' + code) + cb(code) + }) +} // Browser sync server for live reload -gulp.task('browserSync', function() { +function initBrowserSync() { browserSync.init( - [paths.css + "/*.css", paths.js + "*.js", paths.templates + '*.html'], { - proxy: "localhost:8000" - }); -}); + [ + `${paths.css}/*.css`, + `${paths.js}/*.js`, + `${paths.templates}/*.html` + ], { + proxy: "localhost:8000" + } + ) +} // Watch -gulp.task('watch', function() { +function watchPaths() { + watch(`${paths.sass}/*.scss`, styles) + watch(`${paths.templates}/**/*.html`).on("change", reload) + watch([`${paths.js}/*.js`, `!${paths.js}/*.min.js`], scripts).on("change", reload) +} - gulp.watch(paths.sass + '/*.scss', ['styles']); - gulp.watch(paths.js + '/*.js', ['scripts']).on("change", reload); - gulp.watch(paths.images + '/*', ['imgCompression']); - gulp.watch(paths.templates + '/**/*.html').on("change", reload); +// Generate all assets +const generateAssets = parallel( + styles, + scripts, + {% if cookiecutter.custom_bootstrap_compilation == 'y' %}vendorScripts,{% endif %} + imgCompression +) -}); +// Set up dev environment +const dev = parallel( + runServer, + initBrowserSync, + watchPaths +) -// Default task -gulp.task('default', function() { - runSequence(['styles', 'scripts', {% if cookiecutter.custom_bootstrap_compilation == 'y' %}'vendor-scripts', {% endif %}'imgCompression'], ['runServer', 'browserSync', 'watch']); -}); +exports.default = series(generateAssets, dev) +exports["generate-assets"] = generateAssets +exports["dev"] = dev diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index f32c41a2..a15df941 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -3,38 +3,35 @@ "version": "{{ cookiecutter.version }}", "dependencies": {}, "devDependencies": { - {% if cookiecutter.js_task_runner == 'Gulp' %} - {% if cookiecutter.custom_bootstrap_compilation == 'y' %} - "bootstrap": "^4.0.0", - {% endif %} - "browser-sync": "^2.14.0", - "del": "^2.2.2", - "gulp": "^3.9.1", - "gulp-autoprefixer": "^5.0.0", - {% if cookiecutter.custom_bootstrap_compilation == 'y' %} + {% if cookiecutter.js_task_runner == 'Gulp' -%} + {% if cookiecutter.custom_bootstrap_compilation == 'y' -%} + "bootstrap": "4.1.1", "gulp-concat": "^2.6.1", - {% endif %} - "gulp-cssnano": "^2.1.2", - "gulp-imagemin": "^4.1.0", - "gulp-pixrem": "^1.0.0", - "gulp-plumber": "^1.1.0", + "jquery": "3.3.1", + "popper.js": "1.14.3", + {% endif -%} + "autoprefixer": "^9.4.7", + "browser-sync": "^2.14.0", + "cssnano": "^4.1.10", + "gulp": "^4.0.0", + "gulp-imagemin": "^5.0.3", + "gulp-plumber": "^1.2.1", + "gulp-postcss": "^8.0.0", "gulp-rename": "^1.2.2", - "gulp-sass": "^3.1.0", - "gulp-uglify": "^3.0.0", - "gulp-util": "^3.0.7", - {% if cookiecutter.custom_bootstrap_compilation == 'y' %} - "jquery": "^3.2.1-slim", - "popper.js": "^1.12.3", - {% endif %} - "run-sequence": "^2.1.1" - {% endif %} + "gulp-sass": "^4.0.2", + "gulp-uglify-es": "^1.0.4", + "pixrem": "^5.0.0" + {%- endif %} }, "engines": { - "node": ">=0.8.0" + "node": ">=8" }, + "browserslist": [ + "last 2 versions" + ], "scripts": { - {% if cookiecutter.js_task_runner == 'Gulp' %} + {% if cookiecutter.js_task_runner == 'Gulp' -%} "dev": "gulp" - {% endif %} + {%- endif %} } } diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index 5dd5d99e..e43b70af 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -1,16 +1,16 @@ -pytz==2018.4 # https://github.com/stub42/pytz -python-slugify==1.2.5 # https://github.com/un33k/python-slugify -Pillow==5.1.0 # https://github.com/python-pillow/Pillow +pytz==2018.9 # https://github.com/stub42/pytz +python-slugify==2.0.1 # https://github.com/un33k/python-slugify +Pillow==5.4.1 # https://github.com/python-pillow/Pillow {%- if cookiecutter.use_compressor == "y" %} rcssmin==1.0.6{% if cookiecutter.windows == 'y' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin {%- endif %} -argon2-cffi==18.1.0 # https://github.com/hynek/argon2_cffi +argon2-cffi==19.1.0 # https://github.com/hynek/argon2_cffi {%- if cookiecutter.use_whitenoise == 'y' %} -whitenoise==3.3.1 # https://github.com/evansd/whitenoise +whitenoise==4.1.2 # https://github.com/evansd/whitenoise {%- endif %} -redis>=2.10.5 # https://github.com/antirez/redis +redis>=2.10.6, < 3 # pyup: < 3 # https://github.com/antirez/redis {%- if cookiecutter.use_celery == "y" %} -celery==4.2.0 # pyup: <5.0 # https://github.com/celery/celery +celery==4.2.1 # pyup: < 5.0 # https://github.com/celery/celery {%- if cookiecutter.use_docker == 'y' %} flower==0.9.2 # https://github.com/mher/flower {%- endif %} @@ -18,16 +18,16 @@ flower==0.9.2 # https://github.com/mher/flower # Django # ------------------------------------------------------------------------------ -django==2.0.6 # pyup: < 2.1 # https://www.djangoproject.com/ +django==2.0.13 # pyup: < 2.1 # 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.36.0 # https://github.com/pennersr/django-allauth +django-allauth==0.38.0 # 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 {%- endif %} -django-redis==4.9.0 # https://github.com/niwinz/django-redis +django-redis==4.10.0 # https://github.com/niwinz/django-redis # Django REST Framework -djangorestframework==3.8.2 # https://github.com/encode/django-rest-framework +djangorestframework==3.9.1 # https://github.com/encode/django-rest-framework coreapi==2.3.3 # https://github.com/core-api/python-client diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index 63799d99..a496135b 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -2,28 +2,29 @@ Werkzeug==0.14.1 # https://github.com/pallets/werkzeug ipdb==0.11 # https://github.com/gotcha/ipdb -Sphinx==1.7.5 # https://github.com/sphinx-doc/sphinx +Sphinx==1.8.4 # https://github.com/sphinx-doc/sphinx {%- if cookiecutter.use_docker == 'y' %} psycopg2==2.7.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- else %} -psycopg2-binary==2.7.5 # https://github.com/psycopg/psycopg2 +psycopg2-binary==2.7.7 # https://github.com/psycopg/psycopg2 {%- endif %} # Testing # ------------------------------------------------------------------------------ -pytest==3.6.2 # https://github.com/pytest-dev/pytest -pytest-sugar==0.9.1 # https://github.com/Frozenball/pytest-sugar +mypy==0.670 # https://github.com/python/mypy +pytest==4.2.0 # https://github.com/pytest-dev/pytest +pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar # Code quality # ------------------------------------------------------------------------------ -flake8==3.5.0 # https://github.com/PyCQA/flake8 -coverage==4.5.1 # https://github.com/nedbat/coveragepy +flake8==3.7.5 # https://github.com/PyCQA/flake8 +coverage==4.5.2 # https://github.com/nedbat/coveragepy # Django # ------------------------------------------------------------------------------ factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy -django-debug-toolbar==1.9.1 # https://github.com/jazzband/django-debug-toolbar -django-extensions==2.0.7 # https://github.com/django-extensions/django-extensions -django-coverage-plugin==1.5.0 # https://github.com/nedbat/django_coverage_plugin -pytest-django==3.3.2 # https://github.com/pytest-dev/pytest-django +django-debug-toolbar==1.11 # https://github.com/jazzband/django-debug-toolbar +django-extensions==2.1.5 # https://github.com/django-extensions/django-extensions +django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin +pytest-django==3.4.7 # https://github.com/pytest-dev/pytest-django diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index d3b18ed9..34877e3f 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -2,16 +2,16 @@ -r ./base.txt -gunicorn==19.8.1 # https://github.com/benoitc/gunicorn +gunicorn==19.9.0 # https://github.com/benoitc/gunicorn psycopg2==2.7.4 --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" %} -raven==6.9.0 # https://github.com/getsentry/raven-python +raven==6.10.0 # https://github.com/getsentry/raven-python {%- endif %} # Django # ------------------------------------------------------------------------------ -django-storages[boto3]==1.6.6 # https://github.com/jschneier/django-storages -django-anymail[mailgun]==3.0 # https://github.com/anymail/django-anymail +django-storages[boto3]==1.7.1 # https://github.com/jschneier/django-storages +django-anymail[mailgun]==5.0 # https://github.com/anymail/django-anymail \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/runtime.txt b/{{cookiecutter.project_slug}}/runtime.txt index 486fcce1..1935e977 100644 --- a/{{cookiecutter.project_slug}}/runtime.txt +++ b/{{cookiecutter.project_slug}}/runtime.txt @@ -1 +1 @@ -python-3.6.5 +python-3.6.6 diff --git a/{{cookiecutter.project_slug}}/setup.cfg b/{{cookiecutter.project_slug}}/setup.cfg index 1ec89c78..c2139f1d 100644 --- a/{{cookiecutter.project_slug}}/setup.cfg +++ b/{{cookiecutter.project_slug}}/setup.cfg @@ -5,3 +5,17 @@ exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules [pycodestyle] max-line-length = 120 exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules + +[mypy] +python_version = 3.6 +check_untyped_defs = True +ignore_errors = False +ignore_missing_imports = True +strict_optional = True +warn_unused_ignores = True +warn_redundant_casts = True +warn_unused_configs = True + +[mypy-*.migrations.*] +# Django migrations should not produce any errors: +ignore_errors = True diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js index 91ab9e2d..d26d23b9 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js @@ -1,21 +1 @@ /* Project specific Javascript goes here. */ - -/* -Formatting hack to get around crispy-forms unfortunate hardcoding -in helpers.FormHelper: - - if template_pack == 'bootstrap4': - grid_colum_matcher = re.compile('\w*col-(xs|sm|md|lg|xl)-\d+\w*') - using_grid_layout = (grid_colum_matcher.match(self.label_class) or - grid_colum_matcher.match(self.field_class)) - if using_grid_layout: - items['using_grid_layout'] = True - -Issues with the above approach: - -1. Fragile: Assumes Bootstrap 4's API doesn't change (it does) -2. Unforgiving: Doesn't allow for any variation in template design -3. Really Unforgiving: No way to override this behavior -4. Undocumented: No mention in the documentation, or it's too hard for me to find -*/ -$('.form-group').removeClass('row'); diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py index b3f0a388..570abc12 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py @@ -11,6 +11,11 @@ if not settings.configured: 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): @@ -18,11 +23,6 @@ class CeleryAppConfig(AppConfig): verbose_name = 'Celery Config' def ready(self): - # 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') installed_apps = [app_config.name for app_config in apps.get_app_configs()] app.autodiscover_tasks(lambda: installed_apps, force=True) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html index 2cb70566..4470e955 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -17,8 +17,8 @@ {% block css %} {% endraw %}{% if cookiecutter.custom_bootstrap_compilation == "n" %}{% raw %} - - + + {% endraw %}{% endif %}{% raw %} @@ -36,7 +36,7 @@ -
+