diff --git a/.editorconfig b/.editorconfig index 517633b95..585e2abce 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,7 +12,7 @@ trim_trailing_whitespace = true indent_style = space indent_size = 4 -[*.yml] +[*.{html,css,scss,json,yml}] indent_style = space indent_size = 2 diff --git a/.gitignore b/.gitignore index 967469bc6..65f19ad20 100644 --- a/.gitignore +++ b/.gitignore @@ -24,8 +24,20 @@ sftp-config.json *.pot *.pyc .idea +_build # Project Specific Stuff local_settings.py repo_name my_test_project/* + +# Generated when running py.test for the cookiecutter-django generation tests +.cache/ + +# Generated when running celery beat +celerybeat-schedule.db + +# Unit test / coverage reports +.coverage +.tox +.cache diff --git a/.travis.yml b/.travis.yml index 41486605d..b548b9235 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,33 @@ -language: python -sudo: false +# Config file for automatic testing at travis-ci.org -install: -- pip install -r requirements.txt +sudo: required + +services: + - docker + +language: python + +python: 3.5 + +env: + - TOX_ENV=py27 + - TOX_ENV=py34 + - TOX_ENV=py35 + +before_install: + - sudo apt-get update + - sudo apt-get install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" docker-engine=1.10.1-0~trusty + - sudo rm /usr/local/bin/docker-compose + - curl -L https://github.com/docker/compose/releases/download/1.6.0/docker-compose-`uname -s`-`uname -m` > docker-compose + - chmod +x docker-compose + - sudo mv docker-compose /usr/local/bin script: - - py.test + - tox -e $TOX_ENV + - sh tests/test_docker.sh + +install: + - pip install tox notifications: email: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e3696890..ff82dadf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,482 @@ All enhancements and patches to cookiecutter-django will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +### [2016-03-16] +### Changed +- Set the correct postgres username in dev.yml (@calculuscowboy) + +## [2016-03-14] +### Changed +- Enforce `repo_name` as proper python module (@catherinedevlin) + +## [2016-03-08] +### Changed +- Docker configuration now uses docker-compose format v2 (@aeikenberry) +- Make sure that STATIC_URL != MEDIA_URL (@cdvv7788) +- fix minor typos in project README (@menzenski) +- Updated docker docs (@jayfk) + +### Added +- Added database controls for docker (@jayfk) + + +## [2016-03-05] +### Changed +- Update version of Django, celery, django-test-plus (@luzfcb) +- Update version of Hitch tests dependencies: jupyter_client (@luzfcb) +- Update 'now' date in cookiecutter.json (@luzfcb) +- Update the usage example in README (@luzfcb) + +## [2016-03-01] +### Changed +- Update version of Django, flake8, pyflakes, pytest, factory_boy, ipdb, Werkzeug, gevent (@luzfcb) +- Update version of Hitch tests dependencies: click, hitchserve, hitchsystem, hitchtest, ipython, psutil, python-dateutil(@luzfcb) +- Update Tether (JS) version to 1.2.0 (@luzfcb) + +## [2016-02-24] +### Added +- Beginning support for `py.test` (@pydanny) + +### Changed +- Fixed missing div closing tag for "container" on user_list.html (@Eraldo) + +## [2016-02-18] +### Changed +- The status of the registration (open or closed) is now read from the project environment instead of hardcoded in the common settings file. (@Eraldo) +- Renamed the adapter.py file to adapters.py to match the django naming convention. (@Eraldo) + + + +## [2016-02-15] +### Changed +- In `users` app adapter, fix `is_open_for_signup` missing parameter (@oryx2) +- Fixes and improvements in Hitch tests , see [#485](https://github.com/pydanny/cookiecutter-django/pull/485) (@crdoconnor) + + +## [2016-02-12] +### Changed +- Fixed typo (@yunti) + +## [2016-02-07] +### Changed +- In `users` app, use Django 1.9 `LoginRequiredMixin` instead of django-braces implementation (@yunti) +- Update native OS libraries of Hitch Test, because [unixpackage](https://github.com/unixpackage/unixpackage) now supports multiple versions of same Linux distribution (@crdoconnor) +- Update AngularJS version to 1.5.0 (@luzfcb) +- Update version of wheel, Pillow, django_coverage_plugin (@luzfcb) +- Update version of Hitch tests dependencies: decorator, hitchselenium, ipython, ptyprocess, selenium (@luzfcb) +- Provided options for FOSS license choices, or for private efforts, no written license (@pydanny) + +## [2016-02-01] +### Changed +- Update version of Django and django-floppyforms (@luzfcb) +- Update version of Hitch tests dependencies: hitchpython and selenium (@luzfcb) + +## [2016-01-30] +### Changed +- Update flake8 to 2.5.2 (@luzfcb) + +## [2016-01-29] +### Changed +- Update AngularJS version to 1.4.9 (@luzfcb) +- Update jQuery version to 2.2.0 (@luzfcb) +- Update 'now' date in cookiecutter.json (@luzfcb) +- Update version of boto, celery, django_coverage_plugin, django-storages-redux, flake8, gevent, gunicorn, pep8, pytest, tox, Werkzeug (@luzfcb) +- Update version of Hitch tests dependencies: colorama, decorator, hitchpostgres, hitchpython, hitchredis, hitchselenium, hitchserve, hitchsystem, hitchtest, ipython, patool, pickleshare, psutil, python-build, requests, selenium, tblib, traitlets (@luzfcb) + + +## [2016-01-26] +### Changed +- Fixed NEW_RELIC_APP_NAME environment variable (@jayfk) + +## [2016-1-18] +### Added +- Added .dockerignore file (@bogdal) +- Docker tests for travis (@jayfk) + +### Changed +- Removed the $-sign from allowed chars to generate the secret key (@jayfk) + +## [2016-01-17] +### Added +- Adding a section on third party articles referencing `cookiecutter-django` (@mjheo) + +### Changed +- Add celerybeat db to gitignore (@originell) + +## [2016-01-16] +### Added +- Adding an explanation for having `django.contrib.sites`. (@pydanny) + + +## [2016-01-13] +### Changed +- Update setup.py version to 1.9.1 to match Django version. (@Collederas) +- Require Wheel 0.26.0. Needed to install certain packages on CPython 3.5+ like Pillow and psycopg2 (@audreyr) + +## [2016-01-09] +### Changed +- Upgraded django-extensions to 1.6.1 as it fixes a [JSONField bug](https://github.com/django-extensions/django-extensions/blob/master/CHANGELOG.md#161) (@burhan) +- Upgraded Pillow to version 3.1.0 ([upstream changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#310-2016-01-04)) (@burhan) +- Upgraded django to 1.9.1 to integrate various [bugfixes](https://docs.djangoproject.com/en/1.9/releases/1.9.1/) (@burhan) +- Upgraded django-crispy-forms to 1.6 for [BS4 and django 1.9 compatibility fixes](https://github.com/maraujop/django-crispy-forms/blob/dev/CHANGELOG.md#160-201617) (@burhan) +- Upgraded django-model-utils to 2.4, to enable [support for django 1.9](https://github.com/carljm/django-model-utils/blob/master/CHANGES.rst#24-2015-12-03) (@burhan) + +## [2016-01-08] +### Changed +- Fixed redis url on docker (@jayfk) +- Fixed docker on windows (@burhan) + +## [2016-01-06] +### Added +- You can now enable or disable user registration using the ACCOUNT_ALLOW_REGISTRATION setting. (@ddiazpinto) + +### Changed +- Use Postgres 9.5 on docker (@jayfk) + +## [2016-01-04] +### Added +- Add Tether.js because [is needed](http://v4-alpha.getbootstrap.com/components/tooltips/#overview) for proper positioning of Bootstrap tooltips (@EricZaporzan) + +### Changed +- Minor fixes in the docker documentation (@jayfk) +- Made @burhan a core committer (@pydanny) + +## [2015-12-30] +### Changed +- Fixed a bug where the navbar was not displayed correctly (@jvanbrug) + +## [2015-12-21] +### Changed +- Added sentry logger to celery config (@jayfk) + +## [2015-12-16] +- Update preview 4xx error pages to accept `exception` argument (@theskumar) + +## [2015-12-15] +### Changed +- Fix celery worker app name in Procfile (@stepmr) + +## [2015-12-13] +### Changed +- Bumped Django to 1.9 (@areski) +- Support opbeat logging with celery (@stepmr) +- Update runtime.txt with PY2 support (@stepmr) + +## [2015-12-12] +### Added +- Celery worker to Heroku procfile (@stepmr) + +## [2015-12-11] +### Changed +- Fixed issue #436 - cookiecutter variable name was renamed from `celery_support` to `use_celery` in `tests/engine.py` (@luzfcb @otakucode) +- Updated Heroku runtime.txt for python 3.5.1 (@yunti) + +## [2015-12-06] +### Changed +- Reorganization of contributors (@burhan) + +## [2015-12-01] +### Changed +- Update documentation to include the installation os dependencies before development requirements (@failsafe86) + +## [2015-11-29] +### Changed +- Update version of click and python-build (@luzfcb) + +## [2015-11-25] +### Changed +- Update version of psutil, ipython (@luzfcb) +- Update version of gunicorn (@audreyr) +- Remove debugging tools from non-generated part of cookiecutter-django, since those are personal prefs (@audreyr) +- Update version of Django in setup.py (@luzfcb) + +## [2015-11-24] +### Changed +- Update version of Django, coverage and click (@luzfcb) +- Fixed configuration for Celery in local.py. (@luzfcb @hackebrot) + +## [2015-11-23] +### Changed +- Update AngularJS version to 1.4.8 (@luzfcb) +- Update version of cookiecutter, pytest, tox, whitenoise, django-test-plus, django_coverage_plugin, Werkzeug, hitchserve, tornado, unixpackage (@luzfcb) +- Update 'now' date in cookiecutter.json (@luzfcb) +- `sh` package version pinned to `1.11` (@luzfcb) + +## [2015-11-22] +### Changed +- Move div class unquote outside the django if tag (@jvanbrug) +- Changed gevent to `1.1rc1` for python 3 users (@jondelmil / @jayfk) + +## [2015-11-20] +### Changed +- Using python 3.5 on Heroku/Travis (@bogdal) +- Fixed typo in README (@tedmiston) + +## [2015-11-18] +### Added +- Mailhog as a replacement for Maildump (@keybits) + +### Removed +- Maildump because it didn't support Python 3 (@keybits) + +## [2015-11-17] +### Added +- initial configuration to support opbeat (@burhan) + +### Removed +- Took `*.pyc` out of .gitignore, because it's already covered by `*.py[cod]` (@audreyr) + +## [2015-11-16] +### Changed +- Cleanup of main README (@burhan) + +## [2015-11-15] +### Added +- Added `UserFactory` for users.User tests (@ad-m) + +## [2015-11-12] +### Changed +- Update version of django-allauth (@yunti) +- Added a warning in README.rst: ```repo_name must be a valid Python module``` @cdvv7788 + +### Removed +- remove ```{% load url from future %}``` in templates - deprecated in django 1.9 (@yunti) + +## [2015-11-11] +### Added +- Added django_coverage_plugin to measure Django template coverage (@audreyr) + +## [2015-11-09] +### Changed +- Now using py.test for our test suite!! (@hackebrot) +- Python version in travis.yml is now correct for the selected version of Django (@show0k) + +## [2015-11-08] +### Changed +- bump django-extensions version (@garrypolley) + +## [2015-11-07] +### Added +- newrelic support (@amjith) +- DJANGO_SENTRY_DSN to env.example (@jayfk) + +### Changed +- Made `post_gen_hook.set_secret_key()` only changes one CHANGEME!!! at a time. (@pydanny) +- Fixed an error where celery couldn't load the sentry DSN from settings (@jayfk) +- Renamed ADMIN_URL to DJANGO_ADMIN_URL in env.example (@ChrisPappalardo) + +## [2015-11-06] +### Added +- \*tests\* to `.coveragerc`, because including it is cheating! (@pydanny) +- Binaryornot to cookiecutter-django's own tests because otherwise Python 3 blows up (@audreyr) + +### Changed +- `.travis.yml` configuration to support Python 3.4 and 3.5 (@pydanny) +- `.gitignore` configuration so py.test cache files don't show up in git status. + +## [2015-11-05] +### Changed +- Update version of django-extensions (@luzfcb) +- Fix gevent requirement for Python 3 (@mcho421) + +## [2015-11-04] +### Changed +- Update version of Django, cookiecutter, celery, coverage, django-mailgun, django-redis, factory_boy, flake8, pytest and pytz (@luzfcb) +- Update AngularJS version to 1.4.7 (@luzfcb) +- Update 'now' date in cookiecutter.json (@luzfcb) + +## [2015-10-28] +### Changed +- Update deployment-on-heroku.rst for ADMIN_URL (@yunti) + +## [2015-10-27] +### Added +- Added sudo: true to the travis file (@MathijsHoogland) + +## [2015-10-25] +### Added +- Move current logging config into production.py since it's not useful locally anyway. Used only if not using Sentry. (@audreyr) +- `setup.py` so we can list it on PyPI and therefore displayed on djangopackages.com as compatible with Python 3. (@pydanny) +- Versioning and tagging policy (@pydanny) +- Fixed flake8 issue (@pydanny) + +## [2015-10-24] +### Changed +- Update nav in base template to latest Bootstrap 4 version (@audreyr) +- Replaced ADD with COPY in dockerfiles (@audreyr) +- Simplified development dockerfile (@jayfk) +- Moved the docker postgres volume on the development environment to it's own subfolder (@jayfk) +- Renamed DJANGO_CACHE_URL to REDIS_URL (@jayfk / proposed by @pydanny) + +## [2015-10-22] +### Removed +- Remove unnecessary .gitkeep in static/images/ (@audreyr) + +## [2015-10-21] +### Changed +- Updated requirements (@theskumar) +### Removed +- editorconfig comment that was just a isort settings link (@pydanny) + +## [2015-10-19] +### Changed +- On Windows, don't install psycopg2 locally. Still install it in test/prod which are assumed to be Unix. (@audreyr) + +## [2015-10-15] +### Changed +- Made `post_gen_hook` function to change secret keys in files more generic (@pydanny) +- Set cryptographically randomized value to `DJANGO_SECRET_KEY` in `env.example` (@pydanny) + +## [2015-10-14] +### Added +- Documention of project options (@audreyr) +### Changed +- Added clarification on building for local or production (@MathijsHoogland) +- Whitespace correction in dev.yml (@MathijsHoogland) + +## [2015-10-13] +### Changed +- Requirements update (@theskumar) + +## [2015-10-11] +### Changed +- Fixed raven issue on development (#302) (@jazztpt) + +## [2015-10-05] +### Changed +- Update version of Django, Pillow, hitchselenium, psutil (@luzfcb) + +## [2015-10-04] +### Changed +- Remove stray closing tags and fix navbar margin in in base.html (@hairychris) +- Docker docs to be functional and more understandable (@audreyr) + +## [2015-09-30] +### Changed +- Fixed Sentry logging with celery (@jayfk) +- Added pep8 and pyflakes to requirements (@jayfk) +- Fixed url() arguments in urls.py because String view arguments to url() is deprecated in django 1.9 (@siauPatrick) +- Update version of cookiecutter, coverage, django-environ, django-extensions, hitchpython, hitchselenium, hitchserve, pytest, pytz, whitenoise (@luzfcb) +- Update the usage example in README (@luzfcb) +- Update 'now' date in cookiecutter.json (@luzfcb) + +## [2015-09-29] +### Changed +- Fix RST in Docker docs (@andor-pierdelacabeza) + +## [2015-09-27] +### Added +- Added advice on how to persist changes with boot2docker (@jayfk) + +###Changed +- Removed duplicate from `CONTRIBUTORS.rst` (@jayfk) + +## [2015-09-26] +### Added +- Add .pylintrc and .pep8 (@kaidokert) + +### Changed +- Move pep8 rules to setup.cfg (@audreyr) +- Better pep8 rules for exclusion (@audreyr) +- Document all linters (@audreyr) +- Sass linting and improvements to alerts (@audreyr) + +## [2015-09-25] +### Changed +- django-mailgun requirement to 0.7.2 (@pydanny) +- Remove commented-out flake8 ignore rule. (@audreyr) + +## [2015-09-24] +### Changed +- Add user-uploaded media dir to .gitignore (@audreyr) +- Update .editorconfig to use 2 spaces for html, css, scss, json (@audreyr) +- Have flake8 ignore node_modules dir (@audreyr) + +## [2015-09-23] +### Changed +- Add workaround for django-debug-toolbar conflict with Bootstrap 4 (@audreyr) + +## [2015-09-22] +### Added +- Add Python version option for deployment (@yunti) + +## [2015-09-21] +### Changed +- django-mailgun-redux to django-mailgun, because @pydanny now has commit rights +### Removed +- Excess "loggers" from LOGGING setting (@siauPatrick) + +## [2015-09-18] +### Changed +- Major reorganization of docs (@pydanny) +- Fix expanded navbar on mobile (@jayfk) +- Update various requirements (@audreyr) + +## [2015-09-17] +### Added +- Fix for wsgi.py for Raven in dev (@yunti) + +## [2015-09-15] +### Added +- whitespace to allow proper rendering of RST (@IanLee1521 ) + +## [2015-09-14] +### Added +- Functionality to delete taskapp if celery isn't going to be used (@pydanny) + +### Removed +- Remove unused generated CSS styles (@audreyr) + +### Changed +- Use Bootstrap margin utility class `m-b-lg` and remove our custom `navbar-header` class (@audreyr) +- Update Hitch requirements (@audreyr) + +## [2015-09-13] +### Removed +- Styles that already exist in Bootstrap 4 (or 3) (@audreyr) + +### Changed +- Fix issue #296 - change login.html to use [get_providers](https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/templatetags/socialaccount.py#L84-L93) templatetag because ``allauth.socialaccount`` context processor now is [deprecated](http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0) (@luzfcb) + +## [2015-09-09] +### Added +- post_gen_hook to generate a secret key for use in locals.py. You should define your own for production (@pydanny) + +## [2015-09-09] +### Added +- htmlcov to gitignore (@pydanny) + +## [2015-09-04] +### Added +- Easy deploy Heroku button and app.json file (@bogdal) + +## [2015-09-03] +### Added +- For security reasons, we set explicitly the list of allowed hosts (@bogdal) + +## [2015-08-31] +### Removed +- Dokku in favor of docker-compose and other modern Django tools (@pydanny) + +## [2015-08-30] +### Changed +- Moved from Bootstrap 3 to Bootstrap 4 (@audreyr) +- Slight Reorganization of the README docs (@pydanny) +- Dokku docs are out of the README and in the docs folder (@pydanny) +- Small improvements in ``install_python_dependencies.sh`` and ``install_os_dependencies.sh`` scripts (@luzfcb) +- Update version of django-crispy-forms, django-extensions, django-test-plus, gevent, coverage, hitchpython and hitchtest (@luzfcb) +- Update AngularJS version to 1.4.4 (@luzfcb) +- Update the usage example on README (@luzfcb) + +## [2015-08-28] +### Changed +- Switched to django-mailgun-redux so mail doesn't blow up on Python 3 (@pydanny) + + +## [2015-08-27] +### Changed +- Grunt Updates: use libsass, add postcss (@288) + ## [2015-08-20] ### Changed - requirements files to match current dependency versions (@pydanny) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index a6b732594..d05ea0dbd 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -9,3 +9,48 @@ Getting your pull request merged in #. Keep it small. The smaller the pull request the more likely I'll pull it in. #. Pull requests that fix a current issue get priority for review. #. If you're not already in the `CONTRIBUTORS.rst` file, add yourself! + +Testing +------- + +Installation +~~~~~~~~~~~~ + +Please install `tox`_, which is a generic virtualenv management and test command line tool. + +`tox`_ is available for download from `PyPI`_ via `pip`_:: + + $ pip install tox + +It will automatically create a fresh virtual environment and install our test dependencies, +such as `pytest-cookies`_ and `flake8`_. + +Run the Tests +~~~~~~~~~~~~~ + +Tox uses py.test under the hood, hence it supports the same syntax for selecting tests. + +For further information please consult the `pytest usage docs`_. + +To run all tests using various versions of python in virtualenvs defined in tox.ini, just run tox.:: + + $ tox + +It is possible to tests with some versions of python, to do this the command +is:: + + $ tox -e py27,py34 + +Will run py.test with the python2.7, and python3.4 interpreters, for +example. + +To run a particular test with tox for against your current Python version:: + + $ tox -e py -- -k test_default_configuration + +.. _`pytest usage docs`: https://pytest.org/latest/usage.html#specifying-tests-selecting-tests +.. _`tox`: https://tox.readthedocs.org/en/latest/ +.. _`pip`: https://pypi.python.org/pypi/pip/ +.. _`pytest-cookies`: https://pypi.python.org/pypi/pytest-cookies/ +.. _`flake8`: https://pypi.python.org/pypi/flake8/ +.. _`PyPI`: https://pypi.python.org/pypi diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst new file mode 100644 index 000000000..f9d5fceac --- /dev/null +++ b/CONTRIBUTORS.rst @@ -0,0 +1,164 @@ +Contributors +============ + +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 +Jannis Gebauer `@jayfk`_ +Burhan Khalid `@burhan`_ @burhan +=========================== ============= =========== + +*Audrey is also the creator of Cookiecutter. Audrey and +Daniel are on the Cookiecutter core team.* + +.. _@pydanny: https://github.com/pydanny +.. _@luzfcb: https://github.com/luzfcb +.. _@theskumar: https://github.com/theskumar +.. _@audreyr: https://github.com/audreyr +.. _@jayfk: https://github.com/jayfk + +Other Contributors +------------------- + +Listed in alphabetical order. + +========================== ============================ ============== + Name Github Twitter +========================== ============================ ============== + a7p `@a7p`_ + Aaron Eikenberry `@aeikenberry`_ + Adam Bogdał `@bogdal`_ + Adam Dobrawy `@ad-m`_ + Agam Dua + Alberto Sanchez `@alb3rto`_ + Alex Tsai `@caffodian`_ + Alvaro [Andor] `@andor-pierdelacabeza`_ + Amjith Ramanujam `@amjith`_ + Andrew Mikhnevich `@zcho`_ + Andy Rose + Anna Callahan `@jazztpt`_ + Areski Belaid `@areski`_ + Ashley Camba + Barclay Gauld `@yunti`_ + Ben Lopatin + Benjamin Abel + Bouke Haarsma + Burhan Khalid `@burhan`_ @burhan + Catherine Devlin `@catherinedevlin`_ + Chris Curvey `@ccurvey`_ + Chris Franklin + Chris Franklin `@hairychris`_ + Chris Pappalardo `@ChrisPappalardo`_ + Collederas `@Collederas`_ + Cristian Vargas `@cdvv7788`_ + Cullen Rhodes `@c-rhodes`_ + Daniele Tricoli `@eriol`_ + David Díaz `@ddiazpinto`_ @DavidDiazPinto + Davur Clementsen `@dsclementsen`_ @davur + Eraldo Energy `@eraldo`_ + Eyad Al Sibai `@eyadsibai`_ + Felipe Arruda `@arruda`_ + Garry Cairns `@garry-cairns`_ + Garry Polley `@garrypolley`_ + Harry Percival `@hjwp`_ + Henrique G. G. Pereira `@ikkebr`_ + Ian Lee `@IanLee1521`_ + Jan Van Bruggen `@jvanbrug`_ + Julio Castillo `@juliocc`_ + Kaido Kert `@kaidokert`_ + Kaveh `@ka7eh`_ + Kevin A. Stone + Kevin Ndung'u `@kevgathuku`_ + Lin Xianyi `@iynaix`_ + Luis Nell `@originell`_ + Lukas Klein + Lyla Fischer + Martin Blech + Mathijs Hoogland `@MathijsHoogland`_ + Matt Linares + Matt Menzenski `@menzenski`_ + Matt Warren `@mfwarren`_ + mozillazg `@mozillazg`_ + Pablo `@oubiga`_ + Raphael Pierzina `@hackebrot`_ + Roman Afanaskin `@siauPatrick`_ + Russell Davies + stepmr `@stepmr`_ + Sławek Ehlert `@slafs`_ + Taylor Baldwin + Théo Segonds `@show0k`_ + Tom Atkins `@knitatoms`_ + Tom Offermann + Travis McNeill `@Travistock`_ @tavistock_esq + Vitaly Babiy + Yaroslav Halchenko +========================== ============================ ============== + +.. _@a7p: https://github.com/a7p +.. _@ad-m: https://github.com/ad-m +.. _@aeikenberry: https://github.com/aeikenberry +.. _@alb3rto: https://github.com/alb3rto +.. _@amjith: https://github.com/amjith +.. _@andor-pierdelacabeza: https://github.com/andor-pierdelacabeza +.. _@areski: https://github.com/areski +.. _@arruda: https://github.com/arruda +.. _@bogdal: https://github.com/bogdal +.. _@burhan: https://github.com/burhan +.. _@c-rhodes: https://github.com/c-rhodes +.. _@caffodian: https://github.com/caffodian +.. _@catherinedevlin: https://github.com/catherinedevlin +.. _@ccurvey: https://github.com/ccurvey +.. _@cdvv7788: https://github.com/cdvv7788 +.. _@ChrisPappalardo: https://github.com/ChrisPappalardo +.. _@Collederas: https://github.com/Collederas +.. _@ddiazpinto: https://github.com/ddiazpinto +.. _@eraldo: https://github.com/eraldo +.. _@eriol: https://github.com/eriol +.. _@eyadsibai: https://github.com/eyadsibai +.. _@garry-cairns: https://github.com/garry-cairns +.. _@garrypolley: https://github.com/garrypolley +.. _@hackebrot: https://github.com/hackebrot +.. _@hairychris: https://github.com/hairychris +.. _@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 +.. _@juliocc: https://github.com/juliocc +.. _@jvanbrug: https://github.com/jvanbrug +.. _@ka7eh: https://github.com/ka7eh +.. _@kaidokert: https://github.com/kaidokert +.. _@kevgathuku: https://github.com/kevgathuku +.. _@knitatoms: https://github.com/knitatoms +.. _@MathijsHoogland: https://github.com/MathijsHoogland +.. _@menzenski: https://github.com/menzenski +.. _@mfwarren: https://github.com/mfwarren +.. _@mozillazg: https://github.com/mozillazg +.. _@originell: https://github.com/originell +.. _@oubiga: https://github.com/oubiga +.. _@show0k: https://github.com/show0k +.. _@siauPatrick: https://github.com/siauPatrick +.. _@slafs: https://github.com/slafs +.. _@stepmr: https://github.com/stepmr +.. _@Travistock: https://github.com/Tavistock +.. _@yunti: https://github.com/yunti +.. _@zcho: https://github.com/zcho + +Special Thanks +~~~~~~~~~~~~~~ + +The following haven't provided code directly, but have provided guidance and advice. + +* Jannis Leidel +* Nate Aune +* Barry Morrison diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt deleted file mode 100644 index 3b20a0eab..000000000 --- a/CONTRIBUTORS.txt +++ /dev/null @@ -1,63 +0,0 @@ -Code Contributors -================= - -Daniel Greenfeld* -a7p -Lukas Klein -Ben Lopatin -Bouke Haarsma -Agam Dua -Vitaly Babiy -Jim Munro* -Tom Offermann -Lyla Fischer -Taylor Baldwin -Chris Curvey (@ccurvey) -Fábio C. Barrionuevo da Luz (@luzfcb)* -Saurabh Kumar (gh: theskumar / @_theskumar)* -Ashley Camba -Yaroslav Halchenko -Pablo / @oubiga -Tom Atkins / @knitatoms -Julio Castillo / @juliocc -Kevin A. Stone -mozillazg / @mozillazg -Henrique G. G. Pereira / @ikkebr -Travis McNeill / gh: Tavistock / @tavistock_esq -Matt Linares -Russell Davies -Sławek Ehlert / @slafs -Alberto Sanchez / @alb3rto -Eyad Al Sibai / @eyadsibai -Chris Franklin -Benjamin Abel -Felipe Arruda / @arruda -Matt Warren / @mfwarren -Martin Blech -Andy Rose -Andrew Mikhnevich / @zcho -Kevin Ndung'u / @kevgathuku -Kaveh / @ka7eh -Alex Tsai / @caffodian -Garry Cairns / @garry-cairns -Lin Xianyi / @iynaix -Adam Dobrawy / @ad-m -Daniele Tricoli / @eriol -Harry Percival / @hjwp -Cullen Rhodes / @c-rhodes -Burhan Khalid / @burhan -Audrey Roy Greenfeld / @audreyr (and creator/maintainer of cookiecutter) * -Jannis Gebauer / @got_nil -jayfk / @jayfk -stepmr / @stepmr - -* Possesses commit rights - -Special Thanks -============== - -The following haven't provided code directly, but have provided guidance and advice. - -Jannis Leidel (django-configurations) -Nate Aune -Barry Morrison diff --git a/README.rst b/README.rst index e86a8dc79..8a8996a4d 100644 --- a/README.rst +++ b/README.rst @@ -13,33 +13,44 @@ cookiecutter-django :target: https://gitter.im/pydanny/cookiecutter-django?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge -A cookiecutter_ template for Django. +A Cookiecutter_ template for Django. .. _cookiecutter: https://github.com/audreyr/cookiecutter Features --------- -* For Django 1.8 -* Renders Django projects with 100% test coverage -* Twitter Bootstrap_ 3 +* For Django 1.9 +* Renders Django projects with 100% starting test coverage +* Twitter Bootstrap_ v4.0.0 - alpha_ * End-to-end via Hitch_ * AngularJS_ * 12-Factor_ based settings via django-environ_ * Optimized development and production settings * Registration via django-allauth_ -* Procfile_ for deploying to Heroku -* Works great with Dokku +* Comes with custom user model ready to go. * Grunt build for compass and livereload * Basic e-mail configurations for sending emails via Mailgun_ * Media storage using Amazon S3 -* Serve static files from Amazon S3 or Whitenoise_ (optional) -* Pre configured Celery_ (optional) -* Integration with Maildump_ for local email testing (optional) -* Integration with Sentry_ for error logging (optional) -* Docker support using docker-compose_ for dev (with debug) and prod (optional) -* PyCharm "Run/Debug Configurations" for django, grunt and docker (optional) +* Docker support using docker-compose_ for development and production +* Procfile_ for deploying to Heroku +* Works with Python 2.7.x or 3.5.x! +* Run tests with unittest or py.test! + +Optional Integrations +--------------------- + +*These features can be enabled during initial project setup.* + +* Serve static files from Amazon S3 or Whitenoise_ +* Configuration for Celery_ +* Integration with MailHog_ for local email testing +* Integration with Sentry_ for error logging +* Integration with NewRelic_ for performance monitoring +* Integration with Opbeat_ for performance monitoring + +.. _alpha: http://blog.getbootstrap.com/2015/08/19/bootstrap-4-alpha/ .. _Hitch: https://github.com/hitchtest/hitchtest .. _Bootstrap: https://github.com/twbs/bootstrap .. _AngularJS: https://github.com/angular/angular.js @@ -51,9 +62,11 @@ Features .. _Mailgun: https://mailgun.com/ .. _Whitenoise: https://whitenoise.readthedocs.org/ .. _Celery: http://www.celeryproject.org/ -.. _Maildump: https://github.com/ThiefMaster/maildump +.. _MailHog: https://github.com/mailhog/MailHog .. _Sentry: https://getsentry.com +.. _NewRelic: https://newrelic.com .. _docker-compose: https://www.github.com/docker/compose +.. _Opbeat: https://opbeat.com/ Constraints @@ -83,6 +96,8 @@ You'll be prompted for some questions, answer them, then it will create a Django **Warning**: After this point, change 'Daniel Greenfeld', 'pydanny', etc to your own information. +**Warning**: repo_name must be a valid Python module name or you will have issues on imports. + It prompts you for questions. Answer them:: Cloning into 'cookiecutter-django'... @@ -91,22 +106,33 @@ It prompts you for questions. Answer them:: remote: Total 550 (delta 283), reused 479 (delta 222) Receiving objects: 100% (550/550), 127.66 KiB | 58 KiB/s, done. Resolving deltas: 100% (283/283), done. - project_name (default is "project_name")? Reddit Clone - repo_name (default is "Reddit_Clone")? reddit - author_name (default is "Your Name")? Daniel Greenfeld - email (default is "Your email")? pydanny@gmail.com - description (default is "A short description of the project.")? A reddit clone. - domain_name (default is "example.com")? myreddit.com - version (default is "0.1.0")? 0.0.1 - timezone (default is "UTC")? - now (default is "2015/01/13")? 2015/01/16 - year (default is "2015")? - use_whitenoise (default is "y")? - + project_name [project_name]: Reddit Clone + repo_name [Reddit_Clone]: reddit + author_name [Your Name]: Daniel Greenfeld + email [Your email]: pydanny@gmail.com + description [A short description of the project.]: A reddit clone. + domain_name [example.com]: myreddit.com + version [0.1.0]: 0.0.1 + timezone [UTC]: + now [2016/03/01]: 2016/03/05 + year [2015]: + use_whitenoise [y]: n + use_celery [n]: y + use_mailhog [n]: n + use_sentry [n]: y + use_newrelic [n]: y + use_opbeat [n]: y + windows [n]: n + use_python2 [n]: y + Select open_source_license: + 1 - MIT + 2 - BSD + 3 - Not open source + Choose from 1, 2, 3 [1]: 1 Enter the project and take a look around:: - $ cd redditclone/ + $ cd reddit/ $ ls Create a GitHub repo and push it there:: @@ -119,107 +145,33 @@ Create a GitHub repo and push it there:: Now take a look at your repo. Don't forget to carefully look at the generated README. Awesome, right? -Getting up and running ----------------------- +For development, see the following for local development: -The steps below will get you up and running with a local development environment. We assume you have the following installed: +* `Developing locally`_ +* `Developing locally using docker`_ -* pip -* virtualenv -* PostgreSQL +.. _`Developing locally`: http://cookiecutter-django.readthedocs.org/en/latest/developing-locally.html +.. _`Developing locally using docker`: http://cookiecutter-django.readthedocs.org/en/latest/developing-locally-docker.html -First make sure to create and activate a virtualenv_, then open a terminal at the project root and install the requirements for local development:: +Articles +--------- - $ pip install -r requirements/local.txt +* `Development and Deployment of Cookiecutter-Django via Docker`_ +* `Development and Deployment of Cookiecutter-Django on Fedora`_ -.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ +.. _`Development and Deployment of Cookiecutter-Django via Docker`: https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-via-docker +.. _`Development and Deployment of Cookiecutter-Django on Fedora`: https://realpython.com/blog/python/development-and-deployment-of-cookiecutter-django-on-fedora/ -Then, create a PostgreSQL database and add the database configuration using the ``dj-database-url`` app pattern: ``postgres://db_owner:password@dbserver_ip:port/db_name`` either: +Support This Project +--------------------------- -* in the ``config.settings.common.py`` setting file, -* or in the environment variable ``DATABASE_URL`` +This project is maintained by volunteers. Support their efforts by spreading the word about: - -You can now run the usual Django ``migrate`` and ``runserver`` command:: - - $ python manage.py migrate - - $ python manage.py runserver - - -**Live reloading and Sass CSS compilation** - -If you'd like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with the included Grunt task. - -Make sure that nodejs_ is installed. Then in the project root run:: - - $ npm install - -.. _nodejs: http://nodejs.org/download/ - -Now you just need:: - - $ grunt serve - -The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled. - -To get live reloading to work you'll probably need to install an `appropriate browser extension`_ - -.. _appropriate browser extension: http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions- - -It's time to write the code!!! - -Getting up and running using docker ----------------------------------- - -The steps below will get you up and running with a local development environment. We assume you have the following installed: - -* docker -* docker-compose - -Open a terminal at the project root and run the following for local development:: - - $ docker-compose -f dev.yml up - -You can also set the environment variable ``COMPOSE_FILE`` pointing to ``dev.yml`` like this:: - - $ export COMPOSE_FILE=dev.yml - -And then run:: - - $ docker-compose up - - -If you want to connect to python interpreter inside docker container with remote debbuger, you have to run different -container with debug.yml (dev.yml has to be build first):: - - $ docker-compose -f dev.yml build; docker-compose -f debug.yml up - - -See `docker remote debugging instructions <{{cookiecutter.repo_name}}/docs/docker_remote_debugging.rst>`_ for particular IDE (PyCharm, etc). - -To migrate your app and to create a superuser, run:: - - $ docker-compose run django python manage.py migrate - - $ docker-compose run django python manage.py createsuperuser - - -If you are using `boot2docker` to develop on OS X or Windows, you need to create a `/data` partition inside your boot2docker -vm to make all changes persistent. If you don't do that your `/data` directory will get wiped out on every reboot. - -To create a persistent folder, log into the `boot2docker` vm by running:: - - $ bootdocker ssh - -And then:: - - $ sudo su - $ echo 'ln -sfn /mnt/sda1/data /data' >> /var/lib/boot2docker/bootlocal.sh - -In case you are wondering why you can't use a host volume to keep the files on your mac: As of `boot2docker` 1.7 you'll -run into permission problems with mounted host volumes if the container creates his own user and `chown`s the directories -on the volume. Postgres is doing that, so we need this quick fix to ensure that all development data persists. +.. image:: https://s3.amazonaws.com/tsacademy/images/tsa-logo-250x60-transparent-01.png + :name: Two Scoops Academy + :align: center + :alt: Two Scoops Academy + :target: http://www.twoscoops.academy/ For Readers of Two Scoops of Django 1.8 -------------------------------------------- diff --git a/cookiecutter.json b/cookiecutter.json index 6653b5287..9e75f14b3 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,18 +1,22 @@ { "project_name": "project_name", - "repo_name": "{{ cookiecutter.project_name|replace(' ', '_') }}", + "repo_name": "{{ cookiecutter.project_name|replace(' ', '_')|replace('-', '_') }}", "author_name": "Your Name", "email": "Your email", "description": "A short description of the project.", "domain_name": "example.com", "version": "0.1.0", "timezone": "UTC", - "now": "2015/01/13", + "now": "2016/03/05", "year": "{{ cookiecutter.now[:4] }}", "use_whitenoise": "y", "use_celery": "n", - "use_maildump": "n", + "use_mailhog": "n", "use_sentry": "n", + "use_newrelic": "n", + "use_opbeat": "n", "use_pycharm": "n", - "windows": "n" + "windows": "n", + "use_python2": "n", + "open_source_license": ["MIT", "BSD", "Not open source"] } diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..7dab2b193 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/{{ cookiecutter.repo_name }}.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/{{ cookiecutter.repo_name }}.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/{{ cookiecutter.repo_name }}" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/{{ cookiecutter.repo_name }}" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 000000000..8772c827b --- /dev/null +++ b/docs/__init__.py @@ -0,0 +1 @@ +# Included so that Django's startproject comment runs against the docs directory diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..4551e8ddf --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# +# cookiecutter-django documentation build configuration file. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +from datetime import datetime +import os +import sys + +now = datetime.now() + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'cookiecutter-django' +copyright = u"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 +# built documents. +# +# The short X.Y version. +version = '{}.{}.{}'.format(*now.isocalendar()) +# The full version, including alpha/beta/rc tags. +release = '{}.{}.{}'.format(*now.isocalendar()) + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'cookiecutter-djangodoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', + 'cookiecutter-django.tex', + u'cookiecutter-django Documentation', + u"cookiecutter-django", 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'cookiecutter-django', u'cookiecutter-django documentation', + [u"Daniel Roy Greenfeld"], 1) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'cookiecutter-django', u'cookiecutter-django documentation', + u"Daniel Roy Greenfeld", 'cookiecutter-django', + 'A Cookiecutter template for creating production-ready Django projects quickly.', 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' diff --git a/docs/deployment-on-heroku.rst b/docs/deployment-on-heroku.rst new file mode 100644 index 000000000..82bdbace4 --- /dev/null +++ b/docs/deployment-on-heroku.rst @@ -0,0 +1,38 @@ +Deployment on Heroku +==================== + +.. index:: Heroku + +You can either push the 'deploy' button in your generated README.rst or run these commands to deploy the project to Heroku: + +.. code-block:: bash + + heroku create --buildpack https://github.com/heroku/heroku-buildpack-python + + heroku addons:create heroku-postgresql:hobby-dev + heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL + heroku pg:promote DATABASE_URL + + heroku addons:create heroku-redis:hobby-dev + heroku addons:create mailgun + + heroku config:set DJANGO_ADMIN_URL=`openssl rand -base64 32` + heroku config:set DJANGO_SECRET_KEY=`openssl rand -base64 64` + heroku config:set DJANGO_SETTINGS_MODULE='config.settings.production' + heroku config:set DJANGO_ALLOWED_HOSTS='.herokuapp.com' + + heroku config:set DJANGO_AWS_ACCESS_KEY_ID=YOUR_AWS_ID_HERE + heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY_HERE + heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=YOUR_AWS_S3_BUCKET_NAME_HERE + + heroku config:set DJANGO_MAILGUN_SERVER_NAME=YOUR_MALGUN_SERVER + heroku config:set DJANGO_MAILGUN_API_KEY=YOUR_MAILGUN_API_KEY + + heroku config:set PYTHONHASHSEED=random + heroku config:set DJANGO_ADMIN_URL=\^somelocation/ + + git push heroku master + heroku run python manage.py migrate + heroku run python manage.py check --deploy + heroku run python manage.py createsuperuser + heroku open diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst new file mode 100644 index 000000000..7e64a50ee --- /dev/null +++ b/docs/deployment-with-docker.rst @@ -0,0 +1,112 @@ +Deployment with Docker +================================================= + +.. index:: Docker, deployment + +TODO: Review and revise + +**Warning** + +Docker is evolving extremely fast, but it has still some rough edges here and there. Compose is currently (as of version 1.4) +not considered production ready. That means you won't be able to scale to multiple servers and you won't be able to run +zero downtime deployments out of the box. Consider all this as experimental until you understand all the implications +to run docker (with compose) on production. + +**Run your app with docker-compose** + +Prerequisites: + +* docker (at least 1.10) +* docker-compose (at least 1.6) + +Before you start, check out the `docker-compose.yml` file in the root of this project. This is where each component +of this application gets its configuration from. It consists of a `postgres` service that runs the database, `redis` +for caching, `nginx` as reverse proxy and last but not least the `django` application run by gunicorn. +{% if cookiecutter.use_celery == 'y' -%} +Since this application also runs Celery, there are two more services with a service called `celeryworker` that runs the +celery worker process and `celerybeat` that runs the celery beat process. +{% endif %} + + +All of these services except `redis` rely on environment variables set by you. There is an `env.example` file in the +root directory of this project as a starting point. Add your own variables to the file and rename it to `.env`. This +file won't be tracked by git by default so you'll have to make sure to use some other mechanism to copy your secret if +you are relying solely on git. + + +By default, the application is configured to listen on all interfaces on port 80. If you want to change that, open the +`docker-compose.yml` file and replace `0.0.0.0` with your own ip. If you are using `nginx-proxy`_ to run multiple +application stacks on one host, remove the port setting entirely and add `VIRTUAL_HOST={{cookiecutter.domain_name}}` to your env file. +This pass all incoming requests on `nginx-proxy`_ to the nginx service your application is using. + +.. _nginx-proxy: https://github.com/jwilder/nginx-proxy + +Postgres is saving its database files to `/data/{{cookiecutter.repo_name}}/postgres` by default. Change that if you wan't +something else and make sure to make backups since this is not done automatically. + +To get started, pull your code from source control (don't forget the `.env` file) and change to your projects root +directory. + +You'll need to build the stack first. To do that, run:: + + docker-compose build + +Once this is ready, you can run it with:: + + docker-compose up + + +To run a migration, open up a second terminal and run:: + + docker-compose run django python manage.py migrate + +To create a superuser, run:: + + docker-compose run django python manage.py createsuperuser + + +If you need a shell, run:: + + docker-compose run django python manage.py shell + +To get an output of all running containers. + +To check your logs, run:: + + docker-compose logs + +If you want to scale your application, run:: + + docker-compose scale django=4 + docker-compose scale celeryworker=2 + + +**Don't run the scale command on postgres or celerybeat** + +Once you are ready with your initial setup, you wan't to make sure that your application is run by a process manager to +survive reboots and auto restarts in case of an error. You can use the process manager you are most familiar with. All +it needs to do is to run `docker-compose up` in your projects root directory. + +If you are using `supervisor`, you can use this file as a starting point:: + + [program:{{cookiecutter.repo_name}}] + command=docker-compose up + directory=/path/to/{{cookiecutter.repo_name}} + redirect_stderr=true + autostart=true + autorestart=true + priority=10 + + +Place it in `/etc/supervisor/conf.d/{{cookiecutter.repo_name}}.conf` and run:: + + supervisorctl reread + supervisorctl start {{cookiecutter.repo_name}} + +To get the status, run:: + + supervisorctl status + +If you have errors, you can always check your stack with `docker-compose`. Switch to your projects root directory and run:: + + docker-compose ps diff --git a/docs/developing-locally-docker.rst b/docs/developing-locally-docker.rst new file mode 100644 index 000000000..8cbf72342 --- /dev/null +++ b/docs/developing-locally-docker.rst @@ -0,0 +1,137 @@ +Getting Up and Running with Docker +================================== + +.. index:: 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. + +Prerequisites +------------- + +You'll need at least docker 1.10. + +If you don't already have it installed, follow the instructions for your OS: + + - On Mac OS X/Windows, you'll need `Docker Toolbox`_ + - On Linux, you'll need `docker-engine`_ +.. _`Docker Toolbox`: https://github.com/docker/toolbox/releases +.. _`docker-engine`: https://docs.docker.com/engine/installation/ + +Create the Machine (Optional) +----------------------------- + +On Linux you have native Docker, so you don't need to create a VM with +docker-machine to use it. + +However, on Mac/Windows/other systems without native Docker, you'll want to +start by creating a VM with docker-machine:: + + $ docker-machine create --driver virtualbox dev1 + +**Note:** If you want to have more than one docker development environment, then +name them accordingly. Instead of 'dev1' you might have 'dev2', 'myproject', +'djangopackages', et al. + +Get the IP Address +------------------ + +Once your machine is up and running, run this:: + + $ docker-machine ip dev1 + 123.456.789.012 + +This is also the IP address where the Django project will be served from. + +Build the Stack +--------------- + +This can take a while, especially the first time you run this particular command +on your development system:: + + $ docker-compose -f dev.yml build + +If you want to build the production environment you don't have to pass an argument -f, it will automatically use docker-compose.yml. + +Boot the System +--------------- + +This brings up both Django and PostgreSQL. + +The first time it is run it might take a while to get started, but subsequent +runs will occur quickly. + +Open a terminal at the project root and run the following for local development:: + + $ docker-compose -f dev.yml up + +You can also set the environment variable ``COMPOSE_FILE`` pointing to ``dev.yml`` like this:: + + $ export COMPOSE_FILE=dev.yml + +And then run:: + + $ docker-compose up + +Running management commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As with any shell command that we wish to run in our container, this is done +using the ``docker-compose run`` command. + +To migrate your app and to create a superuser, run:: + + $ docker-compose -f dev.yml run django python manage.py migrate + $ docker-compose -f dev.yml run django python manage.py createsuperuser + +Here we specify the ``django`` container as the location to run our management commands. + +Production Mode +~~~~~~~~~~~~~~~ + +Instead of using `dev.yml`, you would use `docker-compose.yml`. + +Database Backups +~~~~~~~~~~~~~~~~ + +The database has to be running to create/restore a backup. + +First, run the app with `docker-compose -f dev.yml up`. + +To create a backup, run:: + + docker-compose -f dev.yml run postgres backup + + +To list backups, run:: + + docker-compose -f dev.yml run postgres list-backups + + +To restore a backup, run:: + + docker-compose -f dev.yml run postgres restore filename.sql + + + +Other Useful Tips +----------------- + +Make a machine the active unit +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This tells our computer that all future commands are specifically for the dev1 machine. +Using the ``eval`` command we can switch machines as needed. + +:: + + $ eval "$(docker-machine env dev1)" + +Detached Mode +~~~~~~~~~~~~~ + +If you want to run the stack in detached mode (in the background), use the ``-d`` argument: + +:: + + $ docker-compose -f dev.yml up -d diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst new file mode 100644 index 000000000..b45883d1e --- /dev/null +++ b/docs/developing-locally.rst @@ -0,0 +1,80 @@ +Getting Up and Running Locally +============================== + +.. index:: pip, virtualenv, PostgreSQL + +The steps below will get you up and running with a local development environment. We assume you have the following installed: + +* pip +* virtualenv +* PostgreSQL + +First make sure to create and activate a virtualenv_, then open a terminal at the project root and install the os dependencies:: + + $ sudo ./install_os_dependencies.sh install + +Then install the requirements for your local development:: + + $ pip install -r requirements/local.txt + +.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ + +Then, create a PostgreSQL database with the following command, where `[repo_name]` is what value you entered for your project's `repo_name`:: + + $ createdb [repo_name] + +`cookiecutter-django` uses the excellent `django-environ`_ package with its ``DATABASE_URL`` environment variable to simplify database configuration in your Django settings. Now all you have to do is compose a definition for ``DATABASE_URL``: + +.. parsed-literal:: + + $ export DATABASE_URL="postgres://**:**\ @127.0.0.1:\ **/**" + +.. _django-environ: http://django-environ.readthedocs.org + +You can now run the usual Django ``migrate`` and ``runserver`` commands:: + + $ python manage.py migrate + $ python manage.py runserver + +**Setup your email backend** + +django-allauth sends an email to verify users (and superusers) after signup and login (if they are still not verified). To send email you need to `configure your email backend`_ + +.. _configure your email backend: http://docs.djangoproject.com/en/1.9/topics/email/#smtp-backend + +In development you can (optionally) use MailHog_ for email testing. MailHog is built with Go so there are no dependencies. To use MailHog:: + +1. `Download the latest release`_ for your operating system +2. Rename the executable to ``mailhog`` and copy it to the root of your project directory +3. Make sure it is executable (e.g. ``chmod +x mailhog``) + +.. _Mailhog: https://github.com/mailhog/MailHog/ +.. _Download the latest release: https://github.com/mailhog/MailHog/releases + +Alternatively simply output emails to the console via: ``EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'`` + +In production basic email configuration is setup to send emails with Mailgun_ + +.. _Mailgun: https://www.mailgun.com/ + +**Live reloading and Sass CSS compilation** + +If you'd like to take advantage of live reloading and Sass / Compass CSS compilation you can do so with the included Grunt task. + +Make sure that nodejs_ is installed. Then in the project root run:: + + $ npm install + +.. _nodejs: http://nodejs.org/download/ + +Now you just need:: + + $ grunt serve + +The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled. + +To get live reloading to work you'll probably need to install an `appropriate browser extension`_ + +.. _appropriate browser extension: http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions- + +It's time to write the code!!! diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 000000000..88e7fc2bb --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,27 @@ +FAQ +==== + +.. index:: FAQ, 12-Factor App + +Why is there a django.contrib.sites directory in cookiecutter-django? +--------------------------------------------------------------------- + +It is there to add a migration so you don't have to manually change the ``sites.Site`` record from ``example.com`` to whatever your domain is. Instead, your ``{{cookiecutter.domain_name}}`` and {{cookiecutter.project_name}} value is placed by **Cookiecutter** in the domain and name fields respectively. + +See `0002_set_site_domain_and_name.py`_. + +.. _`0002_set_site_domain_and_name.py`: https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.repo_name%7D%7D/%7B%7Bcookiecutter.repo_name%7D%7D/contrib/sites/migrations/0002_set_site_domain_and_name.py + + +Why aren't you using just one configuration file (12-Factor App) +---------------------------------------------------------------------- + +TODO + +Why doesn't this follow the layout from Two Scoops of Django 1.8? +---------------------------------------------------------------------- + +You may notice that some elements of this project do not exactly match what we describe in chapter 3 of `Two Scoops of Django`_. The reason for that is this project, amongst other things, serves as a test bed for trying out new ideas and concepts. Sometimes they work, sometimes they don't, but the end result is that it won't necessarily match precisely what is described in the book I co-authored. + + +.. _`Two Scoops of Django`: http://twoscoopspress.com/products/two-scoops-of-django-1-8 diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 000000000..8cfc45e85 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,35 @@ +.. cookiecutter-django documentation master file. + +Welcome to cookiecutter-django's documentation! +==================================================================== + +A Cookiecutter_ template for Django. + +.. _cookiecutter: https://github.com/audreyr/cookiecutter + +.. note:: This is an in-progress documentation reorganization. Locations of files may change dramatically over the course of the next few days. See https://github.com/pydanny/cookiecutter-django/issues/335 + +Contents: + +.. toctree:: + :maxdepth: 2 + + + project-generation-options + developing-locally + developing-locally-docker + settings + linters + live-reloading-and-sass-compilation + deployment-on-heroku + deployment-with-docker + faq + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + +.. At some point it would be good to have a module index of the high level things +we are doing. Then we can * :ref:`modindex` back in. diff --git a/docs/linters.rst b/docs/linters.rst new file mode 100644 index 000000000..c2f76f352 --- /dev/null +++ b/docs/linters.rst @@ -0,0 +1,43 @@ +Linters +======= + +.. index:: linters + + +flake8 +------- + +To run flake8: + + $ flake8 + +The config for flake8 is located in setup.cfg. It specifies: + +* Set max line length to 120 chars +* Exclude .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules + +pylint +------ + +This is included in flake8's checks, but you can also run it separately to see a more detailed report: + + $ pylint + +The config for pylint is located in .pylintrc. It specifies: + +* Use the pylint_common and pylint_django plugins. If using Celery, also use pylint_celery. +* Set max line length to 120 chars +* Disable linting messages for missing docstring and invalid name +* max-parents=13 + +pep8 +----- + +This is included in flake8's checks, but you can also run it separately to see a more detailed report: + + $ pep8 + +The config for pep8 is located in setup.cfg. It specifies: + +* Set max line length to 120 chars +* Exclude .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules \ No newline at end of file diff --git a/docs/live-reloading-and-sass-compilation.rst b/docs/live-reloading-and-sass-compilation.rst new file mode 100644 index 000000000..5996bc327 --- /dev/null +++ b/docs/live-reloading-and-sass-compilation.rst @@ -0,0 +1,24 @@ +Live reloading and Sass CSS compilation +======================================= + +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. + +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:: + + $ grunt serve + +The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled. + +To get live reloading to work you'll probably need to install an `appropriate browser extension`_ + +.. _appropriate browser extension: http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions- diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..aa1d05a27 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\{{ cookiecutter.repo_name }}.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\{{ cookiecutter.repo_name }}.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst new file mode 100644 index 000000000..d879b8ff5 --- /dev/null +++ b/docs/project-generation-options.rst @@ -0,0 +1,55 @@ +Project Generation Options +========================== + +project_name [project_name]: + Your human-readable project name, including any capitalization or spaces. + +repo_name [project_name]: + The slug of your project, without dashes or spaces. Used to name your repo + and in other places where a Python-importable version of your project name + is needed. + +author_name [Your Name]: + You! This goes into places like the LICENSE file. + +email [Your email]: + Your email address. + +description [A short description of the project.] + Used in the generated README.rst and other places. + +domain_name [example.com] + Whatever domain name you plan to use for your project when it goes live. + +version [0.1.0] + The starting version number for your project. + +timezone [UTC] + Used in the common settings file for the `TIME_ZONE` value. + +use_whitenoise [y] + Whether to use WhiteNoise_ for static file serving. + +use_celery [n] + Whether to use Celery_. This gives you the ability to use distributed task + queues in your project. + +use_mailhog [n] + Whether to use MailHog_. MailHog is a tool that simulates email receiving + for development purposes. It runs a simple SMTP server which catches + any message sent to it. Messages are displayed in a web interface which runs at ``http://localhost:8025/`` You need to download the MailHog executable for your operating system, see the 'Developing Locally' docs for instructions. + +use_sentry [n] + Whether to use Sentry_ to log errors from your project. + +windows [n] + Whether you'll be developing on Windows. + +use_python2 [n] + By default, the Python code generated will be for Python 3.x. But if you + answer `y` here, it will be legacy Python 2.7 code. + +.. _WhiteNoise: https://github.com/evansd/whitenoise +.. _Celery: https://github.com/celery/celery +.. _MailHog: https://github.com/mailhog/MailHog +.. _Sentry: https://github.com/getsentry/sentry diff --git a/docs/settings.rst b/docs/settings.rst new file mode 100644 index 000000000..768ff6182 --- /dev/null +++ b/docs/settings.rst @@ -0,0 +1,55 @@ +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. + +For configuration purposes, the following table maps environment variables to their Django setting: + + +======================================= =========================== ============================================== ====================================================================== +Environment Variable Django Setting Development Default Production Default +======================================= =========================== ============================================== ====================================================================== +DJANGO_ADMIN_URL n/a r'^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 CHANGEME!!! 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 +DJANGO_SECURE_FRAME_DENY SECURE_FRAME_DENY n/a True +DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS HSTS_INCLUDE_SUBDOMAINS n/a True +DJANGO_SESSION_COOKIE_HTTPONLY SESSION_COOKIE_HTTPONLY n/a True +DJANGO_SESSION_COOKIE_SECURE SESSION_COOKIE_SECURE n/a False +DJANGO_DEFAULT_FROM_EMAIL DEFAULT_FROM_EMAIL n/a "your_project_name " +DJANGO_SERVER_EMAIL SERVER_EMAIL n/a "your_project_name " +DJANGO_EMAIL_SUBJECT_PREFIX EMAIL_SUBJECT_PREFIX n/a "[your_project_name] " +DJANGO_ALLOWED_HOSTS ALLOWED_HOSTS ['*'] ['your_domain_name'] +======================================= =========================== ============================================== ====================================================================== + +The following table lists settings and their defaults for third-party applications, which may or may not be part of your project: + +======================================= =========================== ============================================== ====================================================================== +Environment Variable Django Setting Development Default Production Default +======================================= =========================== ============================================== ====================================================================== +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 +DJANGO_SENTRY_DSN SENTRY_DSN n/a raises error +DJANGO_SENTRY_CLIENT SENTRY_CLIENT n/a raven.contrib.django.raven_compat.DjangoClient +DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO +DJANGO_MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error +DJANGO_MAILGUN_SERVER_NAME MAILGUN_SERVER_NAME 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 +DJANGO_OPBEAT_APP_ID OPBEAT['APP_ID'] n/a raises error +DJANGO_OPBEAT_SECRET_TOKEN OPBEAT['SECRET_TOKEN'] n/a raises error +DJANGO_OPBEAT_ORGANIZATION_ID OPBEAT['ORGANIZATION_ID'] n/a raises error +======================================= =========================== ============================================== ====================================================================== + +-------------------------- +Other Environment Settings +-------------------------- + +DJANGO_ACCOUNT_ALLOW_REGISTRATION (=True) + Allow enable or disable user registration through `django-allauth` without disabling other characteristics like authentication and account management. (Django Setting: ACCOUNT_ALLOW_REGISTRATION) diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 9371ea248..5efb3f67f 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -1,11 +1,133 @@ -# -*- coding: utf-8 -*- +""" +Does the following: + +1. Generates and saves random secret key +2. Removes the taskapp if celery isn't going to be used +3. Copy files from /docs/ to {{ cookiecutter.repo_name }}/docs/ + + TODO: this might have to be moved to a pre_gen_hook + +A portion of this code was adopted from Django's standard crypto functions and +utilities, specifically: + https://github.com/django/django/blob/master/django/utils/crypto.py +""" +import hashlib import os +import random import shutil -project_directory = os.path.realpath(os.path.curdir) +from cookiecutter.config import DEFAULT_CONFIG + +# Get the root project directory +PROJECT_DIRECTORY = os.path.realpath(os.path.curdir) + +# Use the system PRNG if possible +try: + random = random.SystemRandom() + using_sysrandom = True +except NotImplementedError: + # import warnings + # warnings.warn('A secure pseudo-random number generator is not available ' + # 'on your system. Falling back to Mersenne Twister.') + using_sysrandom = False + +def get_random_string( + length=50, + allowed_chars='abcdefghijklmnopqrstuvwxyz0123456789!@#%^&*(-_=+)'): + """ + Returns a securely generated random string. + The default length of 12 with the a-z, A-Z, 0-9 character set returns + a 71-bit value. log_2((26+26+10)^12) =~ 71 bits + """ + if not using_sysrandom: + # This is ugly, and a hack, but it makes things better than + # the alternative of predictability. This re-seeds the PRNG + # using a value that is hard for an attacker to predict, every + # time a random string is required. This may change the + # properties of the chosen random sequence slightly, but this + # is better than absolute predictability. + random.seed( + hashlib.sha256( + ("%s%s%s" % ( + random.getstate(), + time.time(), + settings.SECRET_KEY)).encode('utf-8') + ).digest()) + return ''.join(random.choice(allowed_chars) for i in range(length)) + +def set_secret_key(setting_file_location): + # Open locals.py + with open(setting_file_location) as f: + file_ = f.read() + + # Generate a SECRET_KEY that matches the Django standard + SECRET_KEY = get_random_string() + + # Replace "CHANGEME!!!" with SECRET_KEY + file_ = file_.replace('CHANGEME!!!', SECRET_KEY, 1) + + # Write the results to the locals.py module + with open(setting_file_location, 'w') as f: + f.write(file_) + + +def make_secret_key(project_directory): + """Generates and saves random secret key""" + # Determine the local_setting_file_location + local_setting = os.path.join( + project_directory, + 'config/settings/local.py' + ) + + # local.py settings file + set_secret_key(local_setting) + + env_file = os.path.join( + project_directory, + 'env.example' + ) + + # env.example file + set_secret_key(env_file) + + +def remove_task_app(project_directory): + """Removes the taskapp if celery isn't going to be used""" + # Determine the local_setting_file_location + task_app_location = os.path.join( + PROJECT_DIRECTORY, + '{{ cookiecutter.repo_name }}/taskapp' + ) + shutil.rmtree(task_app_location) + +# IN PROGRESS +# def copy_doc_files(project_directory): +# cookiecutters_dir = DEFAULT_CONFIG['cookiecutters_dir'] +# cookiecutter_django_dir = os.path.join( +# cookiecutters_dir, +# 'cookiecutter-django', +# 'docs' +# ) +# target_dir = os.path.join( +# project_directory, +# 'docs' +# ) +# for name in os.listdir(cookiecutter_django_dir): +# if name.endswith('.rst') and not name.startswith('index'): +# src = os.path.join(cookiecutter_django_dir, name) +# dst = os.path.join(target_dir, name) +# shutil.copyfile(src, dst) + +# 1. Generates and saves random secret key +make_secret_key(PROJECT_DIRECTORY) + +# 2. Removes the taskapp if celery isn't going to be used +if '{{ cookiecutter.use_celery }}'.lower() == 'n': + remove_task_app(PROJECT_DIRECTORY) + +# 3. Copy files from /docs/ to {{ cookiecutter.repo_name }}/docs/ +# copy_doc_files(PROJECT_DIRECTORY) if '{{cookiecutter.use_pycharm}}' != 'y': - shutil.rmtree(os.path.join(project_directory, '.idea/')) + shutil.rmtree(os.path.join(PROJECT_DIRECTORY, '.idea/')) -docker_private_key = 'compose/debug/keys_to_docker/id_rsa' -os.chmod(os.path.join(project_directory, docker_private_key), 0600) diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py new file mode 100644 index 000000000..1e8c0a78a --- /dev/null +++ b/hooks/pre_gen_project.py @@ -0,0 +1,4 @@ +repo_name = '{{ cookiecutter.repo_name }}' + +if hasattr(repo_name, 'isidentifier'): + assert repo_name.isidentifier(), 'Repo name should be valid Python identifier!' diff --git a/requirements.txt b/requirements.txt index 34ed238c9..50e6bd23e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,10 @@ -cookiecutter==1.0.0 -flake8==2.4.1 -sh - -# Debugging -# ------------------------------------- -ipdb==0.8.1 -ipython==4.0.0 +cookiecutter==1.3.0 +flake8==2.5.4 +sh==1.11 +binaryornot==0.4.0 # Testing -# ------------------------------------- -pytest==2.7.2 -git+git://github.com/mverteuil/pytest-ipdb.git +pytest==2.9.0 +pep8==1.7.0 +pyflakes==1.1.0 +tox==2.3.1 diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..f6facb469 --- /dev/null +++ b/setup.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +import os +import platform +import sys + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +# 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 = "1.9.4" + +if sys.argv[-1] == 'tag': + os.system("git tag -a %s -m 'version %s'" % (version, version)) + os.system("git push --tags") + sys.exit() + +with open('README.rst') as readme_file: + long_description = readme_file.read() + +setup( + name='cookiecutter-django', + version=version, + description='A Cookiecutter template for creating production-ready Django projects quickly.', + long_description=long_description, + author='Daniel Roy Greenfeld', + author_email='pydanny@gmail.com', + url='https://github.com/pydanny/cookiecutter-django', + packages=[], + license='BSD', + zip_safe=False, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Framework :: Django :: 1.9', + 'Intended Audience :: Developers', + 'Natural Language :: English', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Software Development', + ], + keywords=( + 'cookiecutter, Python, projects, project templates, django, ' + 'skeleton, scaffolding, project directory, setup.py' + ), +) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/base.py b/tests/base.py deleted file mode 100644 index 25e9daf7c..000000000 --- a/tests/base.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -import re -import shutil -import unittest -from os.path import exists, dirname, join - -import sh - -from cookiecutter.main import cookiecutter - - -class DjangoCookieTestCase(unittest.TestCase): - - root_dir = dirname(dirname(__file__)) - ctx = {} - destpath = None - - def check_paths(self, paths): - """ - Method to check all paths have correct substitutions, - used by other tests cases - """ - # Construct the cookiecutter search pattern - pattern = "{{(\s?cookiecutter)[.](.*?)}}" - re_obj = re.compile(pattern) - - # Assert that no match is found in any of the files - for path in paths: - for line in open(path, 'r'): - match = re_obj.search(line) - self.assertIsNone( - match, - "cookiecutter variable not replaced in {}".format(path)) - - def generate_project(self, extra_context=None): - ctx = { - "project_name": "My Test Project", - "repo_name": "my_test_project", - "author_name": "Test Author", - "email": "test@example.com", - "description": "A short description of the project.", - "domain_name": "example.com", - "version": "0.1.0", - "timezone": "UTC", - "now": "2015/01/13", - "year": "2015" - } - if extra_context: - assert isinstance(extra_context, dict) - ctx.update(extra_context) - - self.ctx = ctx - self.destpath = join(self.root_dir, self.ctx['repo_name']) - - cookiecutter(template='./', checkout=None, no_input=True, extra_context=ctx) - - # Build a list containing absolute paths to the generated files - paths = [os.path.join(dirpath, file_path) - for dirpath, subdirs, files in os.walk(self.destpath) - for file_path in files] - return paths - - def clean(self): - if exists(self.destpath): - shutil.rmtree(self.destpath) - sh.cd(self.root_dir) - - def tearDown(self): - self.clean() diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py new file mode 100644 index 000000000..0f982660f --- /dev/null +++ b/tests/test_cookiecutter_generation.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +import os +import re +import sh + +import pytest +from binaryornot.check import is_binary + +PATTERN = "{{(\s?cookiecutter)[.](.*?)}}" +RE_OBJ = re.compile(PATTERN) + + +@pytest.fixture +def context(): + return { + "project_name": "My Test Project", + "repo_name": "my_test_project", + "author_name": "Test Author", + "email": "test@example.com", + "description": "A short description of the project.", + "domain_name": "example.com", + "version": "0.1.0", + "timezone": "UTC", + "now": "2015/01/13", + "year": "2015" + } + + +def build_files_list(root_dir): + """Build a list containing absolute paths to the generated files.""" + return [ + os.path.join(dirpath, file_path) + for dirpath, subdirs, files in os.walk(root_dir) + for file_path in files + ] + + +def check_paths(paths): + """Method to check all paths have correct substitutions, + used by other tests cases + """ + # Assert that no match is found in any of the files + for path in paths: + if is_binary(path): + continue + for line in open(path, 'r'): + match = RE_OBJ.search(line) + msg = "cookiecutter variable not replaced in {}" + assert match is None, msg.format(path) + + +def test_default_configuration(cookies, context): + result = cookies.bake(extra_context=context) + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == context['repo_name'] + assert result.project.isdir() + + paths = build_files_list(str(result.project)) + assert paths + check_paths(paths) + + +@pytest.fixture(params=['use_mailhog', 'use_celery', 'windows']) +def feature_context(request, context): + context.update({request.param: 'y'}) + return context + + +def test_enabled_features(cookies, feature_context): + result = cookies.bake(extra_context=feature_context) + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == feature_context['repo_name'] + assert result.project.isdir() + + paths = build_files_list(str(result.project)) + assert paths + check_paths(paths) + + +def test_flake8_compliance(cookies): + """generated project should pass flake8""" + result = cookies.bake() + + try: + sh.flake8(str(result.project)) + except sh.ErrorReturnCode as e: + pytest.fail(e) diff --git a/tests/test_cookiecutter_substitution.py b/tests/test_cookiecutter_substitution.py deleted file mode 100644 index 5ac07406f..000000000 --- a/tests/test_cookiecutter_substitution.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import absolute_import -import sh - -from .base import DjangoCookieTestCase - - -class TestCookiecutterSubstitution(DjangoCookieTestCase): - """Test that all cookiecutter instances are substituted""" - - def test_default_configuration(self): - # Build a list containing absolute paths to the generated files - paths = self.generate_project() - self.check_paths(paths) - - def test_maildump_enabled(self): - paths = self.generate_project(extra_context={'use_maildump': 'y'}) - self.check_paths(paths) - - def test_celery_enabled(self): - paths = self.generate_project(extra_context={'use_celery': 'y'}) - self.check_paths(paths) - - def test_windows_enabled(self): - paths = self.generate_project(extra_context={'windows': 'y'}) - self.check_paths(paths) - - def test_flake8_compliance(self): - """generated project should pass flake8""" - self.generate_project() - try: - sh.flake8(self.destpath) - except sh.ErrorReturnCode as e: - raise AssertionError(e) diff --git a/tests/test_docker.sh b/tests/test_docker.sh new file mode 100644 index 000000000..30a27dad3 --- /dev/null +++ b/tests/test_docker.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# this is a very simple script that tests the docker configuration for cookiecutter-django +# it is meant to be run from the root directory of the repository, eg: +# sh tests/test_docker.sh + +# install test requirements +pip install -r requirements.txt + +# create a cache directory +mkdir -p .cache/docker +cd .cache/docker + +# create the project using the default settings in cookiecutter.json +cookiecutter ../../ --no-input --overwrite-if-exists +cd project_name + +# run the project's tests +docker-compose -f dev.yml run django python manage.py test diff --git a/tox.ini b/tox.ini new file mode 100644 index 000000000..deb1dd503 --- /dev/null +++ b/tox.ini @@ -0,0 +1,12 @@ +[tox] +skipsdist = true +envlist = py27,py34,py35 + +[testenv] +passenv = LC_ALL, LANG, HOME +deps = + binaryornot + flake8 + pytest-cookies + sh +commands = py.test {posargs:tests} diff --git a/{{cookiecutter.repo_name}}/.coveragerc b/{{cookiecutter.repo_name}}/.coveragerc index 403f3303e..525cd2ff0 100644 --- a/{{cookiecutter.repo_name}}/.coveragerc +++ b/{{cookiecutter.repo_name}}/.coveragerc @@ -1,3 +1,5 @@ [run] include = {{cookiecutter.repo_name}}/* -omit = *migrations* +omit = *migrations*, *tests* +plugins = + django_coverage_plugin diff --git a/{{cookiecutter.repo_name}}/.dockerignore b/{{cookiecutter.repo_name}}/.dockerignore new file mode 100644 index 000000000..e63c0c184 --- /dev/null +++ b/{{cookiecutter.repo_name}}/.dockerignore @@ -0,0 +1,4 @@ +.* +!.coveragerc +!.env +!.pylintrc diff --git a/{{cookiecutter.repo_name}}/.editorconfig b/{{cookiecutter.repo_name}}/.editorconfig index e22324786..78c90f9c7 100644 --- a/{{cookiecutter.repo_name}}/.editorconfig +++ b/{{cookiecutter.repo_name}}/.editorconfig @@ -13,13 +13,12 @@ indent_style = space indent_size = 4 [*.py] -# https://github.com/timothycrosley/isort/wiki/isort-Settings line_length=120 known_first_party={{ cookiecutter.repo_name }} multi_line_output=3 default_section=THIRDPARTY -[*.yml] +[*.{html,css,scss,json,yml}] indent_style = space indent_size = 2 diff --git a/{{cookiecutter.repo_name}}/.gitignore b/{{cookiecutter.repo_name}}/.gitignore index 50ef6fb8d..0ac2f0c84 100644 --- a/{{cookiecutter.repo_name}}/.gitignore +++ b/{{cookiecutter.repo_name}}/.gitignore @@ -21,7 +21,6 @@ sftp-config.json # Basics *.py[cod] -*.pyc __pycache__ # Logs @@ -32,6 +31,7 @@ pip-log.txt .coverage .tox nosetests.xml +htmlcov # Translations *.mo @@ -71,3 +71,12 @@ node_modules/ # virtual environments .env + +# User-uploaded media +{{ cookiecutter.repo_name }}/media/ + +# Hitch directory +tests/.hitch + +# MailHog binary +mailhog diff --git a/{{cookiecutter.repo_name}}/.pylintrc b/{{cookiecutter.repo_name}}/.pylintrc new file mode 100644 index 000000000..ce2204c9d --- /dev/null +++ b/{{cookiecutter.repo_name}}/.pylintrc @@ -0,0 +1,11 @@ +[MASTER] +load-plugins=pylint_common, pylint_django{% if cookiecutter.use_celery == "y" %}, pylint_celery {% endif %} + +[FORMAT] +max-line-length=120 + +[MESSAGES CONTROL] +disable=missing-docstring,invalid-name + +[DESIGN] +max-parents=13 \ No newline at end of file diff --git a/{{cookiecutter.repo_name}}/.travis.yml b/{{cookiecutter.repo_name}}/.travis.yml index f7e40a1e4..85421fb96 100644 --- a/{{cookiecutter.repo_name}}/.travis.yml +++ b/{{cookiecutter.repo_name}}/.travis.yml @@ -1,3 +1,4 @@ +sudo: true before_install: - sudo apt-get update -qq - sudo apt-get install -qq build-essential gettext python-dev zlib1g-dev libpq-dev xvfb @@ -7,7 +8,11 @@ before_install: - sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm language: python python: - - "3.4" +{% if cookiecutter.use_python2 == 'n' -%} + - "3.5" +{% else %} + - "2.7" +{%- endif %} install: - "pip install hitch" - "cd tests" diff --git a/{{cookiecutter.repo_name}}/Dockerfile b/{{cookiecutter.repo_name}}/Dockerfile index 8bf6b0760..f2f662d5d 100644 --- a/{{cookiecutter.repo_name}}/Dockerfile +++ b/{{cookiecutter.repo_name}}/Dockerfile @@ -1,14 +1,26 @@ -FROM cookiecutterdjango/base +{% if cookiecutter.use_python2 == 'n' -%} +FROM python:3.5 +{% else %} +FROM python:2.7 +{%- endif %} +ENV PYTHONUNBUFFERED 1 # Requirements have to be pulled and installed here, otherwise caching won't work -ADD ./requirements /requirements -ADD ./requirements.txt /requirements.txt +COPY ./requirements /requirements -RUN pip install -r /requirements.txt -RUN pip install -r /requirements/local.txt +RUN pip install -r /requirements/production.txt -ADD . /app +RUN groupadd -r django && useradd -r -g django django +COPY . /app +RUN chown -R django /app + +COPY ./compose/django/gunicorn.sh /gunicorn.sh +COPY ./compose/django/entrypoint.sh /entrypoint.sh +RUN sed -i 's/\r//' /entrypoint.sh +RUN sed -i 's/\r//' /gunicorn.sh +RUN chmod +x /entrypoint.sh && chown django /entrypoint.sh +RUN chmod +x /gunicorn.sh && chown django /gunicorn.sh WORKDIR /app -ENTRYPOINT ["/app/compose/django/entrypoint.sh"] +ENTRYPOINT ["/entrypoint.sh"] diff --git a/{{cookiecutter.repo_name}}/Dockerfile-dev b/{{cookiecutter.repo_name}}/Dockerfile-dev new file mode 100644 index 000000000..c3f54a9e3 --- /dev/null +++ b/{{cookiecutter.repo_name}}/Dockerfile-dev @@ -0,0 +1,18 @@ +{% if cookiecutter.use_python2 == 'n' -%} +FROM python:3.5 +{% else %} +FROM python:2.7 +{%- endif %} +ENV PYTHONUNBUFFERED 1 + +# Requirements have to be pulled and installed here, otherwise caching won't work +COPY ./requirements /requirements +RUN pip install -r /requirements/local.txt + +COPY ./compose/django/entrypoint.sh /entrypoint.sh +RUN sed -i 's/\r//' /entrypoint.sh +RUN chmod +x /entrypoint.sh + +WORKDIR /app + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/{{cookiecutter.repo_name}}/Gruntfile.js b/{{cookiecutter.repo_name}}/Gruntfile.js index b88b11725..1eaade285 100644 --- a/{{cookiecutter.repo_name}}/Gruntfile.js +++ b/{{cookiecutter.repo_name}}/Gruntfile.js @@ -22,7 +22,6 @@ module.exports = function (grunt) { images: this.app + '/static/images', js: this.app + '/static/js', manageScript: 'manage.py', - {% if cookiecutter.use_maildump=="y" -%}mailserverpid: 'mailserver.pid',{%- endif %} } }; @@ -79,7 +78,7 @@ module.exports = function (grunt) { }, } }, - + //see https://github.com/nDmitry/grunt-postcss postcss: { options: { @@ -113,16 +112,16 @@ module.exports = function (grunt) { runDjango: { cmd: 'python <%= paths.manageScript %> runserver' }, - {% if cookiecutter.use_maildump == "y" -%}runMailDump: { - cmd: 'maildump -p <%= paths.mailserverpid %>' - }, - stopMailDump: { - cmd: 'maildump -p <%= paths.mailserverpid %> --stop' + {% if cookiecutter.use_mailhog == "y" -%}runMailHog: { + cmd: './mailhog' },{%- endif %} } }); grunt.registerTask('serve', [ + {% if cookiecutter.use_mailhog == "y" -%} + 'bgShell:runMailHog', + {%- endif %} 'bgShell:runDjango', 'watch' ]); @@ -135,12 +134,5 @@ module.exports = function (grunt) { grunt.registerTask('default', [ 'build' ]); - {% if cookiecutter.use_maildump == "y" -%} - grunt.registerTask('start-email-server', [ - 'bgShell:runMailDump' - ]); - grunt.registerTask('stop-email-server', [ - 'bgShell:stopMailDump' - ]);{%- endif %} }; diff --git a/{{cookiecutter.repo_name}}/LICENSE b/{{cookiecutter.repo_name}}/LICENSE index 2564db27b..0380bee51 100644 --- a/{{cookiecutter.repo_name}}/LICENSE +++ b/{{cookiecutter.repo_name}}/LICENSE @@ -1,3 +1,13 @@ +{% if cookiecutter.open_source_license == 'MIT' %} +The MIT License (MIT) +Copyright (c) {{ cookiecutter.year }}, {{ cookiecutter.author_name }} + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +{% elif cookiecutter.license == 'BSD' %} Copyright (c) {{ cookiecutter.year }}, {{ cookiecutter.author_name }} All rights reserved. @@ -25,3 +35,4 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +{% endif %} \ No newline at end of file diff --git a/{{cookiecutter.repo_name}}/Procfile b/{{cookiecutter.repo_name}}/Procfile index c00f42350..612f46625 100644 --- a/{{cookiecutter.repo_name}}/Procfile +++ b/{{cookiecutter.repo_name}}/Procfile @@ -1 +1,4 @@ web: gunicorn config.wsgi:application +{% if cookiecutter.use_celery == "y" -%} +worker: {% if cookiecutter.use_newrelic == "y" %}newrelic-admin run-program {% endif %}celery worker --app={{cookiecutter.repo_name}}.taskapp --loglevel=info +{%- endif %} diff --git a/{{cookiecutter.repo_name}}/README.rst b/{{cookiecutter.repo_name}}/README.rst index 79dfb430a..c57a953a8 100644 --- a/{{cookiecutter.repo_name}}/README.rst +++ b/{{cookiecutter.repo_name}}/README.rst @@ -7,77 +7,14 @@ LICENSE: BSD Settings --------- +------------ -{{cookiecutter.project_name}} 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. +Moved to settings_. -For configuration purposes, the following table maps the '{{cookiecutter.project_name}}' environment variables to their Django setting: +.. _settings: http://cookiecutter-django.readthedocs.org/en/latest/settings.html -======================================= =========================== ============================================== ====================================================================== -Environment Variable Django Setting Development Default Production Default -======================================= =========================== ============================================== ====================================================================== -DJANGO_CACHES CACHES (default) locmem redis -DJANGO_DATABASES DATABASES (default) See code See code -DJANGO_DEBUG DEBUG True False -DJANGO_SECRET_KEY SECRET_KEY CHANGEME!!! 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 -DJANGO_SECURE_FRAME_DENY SECURE_FRAME_DENY n/a True -DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS HSTS_INCLUDE_SUBDOMAINS n/a True -DJANGO_SESSION_COOKIE_HTTPONLY SESSION_COOKIE_HTTPONLY n/a True -DJANGO_SESSION_COOKIE_SECURE SESSION_COOKIE_SECURE n/a False -DJANGO_DEFAULT_FROM_EMAIL DEFAULT_FROM_EMAIL n/a "{{cookiecutter.project_name}} " -DJANGO_SERVER_EMAIL SERVER_EMAIL n/a "{{cookiecutter.project_name}} " -DJANGO_EMAIL_SUBJECT_PREFIX EMAIL_SUBJECT_PREFIX n/a "[{{cookiecutter.project_name}}] " -======================================= =========================== ============================================== ====================================================================== - -The following table lists settings and their defaults for third-party applications: - -======================================= =========================== ============================================== ====================================================================== -Environment Variable Django Setting Development Default Production Default -======================================= =========================== ============================================== ====================================================================== -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 -{% if cookiecutter.use_sentry == "y" -%}DJANGO_SENTRY_DSN SENTRY_DSN n/a raises error -DJANGO_SENTRY_CLIENT SENTRY_CLIENT n/a raven.contrib.django.raven_compat.DjangoClient -DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO{%- endif %} -DJANGO_MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error -DJANGO_MAILGUN_SERVER_NAME MAILGUN_SERVER_NAME n/a raises error -======================================= =========================== ============================================== ====================================================================== - -Getting up and running ----------------------- - -Basics -^^^^^^ - -The steps below will get you up and running with a local development environment. We assume you have the following installed: - -* pip -* virtualenv -* PostgreSQL - -First make sure to create and activate a virtualenv_, then open a terminal at the project root and install the requirements for local development:: - - $ pip install -r requirements/local.txt - -.. _virtualenv: http://docs.python-guide.org/en/latest/dev/virtualenvs/ - -Create a local PostgreSQL database:: - - $ createdb {{ cookiecutter.repo_name }} - -Run ``migrate`` on your new database:: - - $ python manage.py migrate - -You can now run the ``runserver_plus`` command:: - - $ python manage.py runserver_plus - -Open up your browser to http://127.0.0.1:8000/ to see the site running locally. +Basic Commands +-------------- Setting Up Your Users ^^^^^^^^^^^^^^^^^^^^^ @@ -99,34 +36,25 @@ To run the tests, check your test coverage, and generate an HTML coverage report $ coverage html $ open htmlcov/index.html +Running tests with py.test +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + $ py.test + Live reloading and Sass CSS compilation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -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. +Moved to `Live reloading and SASS compilation`_. -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:: - - $ grunt serve - -The base app will now run as it would with the usual ``manage.py runserver`` but with live reloading and Sass compilation enabled. - -To get live reloading to work you'll probably need to install an `appropriate browser extension`_ - -.. _appropriate browser extension: http://feedback.livereload.com/knowledgebase/articles/86242-how-do-i-install-and-use-the-browser-extensions- +.. _`Live reloading and SASS compilation`: http://cookiecutter-django.readthedocs.org/en/latest/live-reloading-and-sass-compilation.html {% if cookiecutter.use_celery == "y" %} + Celery ^^^^^^ + This app comes with Celery. To run a celery worker: @@ -136,31 +64,32 @@ To run a celery worker: cd {{cookiecutter.repo_name}} celery -A {{cookiecutter.repo_name}}.taskapp worker -l info -Please note: For Celerys 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. +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. + {% endif %} -{% if cookiecutter.use_maildump == "y" %} + +{% if cookiecutter.use_mailhog == "y" %} Email Server ^^^^^^^^^^^^ -In development, it is often nice to be able to see emails that are being sent from your application. For this purpose, -a Grunt task exists to start an instance of `maildump`_ which is a local SMTP server with an online interface. +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. -.. _maildump: https://github.com/ThiefMaster/maildump +.. _mailhog: https://github.com/mailhog/MailHog -Make sure you have nodejs installed, and then type the following:: +To start the service, make sure you have nodejs installed, and then type the following:: - $ grunt start-email-server + $ npm install + $ grunt serve -This will start an email server. The project is setup to deliver to the email server by default. To view messages -that are sent by your application, open your browser to http://127.0.0.1:1080 +(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. -To stop the email server:: +To view messages that are sent by your application, open your browser and go to ``http://127.0.0.1:8025`` - $ grunt stop-email-server +The email server will exit when you exit the Grunt task on the CLI with Ctrl+C. -The email server listens on 127.0.0.1:1025 {% endif %} + {% if cookiecutter.use_sentry == "y" %} Sentry @@ -170,6 +99,7 @@ Sentry is an error logging aggregator service. You can sign up for a free accoun The system is setup with reasonable defaults, including 404 logging and integration with the WSGI application. You must set the DSN url in production. + {% endif %} It's time to write the code!!! @@ -202,195 +132,21 @@ The testing framework runs Django, Celery (if enabled), Postgres, HitchSMTP (a m Deployment ---------- -It is possible to deploy to Heroku, to your own server by using Dokku, an open source Heroku clone or using docker-compose. +We provide tools and instructions for deploying using Docker and Heroku. Heroku ^^^^^^ -Run these commands to deploy the project to Heroku: +.. image:: https://www.herokucdn.com/deploy/button.png + :target: https://heroku.com/deploy -.. code-block:: bash +See detailed `cookiecutter-django Heroku documentation`_. - heroku create --buildpack https://github.com/heroku/heroku-buildpack-python - - heroku addons:create heroku-postgresql:hobby-dev - heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL - heroku pg:promote DATABASE_URL - - heroku addons:create heroku-redis:hobby-dev - heroku addons:create mailgun - - heroku config:set DJANGO_SECRET_KEY=`openssl rand -base64 32` - heroku config:set DJANGO_SETTINGS_MODULE='config.settings.production' - - heroku config:set DJANGO_AWS_ACCESS_KEY_ID=YOUR_AWS_ID_HERE - heroku config:set DJANGO_AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY_HERE - heroku config:set DJANGO_AWS_STORAGE_BUCKET_NAME=YOUR_AWS_S3_BUCKET_NAME_HERE - - heroku config:set DJANGO_MAILGUN_SERVER_NAME=YOUR_MALGUN_SERVER - heroku config:set DJANGO_MAILGUN_API_KEY=YOUR_MAILGUN_API_KEY - - heroku config:set PYTHONHASHSEED=random - - git push heroku master - heroku run python manage.py migrate - heroku run python manage.py check --deploy - heroku run python manage.py createsuperuser - heroku open - -Dokku -^^^^^ - -You need to make sure you have a server running Dokku with at least 1GB of RAM. Backing services are -added just like in Heroku however you must ensure you have the relevant Dokku plugins installed. - -.. code-block:: bash - - cd /var/lib/dokku/plugins - git clone https://github.com/rlaneve/dokku-link.git link - git clone https://github.com/luxifer/dokku-redis-plugin redis - git clone https://github.com/jezdez/dokku-postgres-plugin postgres - dokku plugins-install - -You can specify the buildpack you wish to use by creating a file name .env containing the following. - -.. code-block:: bash - - export BUILDPACK_URL= - -You can then deploy by running the following commands. - -.. code-block:: bash - - git remote add dokku dokku@yourservername.com:{{cookiecutter.repo_name}} - git push dokku master - ssh -t dokku@yourservername.com dokku redis:create {{cookiecutter.repo_name}}-redis - ssh -t dokku@yourservername.com dokku redis:link {{cookiecutter.repo_name}}-redis {{cookiecutter.repo_name}} - ssh -t dokku@yourservername.com dokku postgres:create {{cookiecutter.repo_name}}-postgres - ssh -t dokku@yourservername.com dokku postgres:link {{cookiecutter.repo_name}}-postgres {{cookiecutter.repo_name}} - ssh -t dokku@yourservername.com dokku config:set {{cookiecutter.repo_name}} DJANGO_SECRET_KEY=RANDOM_SECRET_KEY_HERE - ssh -t dokku@yourservername.com dokku config:set {{cookiecutter.repo_name}} DJANGO_SETTINGS_MODULE='config.settings.production' - ssh -t dokku@yourservername.com dokku config:set {{cookiecutter.repo_name}} DJANGO_AWS_ACCESS_KEY_ID=YOUR_AWS_ID_HERE - ssh -t dokku@yourservername.com dokku config:set {{cookiecutter.repo_name}} DJANGO_AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY_HERE - ssh -t dokku@yourservername.com dokku config:set {{cookiecutter.repo_name}} DJANGO_AWS_STORAGE_BUCKET_NAME=YOUR_AWS_S3_BUCKET_NAME_HERE - ssh -t dokku@yourservername.com dokku config:set {{cookiecutter.repo_name}} DJANGO_MAILGUN_API_KEY=YOUR_MAILGUN_API_KEY - ssh -t dokku@yourservername.com dokku config:set {{cookiecutter.repo_name}} DJANGO_MAILGUN_SERVER_NAME=YOUR_MAILGUN_SERVER - ssh -t dokku@yourservername.com dokku run {{cookiecutter.repo_name}} python manage.py migrate - ssh -t dokku@yourservername.com dokku run {{cookiecutter.repo_name}} python manage.py createsuperuser - -When deploying via Dokku make sure you backup your database in some fashion as it is NOT done automatically. +.. _`cookiecutter-django Heroku documentation`: http://cookiecutter-django.readthedocs.org/en/latest/deployment-on-heroku.html Docker ^^^^^^ -**Warning** +See detailed `cookiecutter-django Docker documentation`_. -Docker is evolving extremely fast, but it has still some rough edges here and there. Compose is currently (as of version 1.4) -not considered production ready. That means you won't be able to scale to multiple servers and you won't be able to run -zero downtime deployments out of the box. Consider all this as experimental until you understand all the implications -to run docker (with compose) on production. - -**Run your app with docker-compose** - -Prerequisites: - -* docker (tested with 1.8) -* docker-compose (tested with 0.4) - -Before you start, check out the `docker-compose.yml` file in the root of this project. This is where each component -of this application gets its configuration from. It consists of a `postgres` service that runs the database, `redis` -for caching, `nginx` as reverse proxy and last but not least the `django` application run by gunicorn. -{% if cookiecutter.use_celery == 'y' %} - - -Since this application also runs Celery, there are two more services with a service called `celeryworker` that runs the -celery worker process and `celerybeat` that runs the celery beat process. -{% endif %} - - -All of these services except `redis` rely on environment variables set by you. There is an `env.example` file in the -root directory of this project as a starting point. Add your own variables to the file and rename it to `.env`. This -file won't be tracked by git by default so you'll have to make sure to use some other mechanism to copy your secret if -you are relying solely on git. - - -By default, the application is configured to listen on all interfaces on port 80. If you want to change that, open the -`docker-compose.yml` file and replace `0.0.0.0` with your own ip. If you are using `nginx-proxy`_ to run multiple -application stacks on one host, remove the port setting entirely and add `VIRTUAL_HOST={{cookiecutter.domain_name}}` to your env file. -This pass all incoming requests on `nginx-proxy` to the nginx service your application is using. - -.. _nginx-proxy: https://github.com/jwilder/nginx-proxy - -Postgres is saving its database files to `/data/{{cookiecutter.repo_name}}/postgres` by default. Change that if you wan't -something else and make sure to make backups since this is not done automatically. - -To get started, pull your code from source control (don't forget the `.env` file) and change to your projects root -directory. - -You'll need to build the stack first. To do that, run:: - - docker-compose build - -Once this is ready, you can run it with:: - - docker-compose up - - -To run a migration, open up a second terminal and run:: - - docker-compose run django python manage.py migrate - -To create a superuser, run:: - - docker-compose run django python manage.py createsuperuser - - -If you need a shell, run:: - - docker-compose run django python manage.py shell_plus - - -Once you are ready with your initial setup, you wan't to make sure that your application is run by a process manager to -survive reboots and auto restarts in case of an error. You can use the process manager you are most familiar with. All -it needs to do is to run `docker-compose up` in your projects root directory. - -If you are using `supervisor`, you can use this file as a starting point:: - - [program:{{cookiecutter.repo_name}}] - command=docker-compose up - directory=/path/to/{{cookiecutter.repo_name}} - redirect_stderr=true - autostart=true - autorestart=true - priority=10 - - -Place it in `/etc/supervisor/conf.d/{{cookiecutter.repo_name}}.conf` and run:: - - supervisorctl reread - supervisorctl start {{cookiecutter.repo_name}} - - - -To get the status, run:: - - supervisorctl status - -If you have errors, you can always check your stack with `docker-compose`. Switch to your projects root directory and run:: - - docker-compose ps - - -to get an output of all running containers. - -To check your logs, run:: - - docker-compose logs - -If you want to scale your application, run:: - - docker-compose scale django=4 - docker-compose scale celeryworker=2 - - -**Don't run the scale command on postgres or celerybeat** +.. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.org/en/latest/deployment-with-docker.html diff --git a/{{cookiecutter.repo_name}}/app.json b/{{cookiecutter.repo_name}}/app.json new file mode 100644 index 000000000..078f87eaf --- /dev/null +++ b/{{cookiecutter.repo_name}}/app.json @@ -0,0 +1,38 @@ +{ + "name": "{{cookiecutter.repo_name}}", + "description": "{{cookiecutter.description}}", + "env": { + "BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-python", + "DJANGO_SETTINGS_MODULE": "config.settings.production", + "DJANGO_SECRET_KEY": { + "description": "A secret key for verifying the integrity of signed cookies.", + "generator": "secret" + }, + "DJANGO_ALLOWED_HOSTS": { + "description": "Comma-separated list of hosts", + "value": ".herokuapp.com" + }, + "DJANGO_ADMIN_URL": { + "description": "A secret URL for the Django admin", + "generator": "secret" + }, + "DJANGO_AWS_ACCESS_KEY_ID": "", + "DJANGO_AWS_SECRET_ACCESS_KEY": "", + "DJANGO_AWS_STORAGE_BUCKET_NAME": "", + "DJANGO_MAILGUN_SERVER_NAME": "", + {% if cookiecutter.use_newrelic == "y" -%} + "NEW_RELIC_LICENSE_KEY": "", + "NEW_RELIC_APP_NAME": "", + {%- endif %} + "DJANGO_MAILGUN_API_KEY": ""{% if cookiecutter.use_sentry == "y" -%}, + "DJANGO_SENTRY_DSN": ""{%- endif %} + }, + "scripts": { + "postdeploy": "python manage.py migrate" + }, + "addons": [ + "heroku-postgresql:hobby-dev", + "heroku-redis:hobby-dev", + "mailgun" + ] +} diff --git a/{{cookiecutter.repo_name}}/compose/django/entrypoint.sh b/{{cookiecutter.repo_name}}/compose/django/entrypoint.sh old mode 100755 new mode 100644 index 5e052064d..e9286baa5 --- a/{{cookiecutter.repo_name}}/compose/django/entrypoint.sh +++ b/{{cookiecutter.repo_name}}/compose/django/entrypoint.sh @@ -4,29 +4,15 @@ set -e # Since docker-compose relies heavily on environment variables itself for configuration, we'd have to define multiple # environment variables just to support cookiecutter out of the box. That makes no sense, so this little entrypoint # does all this for us. -export DJANGO_CACHE_URL=redis://redis:6379/0 +export REDIS_URL=redis://redis:6379 # the official postgres image uses 'postgres' as default user if not set explictly. -if [ -z "$POSTGRES_ENV_POSTGRES_USER" ]; then - export POSTGRES_ENV_POSTGRES_USER=postgres -fi +if [ -z "$POSTGRES_USER" ]; then + export POSTGRES_USER=postgres +fi -export DATABASE_URL=postgres://$POSTGRES_ENV_POSTGRES_USER:$POSTGRES_ENV_POSTGRES_PASSWORD@postgres:5432/$POSTGRES_ENV_POSTGRES_USER +export DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$POSTGRES_USER {% if cookiecutter.use_celery == 'y' %} -export CELERY_BROKER_URL=$DJANGO_CACHE_URL +export CELERY_BROKER_URL=$REDIS_URL/0 {% endif %} - -# create a user, with UID of host user, -# read more about that trick: http://stackoverflow.com/a/28596874/338581 -TARGET_USER_GID=$(stat -c "%u" /app) -useradd -m -s /bin/bash -u $TARGET_USER_GID django - -echo -e "\n------------------------------------------------------------\n" -su -c "npm install" django -echo -e "\n------------------------------------------------------------\n" -su -c "grunt build" django -echo -e "\n------------------------------------------------------------\n" - -# somehow, when $@ is used directly, this doesn't work -COMMAND=$@ -su -c "$COMMAND" django +exec "$@" diff --git a/{{cookiecutter.repo_name}}/compose/postgres/Dockerfile b/{{cookiecutter.repo_name}}/compose/postgres/Dockerfile new file mode 100644 index 000000000..332723640 --- /dev/null +++ b/{{cookiecutter.repo_name}}/compose/postgres/Dockerfile @@ -0,0 +1,11 @@ +FROM postgres:9.5 + +# add backup scripts +ADD backup.sh /usr/local/bin/backup +ADD restore.sh /usr/local/bin/restore +ADD list-backups.sh /usr/local/bin/list-backups + +# make them executable +RUN chmod +x /usr/local/bin/restore +RUN chmod +x /usr/local/bin/list-backups +RUN chmod +x /usr/local/bin/backup diff --git a/{{cookiecutter.repo_name}}/compose/postgres/backup.sh b/{{cookiecutter.repo_name}}/compose/postgres/backup.sh new file mode 100644 index 000000000..97c95e1df --- /dev/null +++ b/{{cookiecutter.repo_name}}/compose/postgres/backup.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# stop on errors +set -e + +# we might run into trouble when using the default `postgres` user, e.g. when dropping the postgres +# database in restore.sh. Check that something else is used here +if [ "$POSTGRES_USER" == "postgres" ] +then + echo "creating a backup as the postgres user is not supported, make sure to set the POSTGRES_USER environment variable" + exit 1 +fi + +# export the postgres password so that subsequent commands don't ask for it +export PGPASSWORD=$POSTGRES_PASSWORD + +echo "creating backup" +echo "---------------" + +FILENAME=backup_$(date +'%Y_%m_%dT%H_%M_%S').sql +pg_dump -h postgres -U $POSTGRES_USER >> /backups/$FILENAME + +echo "successfully created backup $FILENAME" diff --git a/{{cookiecutter.repo_name}}/compose/postgres/list-backups.sh b/{{cookiecutter.repo_name}}/compose/postgres/list-backups.sh new file mode 100644 index 000000000..75972b75b --- /dev/null +++ b/{{cookiecutter.repo_name}}/compose/postgres/list-backups.sh @@ -0,0 +1,4 @@ +#!/bin/bash +echo "listing available backups" +echo "-------------------------" +ls /backups/ diff --git a/{{cookiecutter.repo_name}}/compose/postgres/restore.sh b/{{cookiecutter.repo_name}}/compose/postgres/restore.sh new file mode 100644 index 000000000..750082803 --- /dev/null +++ b/{{cookiecutter.repo_name}}/compose/postgres/restore.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# stop on errors +set -e + +# we might run into trouble when using the default `postgres` user, e.g. when dropping the postgres +# database in restore.sh. Check that something else is used here +if [ "$POSTGRES_USER" == "postgres" ] +then + echo "restoring as the postgres user is not supported, make sure to set the POSTGRES_USER environment variable" + exit 1 +fi + +# export the postgres password so that subsequent commands don't ask for it +export PGPASSWORD=$POSTGRES_PASSWORD + +# check that we have an argument for a filename candidate +if [[ $# -eq 0 ]] ; then + echo 'usage:' + echo ' docker-compose run postgres restore ' + echo '' + echo 'to get a list of available backups, run:' + echo ' docker-compose run postgres list-backups' + exit 1 +fi + +# set the backupfile variable +BACKUPFILE=/backups/$1 + +# check that the file exists +if ! [ -f $BACKUPFILE ]; then + echo "backup file not found" + echo 'to get a list of available backups, run:' + echo ' docker-compose run postgres list-backups' + exit 1 +fi + +echo "beginning restore from $1" +echo "-------------------------" + +# delete the db +# deleting the db can fail. Spit out a comment if this happens but continue since the db +# is created in the next step +echo "deleting old database $POSTGRES_USER" +if dropdb -h postgres -U $POSTGRES_USER $POSTGRES_USER +then echo "deleted $POSTGRES_USER database" +else echo "database $POSTGRES_USER does not exist, continue" +fi + +# create a new database +echo "creating new database $POSTGRES_USER" +createdb -h postgres -U $POSTGRES_USER $POSTGRES_USER -O $POSTGRES_USER + +# restore the database +echo "restoring database $POSTGRES_USER" +psql -h postgres -U $POSTGRES_USER < $BACKUPFILE diff --git a/{{cookiecutter.repo_name}}/config/settings/common.py b/{{cookiecutter.repo_name}}/config/settings/common.py index 41447d12e..f21089f71 100644 --- a/{{cookiecutter.repo_name}}/config/settings/common.py +++ b/{{cookiecutter.repo_name}}/config/settings/common.py @@ -212,6 +212,10 @@ ACCOUNT_AUTHENTICATION_METHOD = 'username' ACCOUNT_EMAIL_REQUIRED = True ACCOUNT_EMAIL_VERIFICATION = 'mandatory' +ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True) +ACCOUNT_ADAPTER = '{{cookiecutter.repo_name}}.users.adapters.AccountAdapter' +SOCIALACCOUNT_ADAPTER = '{{cookiecutter.repo_name}}.users.adapters.SocialAccountAdapter' + # Custom user app defaults # Select the correct user model AUTH_USER_MODEL = 'users.User' @@ -220,39 +224,6 @@ LOGIN_URL = 'account_login' # SLUGLIFIER AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify' - - -# LOGGING CONFIGURATION -# ------------------------------------------------------------------------------ -# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error when DEBUG=False. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } -} {% if cookiecutter.use_celery == "y" %} ########## CELERY INSTALLED_APPS += ('{{cookiecutter.repo_name}}.taskapp.celery.CeleryConfig',) @@ -261,4 +232,8 @@ INSTALLED_APPS += ('kombu.transport.django',) BROKER_URL = env("CELERY_BROKER_URL", default='django://') ########## END CELERY {% endif %} + +# Location of root django.contrib.admin URL, use {% raw %}{% url 'admin:index' %}{% endraw %} +ADMIN_URL = r'^admin/' + # Your common stuff: Below this line define 3rd party library settings diff --git a/{{cookiecutter.repo_name}}/config/settings/local.py b/{{cookiecutter.repo_name}}/config/settings/local.py index cbe5d58db..cee3e9cc6 100644 --- a/{{cookiecutter.repo_name}}/config/settings/local.py +++ b/{{cookiecutter.repo_name}}/config/settings/local.py @@ -25,7 +25,7 @@ SECRET_KEY = env("DJANGO_SECRET_KEY", default='CHANGEME!!!') # ------------------------------------------------------------------------------ EMAIL_HOST = 'localhost' EMAIL_PORT = 1025 -{%if cookiecutter.use_maildump == "n" -%} +{%if cookiecutter.use_mailhog == "n" -%} EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend') {%- endif %} @@ -60,7 +60,7 @@ INSTALLED_APPS += ('django_extensions', ) # TESTING # ------------------------------------------------------------------------------ TEST_RUNNER = 'django.test.runner.DiscoverRunner' -{% if cookiecutter.celery_support == "y" %} +{% if cookiecutter.use_celery == "y" %} ########## CELERY # In development, all tasks will be executed locally by blocking until the task returns CELERY_ALWAYS_EAGER = True diff --git a/{{cookiecutter.repo_name}}/config/settings/production.py b/{{cookiecutter.repo_name}}/config/settings/production.py index 23830a33a..f541843f5 100644 --- a/{{cookiecutter.repo_name}}/config/settings/production.py +++ b/{{cookiecutter.repo_name}}/config/settings/production.py @@ -9,6 +9,9 @@ Production Configurations {% if cookiecutter.use_sentry == "y" %} - Use sentry for error logging {% endif %} +{% if cookiecutter.use_opbeat == "y" %} +- Use opbeat for error reporting +{% endif %} ''' from __future__ import absolute_import, unicode_literals @@ -52,7 +55,19 @@ MIDDLEWARE_CLASSES = SECURITY_MIDDLEWARE + \ MIDDLEWARE_CLASSES = SECURITY_MIDDLEWARE + MIDDLEWARE_CLASSES {%- endif %} - +{% if cookiecutter.use_opbeat == "y" -%} +# opbeat integration +# See https://opbeat.com/languages/django/ +INSTALLED_APPS += ('opbeat.contrib.django',) +OPBEAT = { + 'ORGANIZATION_ID': env('DJANGO_OPBEAT_ORGANIZATION_ID'), + 'APP_ID': env('DJANGO_OPBEAT_APP_ID'), + 'SECRET_TOKEN': env('DJANGO_OPBEAT_SECRET_TOKEN') +} +MIDDLEWARE_CLASSES = ( + 'opbeat.contrib.django.middleware.OpbeatAPMMiddleware', +) + MIDDLEWARE_CLASSES +{%- endif %} # set this to 60 seconds and then to 518400 when you can prove it works SECURE_HSTS_SECONDS = 60 SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( @@ -69,7 +84,7 @@ SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True) # ------------------------------------------------------------------------------ # Hosts/domain names that are valid for this site # See https://docs.djangoproject.com/en/1.6/ref/settings/#allowed-hosts -ALLOWED_HOSTS = ["*"] +ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['{{cookiecutter.domain_name}}']) # END SITE CONFIGURATION INSTALLED_APPS += ("gunicorn", ) @@ -82,7 +97,6 @@ INSTALLED_APPS += ("gunicorn", ) INSTALLED_APPS += ( 'storages', ) -DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' AWS_ACCESS_KEY_ID = env('DJANGO_AWS_ACCESS_KEY_ID') AWS_SECRET_ACCESS_KEY = env('DJANGO_AWS_SECRET_ACCESS_KEY') @@ -104,15 +118,25 @@ AWS_HEADERS = { # URL that handles the media served from MEDIA_ROOT, used for managing # stored files. +{% if cookiecutter.use_whitenoise == 'y' -%} MEDIA_URL = 'https://s3.amazonaws.com/%s/' % AWS_STORAGE_BUCKET_NAME +{% else %} +# See:http://stackoverflow.com/questions/10390244/ +from storages.backends.s3boto import S3BotoStorage +StaticRootS3BotoStorage = lambda: S3BotoStorage(location='static') +MediaRootS3BotoStorage = lambda: S3BotoStorage(location='media') +DEFAULT_FILE_STORAGE = 'config.settings.production.MediaRootS3BotoStorage' -# Static Assests +MEDIA_URL = 'https://s3.amazonaws.com/%s/media/' % AWS_STORAGE_BUCKET_NAME +{%- endif %} + +# Static Assets # ------------------------ {% if cookiecutter.use_whitenoise == 'y' -%} STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage' {% else %} -STATICFILES_STORAGE = DEFAULT_FILE_STORAGE -STATIC_URL = MEDIA_URL +STATIC_URL = 'https://s3.amazonaws.com/%s/static/' % AWS_STORAGE_BUCKET_NAME +STATICFILES_STORAGE = 'config.settings.production.StaticRootS3BotoStorage' # See: https://github.com/antonagestam/collectfast # For Django 1.7+, 'collectfast' should come before @@ -130,6 +154,10 @@ MAILGUN_ACCESS_KEY = env('DJANGO_MAILGUN_API_KEY') MAILGUN_SERVER_NAME = env('DJANGO_MAILGUN_SERVER_NAME') EMAIL_SUBJECT_PREFIX = env("DJANGO_EMAIL_SUBJECT_PREFIX", default='[{{cookiecutter.project_name}}] ') SERVER_EMAIL = env('DJANGO_SERVER_EMAIL', default=DEFAULT_FROM_EMAIL) +{% if cookiecutter.use_newrelic == 'y'-%} +NEW_RELIC_LICENSE_KEY = env('NEW_RELIC_LICENSE_KEY') +NEW_RELIC_APP_NAME = env('NEW_RELIC_APP_NAME') +{%- endif %} # TEMPLATE CONFIGURATION # ------------------------------------------------------------------------------ @@ -151,7 +179,7 @@ DATABASES['default'] = env.db("DATABASE_URL") CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", - "LOCATION": "{0}/{1}".format(env.cache_url('REDIS_URL', default="redis://127.0.0.1:6379"), 0), + "LOCATION": "{0}/{1}".format(env('REDIS_URL', default="redis://127.0.0.1:6379"), 0), "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "IGNORE_EXCEPTIONS": True, # mimics memcache behavior. @@ -162,7 +190,8 @@ CACHES = { {% if cookiecutter.use_sentry == "y" %} # Sentry Configuration -SENTRY_CLIENT = env('DJANGO_SENTRY_CLIENT') +SENTRY_DSN = env('DJANGO_SENTRY_DSN') +SENTRY_CLIENT = env('DJANGO_SENTRY_CLIENT', default='raven.contrib.django.raven_compat.DjangoClient') LOGGING = { 'version': 1, 'disable_existing_loggers': True, @@ -203,11 +232,68 @@ LOGGING = { 'handlers': ['console'], 'propagate': False, }, + 'django.security.DisallowedHost': { + 'level': 'ERROR', + 'handlers': ['console', 'sentry'], + 'propagate': False, + }, }, } -SENTRY_CELERY_LOGLEVEL = env('DJANGO_SENTRY_LOG_LEVEL', logging.INFO) +SENTRY_CELERY_LOGLEVEL = env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO) RAVEN_CONFIG = { - 'CELERY_LOGLEVEL': env('DJANGO_SENTRY_LOG_LEVEL', logging.INFO) + 'CELERY_LOGLEVEL': env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO), + 'DSN': SENTRY_DSN +} +{% elif cookiecutter.use_sentry == "n" %} +# LOGGING CONFIGURATION +# ------------------------------------------------------------------------------ +# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging +# A sample logging configuration. The only tangible logging +# performed by this configuration is to send an email to +# the site admins on every HTTP 500 error when DEBUG=False. +# See http://docs.djangoproject.com/en/dev/topics/logging for +# more details on how to customize your logging configuration. +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'filters': { + 'require_debug_false': { + '()': 'django.utils.log.RequireDebugFalse' + } + }, + 'formatters': { + 'verbose': { + 'format': '%(levelname)s %(asctime)s %(module)s ' + '%(process)d %(thread)d %(message)s' + }, + }, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'filters': ['require_debug_false'], + 'class': 'django.utils.log.AdminEmailHandler' + }, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'verbose', + }, + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True + }, + 'django.security.DisallowedHost': { + 'level': 'ERROR', + 'handlers': ['console', 'mail_admins'], + 'propagate': True + } + } } {% endif %} +# Custom Admin URL, use {% raw %}{% url 'admin:index' %}{% endraw %} +ADMIN_URL = env('DJANGO_ADMIN_URL') + # Your production stuff: Below this line define 3rd party library settings diff --git a/{{cookiecutter.repo_name}}/config/urls.py b/{{cookiecutter.repo_name}}/config/urls.py index 106445512..7d394947e 100644 --- a/{{cookiecutter.repo_name}}/config/urls.py +++ b/{{cookiecutter.repo_name}}/config/urls.py @@ -6,13 +6,14 @@ from django.conf.urls import include, url from django.conf.urls.static import static from django.contrib import admin from django.views.generic import TemplateView +from django.views import defaults as default_views urlpatterns = [ url(r'^$', TemplateView.as_view(template_name='pages/home.html'), name="home"), url(r'^about/$', TemplateView.as_view(template_name='pages/about.html'), name="about"), - # Django Admin - url(r'^admin/', include(admin.site.urls)), + # Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %} + url(settings.ADMIN_URL, include(admin.site.urls)), # User management url(r'^users/', include("{{ cookiecutter.repo_name }}.users.urls", namespace="users")), @@ -27,8 +28,8 @@ if settings.DEBUG: # This allows the error pages to be debugged during development, just visit # these url in browser to see how these error pages look like. urlpatterns += [ - url(r'^400/$', 'django.views.defaults.bad_request'), - url(r'^403/$', 'django.views.defaults.permission_denied'), - url(r'^404/$', 'django.views.defaults.page_not_found'), - url(r'^500/$', 'django.views.defaults.server_error'), + url(r'^400/$', default_views.bad_request, kwargs={'exception': Exception("Bad Request!")}), + url(r'^403/$', default_views.permission_denied, kwargs={'exception': Exception("Permission Denied")}), + url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception("Page not Found")}), + url(r'^500/$', default_views.server_error), ] diff --git a/{{cookiecutter.repo_name}}/config/wsgi.py b/{{cookiecutter.repo_name}}/config/wsgi.py index 47f60dd44..50ca3afef 100644 --- a/{{cookiecutter.repo_name}}/config/wsgi.py +++ b/{{cookiecutter.repo_name}}/config/wsgi.py @@ -15,12 +15,18 @@ framework. """ import os +{% if cookiecutter.use_newrelic == "y" -%} +if os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.production": + import newrelic.agent + newrelic.agent.initialize() +{%- endif %} from django.core.wsgi import get_wsgi_application {% if cookiecutter.use_whitenoise == 'y' -%} from whitenoise.django import DjangoWhiteNoise {%- endif %} {% if cookiecutter.use_sentry == "y" -%} -from raven.contrib.django.raven_compat.middleware.wsgi import Sentry +if os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.production": + from raven.contrib.django.raven_compat.middleware.wsgi import Sentry {%- endif %} # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks @@ -40,9 +46,13 @@ application = get_wsgi_application() application = DjangoWhiteNoise(application) {%- endif %} {% if cookiecutter.use_sentry == "y" -%} -application = Sentry(application) +if os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.production": + application = Sentry(application) +{%- endif %} +{% if cookiecutter.use_newrelic == "y" -%} +if os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.production": + application = newrelic.agent.WSGIApplicationWrapper(application) {%- endif %} - # Apply WSGI middleware here. # from helloworld.wsgi import HelloWorldApplication # application = HelloWorldApplication(application) diff --git a/{{cookiecutter.repo_name}}/dev.yml b/{{cookiecutter.repo_name}}/dev.yml index 98146ffed..b48f53c89 100644 --- a/{{cookiecutter.repo_name}}/dev.yml +++ b/{{cookiecutter.repo_name}}/dev.yml @@ -1,16 +1,27 @@ -postgres: - image: postgres - volumes: - # If you are using boot2docker, postgres data has to live in the VM for now until #581 is fixed - # for more info see here: https://github.com/boot2docker/boot2docker/issues/581 - - /data/{{cookiecutter.repo_name}}/postgres:/var/lib/postgresql/data +version: '2' +services: + postgres: + build: ./compose/postgres + volumes: + # If you are using boot2docker, postgres data has to live in the VM for now until #581 is fixed + # for more info see here: https://github.com/boot2docker/boot2docker/issues/581 + - /data/dev/{{cookiecutter.repo_name}}/postgres:/var/lib/postgresql/data + - /data/dev/{{cookiecutter.repo_name}}/postgres-backups:/backups + environment: + - POSTGRES_USER={{cookiecutter.repo_name}} -django: - build: . - command: python /app/manage.py runserver_plus 0.0.0.0:8000 - volumes: - - .:/app - ports: - - "8000:8000" - links: - - postgres \ No newline at end of file + django: + build: + context: . + dockerfile: Dockerfile-dev + command: python /app/manage.py runserver_plus 0.0.0.0:8000 + depends_on: + - postgres + environment: + - POSTGRES_USER={{cookiecutter.repo_name}} + volumes: + - .:/app + ports: + - "8000:8000" + links: + - postgres diff --git a/{{cookiecutter.repo_name}}/docker-compose.yml b/{{cookiecutter.repo_name}}/docker-compose.yml index 4ab8e9b76..798cb2322 100644 --- a/{{cookiecutter.repo_name}}/docker-compose.yml +++ b/{{cookiecutter.repo_name}}/docker-compose.yml @@ -1,45 +1,58 @@ -postgres: - image: postgres:9.4 - volumes: - - /data/{{cookiecutter.repo_name}}/postgres:/var/lib/postgresql/data - env_file: .env +version: '2' +services: + postgres: + build: ./compose/postgres + volumes: + - /data/{{cookiecutter.repo_name}}/postgres:/var/lib/postgresql/data + - /data/{{cookiecutter.repo_name}}/postgres-backups:/backups + env_file: .env -django: - build: . - user: django - links: - - postgres - - redis - command: /app/compose/django/gunicorn.sh - env_file: .env + django: + build: + context: . + user: django + depends_on: + - postgres + - redis + command: /gunicorn.sh + env_file: .env + depends_on: + - postgres + - redis -nginx: - build: ./compose/nginx - links: - - django - ports: - - "0.0.0.0:80:80" + nginx: + build: ./compose/nginx + depends_on: + - django + ports: + - "0.0.0.0:80:80" -redis: - image: redis:3.0 - -{% if cookiecutter.use_celery == 'y' %} -celeryworker: - build: . - user: django - env_file: .env - links: - - postgres - - redis - command: celery -A {{cookiecutter.repo_name}}.taskapp worker -l INFO - -celerybeat: - build: . - user: django - env_file: .env - links: - - postgres - - redis - command: celery -A {{cookiecutter.repo_name}}.taskapp beat -l INFO -{% endif %} + redis: + image: redis:3.0 + {% if cookiecutter.use_celery == 'y' %} + celeryworker: + build: + context: . + user: django + env_file: .env + depends_on: + - postgres + - redis + command: celery -A {{cookiecutter.repo_name}}.taskapp worker -l INFO + depends_on: + - postgres + - redis + celerybeat: + build: + context: . + user: django + env_file: .env + depends_on: + - postgres + - redis + command: celery -A {{cookiecutter.repo_name}}.taskapp beat -l INFO + depends_on: + - postgres + - redis + {% endif %} diff --git a/{{cookiecutter.repo_name}}/env.example b/{{cookiecutter.repo_name}}/env.example index b43753243..85c96d3cd 100644 --- a/{{cookiecutter.repo_name}}/env.example +++ b/{{cookiecutter.repo_name}}/env.example @@ -1,12 +1,27 @@ POSTGRES_PASSWORD=mysecretpass POSTGRES_USER=postgresuser +DJANGO_ADMIN_URL= DJANGO_SETTINGS_MODULE=config.settings.production -DJANGO_SECRET_KEY= +DJANGO_SECRET_KEY=CHANGEME!!! +DJANGO_ALLOWED_HOSTS=.{{ cookiecutter.domain_name }} DJANGO_AWS_ACCESS_KEY_ID= DJANGO_AWS_SECRET_ACCESS_KEY= DJANGO_AWS_STORAGE_BUCKET_NAME= DJANGO_MAILGUN_API_KEY= DJANGO_MAILGUN_SERVER_NAME= DJANGO_SERVER_EMAIL= -DJANGO_SECURE_SSL_REDIRECT=False \ No newline at end of file +DJANGO_SECURE_SSL_REDIRECT=False +DJANGO_ACCOUNT_ALLOW_REGISTRATION=True +{% if cookiecutter.use_sentry == 'y' -%} +DJANGO_SENTRY_DSN= +{% endif %} +{% if cookiecutter.use_newrelic == 'y' -%} +NEW_RELIC_LICENSE_KEY= +NEW_RELIC_APP_NAME={{cookiecutter.repo_name}} +{% endif %} +{% if cookiecutter.use_opbeat == 'y' -%} +DJANGO_OPBEAT_ORGANIZATION_ID +DJANGO_OPBEAT_APP_ID +DJANGO_OPBEAT_SECRET_TOKEN +{% endif %} diff --git a/{{cookiecutter.repo_name}}/install_os_dependencies.sh b/{{cookiecutter.repo_name}}/install_os_dependencies.sh index 477dcafd0..aea9dec45 100755 --- a/{{cookiecutter.repo_name}}/install_os_dependencies.sh +++ b/{{cookiecutter.repo_name}}/install_os_dependencies.sh @@ -24,7 +24,7 @@ function usage_message() # Read the requirements.apt file, and remove comments and blank lines function list_packages(){ - cat ${OS_REQUIREMENTS_FILENAME} | grep -v "#" | grep -v "^$"; + grep -v "#" ${OS_REQUIREMENTS_FILENAME} | grep -v "^$"; } function install() diff --git a/{{cookiecutter.repo_name}}/install_python_dependencies.sh b/{{cookiecutter.repo_name}}/install_python_dependencies.sh index 0de90c829..34929e607 100755 --- a/{{cookiecutter.repo_name}}/install_python_dependencies.sh +++ b/{{cookiecutter.repo_name}}/install_python_dependencies.sh @@ -3,7 +3,7 @@ pip --version >/dev/null 2>&1 || { echo >&2 -e "\npip is required but it's not installed." echo >&2 -e "You can install it by running the following command:\n" - echo >&2 "wget https://bootstrap.pypa.io/get-pip.py; chmod +x get-pip.py; sudo ./get-pip.py" + echo >&2 "wget https://bootstrap.pypa.io/get-pip.py --output-document=get-pip.py; chmod +x get-pip.py; sudo -H python3 get-pip.py" echo >&2 -e "\n" echo >&2 -e "\nFor more information, see pip documentation: https://pip.pypa.io/en/latest/" exit 1; @@ -12,7 +12,7 @@ pip --version >/dev/null 2>&1 || { virtualenv --version >/dev/null 2>&1 || { echo >&2 -e "\nvirtualenv is required but it's not installed." echo >&2 -e "You can install it by running the following command:\n" - echo >&2 "sudo pip install virtualenv" + echo >&2 "sudo -H pip3 install virtualenv" echo >&2 -e "\n" echo >&2 -e "\nFor more information, see virtualenv documentation: https://virtualenv.pypa.io/en/latest/" exit 1; @@ -21,7 +21,7 @@ virtualenv --version >/dev/null 2>&1 || { if [ -z "$VIRTUAL_ENV" ]; then echo >&2 -e "\nYou need activate a virtualenv first" echo >&2 -e 'If you do not have a virtualenv created, run the following command to create and automatically activate a new virtualenv named "venv" on current folder:\n' - echo >&2 -e "virtualenv venv" + echo >&2 -e "virtualenv venv --python=\`which python3\`" echo >&2 -e "\nTo leave/disable the currently active virtualenv, run the following command:\n" echo >&2 "deactivate" echo >&2 -e "\nTo activate the virtualenv again, run the following command:\n" diff --git a/{{cookiecutter.repo_name}}/pytest.ini b/{{cookiecutter.repo_name}}/pytest.ini new file mode 100644 index 000000000..d19d28c54 --- /dev/null +++ b/{{cookiecutter.repo_name}}/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +DJANGO_SETTINGS_MODULE=config.settings.local diff --git a/{{cookiecutter.repo_name}}/requirements/base.txt b/{{cookiecutter.repo_name}}/requirements/base.txt index 6f39bbb7c..836d7241d 100644 --- a/{{cookiecutter.repo_name}}/requirements/base.txt +++ b/{{cookiecutter.repo_name}}/requirements/base.txt @@ -1,45 +1,58 @@ +{% if cookiecutter.use_python2 == 'n' -%} +# Wheel 0.25+ needed to install certain packages on CPython 3.5+ +# like Pillow and psycopg2 +# See http://bitly.com/wheel-building-fails-CPython-35 +# Verified bug on Python 3.5.1 +wheel==0.29.0 +{%- endif %} + # Bleeding edge Django -django==1.8.4 +django==1.9.4 # Configuration -django-environ==0.3.0 +django-environ==0.4.0 django-secure==1.0.1 {% if cookiecutter.use_whitenoise == 'y' -%} -whitenoise==2.0.3 +whitenoise==2.0.6 {%- endif %} # Forms django-braces==1.8.1 -django-crispy-forms==1.5.0 -django-floppyforms==1.5.2 +django-crispy-forms==1.6.0 +django-floppyforms==1.6.1 # Models -django-model-utils==2.3.1 +django-model-utils==2.4 # Images -Pillow==2.9.0 +Pillow==3.1.1 # For user registration, either via email or social # Well-built with regular release cycles! -django-allauth==0.23.0 +django-allauth==0.24.1 -# For the persistence stores +{% if cookiecutter.windows == 'y' -%} +# On Windows, you must download/install psycopg2 manually +# from http://www.lfd.uci.edu/~gohlke/pythonlibs/#psycopg +{% else %} +# Python-PostgreSQL Database Adapter psycopg2==2.6.1 +{%- endif %} # Unicode slugification unicode-slugify==0.1.3 -django-autoslug==1.8.0 +django-autoslug==1.9.3 # Time zones support -pytz==2015.4 +pytz==2015.7 # Redis support -django-redis==4.2.0 +django-redis==4.3.0 redis>=2.10.0 {% if cookiecutter.use_celery == "y" %} -celery==3.1.18 +celery==3.1.21 {% endif %} # Your custom requirements go here diff --git a/{{cookiecutter.repo_name}}/requirements/local.txt b/{{cookiecutter.repo_name}}/requirements/local.txt index 431b16665..821b204af 100644 --- a/{{cookiecutter.repo_name}}/requirements/local.txt +++ b/{{cookiecutter.repo_name}}/requirements/local.txt @@ -1,21 +1,19 @@ # Local development dependencies go here -r base.txt -coverage==3.7.1 +coverage==4.0.3 +django_coverage_plugin==1.2.2 Sphinx -django-extensions==1.5.5 -Werkzeug==0.10.4 -django-test-plus==1.0.8 -factory_boy==2.5.2 +django-extensions==1.6.1 +Werkzeug==0.11.4 +django-test-plus==1.0.12 +factory_boy==2.6.1 # django-debug-toolbar that works with Django 1.5+ -django-debug-toolbar==1.3.2 +django-debug-toolbar==1.4 # improved REPL -ipdb==0.8.1 +ipdb==0.9.0 -{% if cookiecutter.use_maildump == "y" -%} -# Required by maildump. Need to pin dependency to gevent beta to be Python 3-compatible. -gevent==1.1b2 -# Enables better email testing -maildump==0.5.1 -{%- endif %} +# pytest! +pytest-django==2.9.1 +pytest-sugar==0.5.1 diff --git a/{{cookiecutter.repo_name}}/requirements/production.txt b/{{cookiecutter.repo_name}}/requirements/production.txt index 73d66d190..0c25b9090 100644 --- a/{{cookiecutter.repo_name}}/requirements/production.txt +++ b/{{cookiecutter.repo_name}}/requirements/production.txt @@ -2,25 +2,49 @@ # production that isn't in development. -r base.txt +{% if cookiecutter.windows == 'y' -%} +# Python-PostgreSQL Database Adapter +# If using Win for dev, this assumes Unix in prod +# ------------------------------------------------ +psycopg2==2.6.1 +{%- endif %} + # WSGI Handler # ------------------------------------------------ +{% if cookiecutter.use_python2 == 'y' -%} gevent==1.0.2 -gunicorn==19.3.0 +{% else %} +# there's no python 3 support in stable, have to use the latest release candidate for gevent +gevent==1.1rc5 +{% endif %} +gunicorn==19.4.5 # Static and Media Storage # ------------------------------------------------ -boto==2.38.0 -django-storages-redux==1.3 +boto==2.39.0 +django-storages-redux==1.3.2 {% if cookiecutter.use_whitenoise != 'y' -%} Collectfast==0.2.3 {%- endif %} # Mailgun Support # --------------- -django-mailgun==0.2.2 +django-mailgun==0.8.0 {% if cookiecutter.use_sentry == "y" -%} # Raven is the Sentry client # -------------------------- raven {%- endif %} + +{% if cookiecutter.use_newrelic == "y" -%} +# Newrelic agent for performance monitoring +# ----------------------------------------- +newrelic +{%- endif %} + +{% if cookiecutter.use_opbeat == "y" -%} +# Opbeat agent for performance monitoring +# ----------------------------------------- +opbeat +{%- endif %} diff --git a/{{cookiecutter.repo_name}}/requirements/test.txt b/{{cookiecutter.repo_name}}/requirements/test.txt index dae48a1ce..db5e70b88 100644 --- a/{{cookiecutter.repo_name}}/requirements/test.txt +++ b/{{cookiecutter.repo_name}}/requirements/test.txt @@ -1,6 +1,18 @@ # Test dependencies go here. -r base.txt -coverage==4.0b1 -flake8==2.4.1 -django-test-plus==1.0.8 -factory_boy==2.5.2 + +{% if cookiecutter.windows == 'y' -%} +# Python-PostgreSQL Database Adapter +# If using Win for dev, this assumes Unix in test/prod +psycopg2==2.6.1 +{%- endif %} + +coverage==4.0.3 +django_coverage_plugin==1.2.2 +flake8==2.5.4 +django-test-plus==1.0.12 +factory_boy==2.6.1 + +# pytest! +pytest-django==2.9.1 +pytest-sugar==0.5.1 diff --git a/{{cookiecutter.repo_name}}/runtime.txt b/{{cookiecutter.repo_name}}/runtime.txt new file mode 100644 index 000000000..84c90354c --- /dev/null +++ b/{{cookiecutter.repo_name}}/runtime.txt @@ -0,0 +1,5 @@ +{% if cookiecutter.use_python2 == 'n' -%} +python-3.5.1 +{% else %} +python-2.7.10 +{%- endif %} diff --git a/{{cookiecutter.repo_name}}/setup.cfg b/{{cookiecutter.repo_name}}/setup.cfg index 51059eda1..c18b80d95 100644 --- a/{{cookiecutter.repo_name}}/setup.cfg +++ b/{{cookiecutter.repo_name}}/setup.cfg @@ -1,4 +1,7 @@ [flake8] -#ignore = E265 max-line-length = 120 -exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs +exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules + +[pep8] +max-line-length = 120 +exclude=.tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules diff --git a/{{cookiecutter.repo_name}}/tests/all.settings b/{{cookiecutter.repo_name}}/tests/all.settings new file mode 100644 index 000000000..0c3965603 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/all.settings @@ -0,0 +1,21 @@ +# Global settings for your hitch tests + +failfast: true +xvfb: false +pause_on_success: false +pause_on_failure: false +startup_timeout: 45 +shutdown_timeout: 5 +environment_variables: + DATABASE_URL: postgres://{{cookiecutter.repo_name}}:password@127.0.0.1:15432/{{cookiecutter.repo_name}} + SECRET_KEY: cj5^uos4tfCdfghjkf5hq$9$(@-79^e9&x$3vyf#igvsfm4d=+ + CELERY_BROKER_URL: redis://localhost:16379 + DJANGO_EMAIL_BACKEND: django.core.mail.backends.smtp.EmailBackend +window_size: + width: 1024 + height: 600 +python_version: {% if cookiecutter.use_python2 == 'n' %}3.5.0{% else %}2.7.10{% endif %} +environment: + - approved_platforms: + - linux + - darwin diff --git a/{{cookiecutter.repo_name}}/tests/ci.settings b/{{cookiecutter.repo_name}}/tests/ci.settings new file mode 100644 index 000000000..d282b3468 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/ci.settings @@ -0,0 +1,15 @@ +# Continuous integration settings for your tests +# +# Run with : hitch test . --settings ci.settings +# +# * Does not stop test run on first failure. +# * Firefox is run headless. +# * Start up timeout is higher (CI machines are not always powerful) + +failfast: false +xvfb: true +startup_timeout: 60 + +# Comment out in Jenkins or other CI environments that don't print +# color characters correctly: +# colorless: true diff --git a/{{cookiecutter.repo_name}}/tests/engine.py b/{{cookiecutter.repo_name}}/tests/engine.py index 388b08fde..fa6b2b658 100644 --- a/{{cookiecutter.repo_name}}/tests/engine.py +++ b/{{cookiecutter.repo_name}}/tests/engine.py @@ -19,30 +19,25 @@ class ExecutionEngine(hitchtest.ExecutionEngine): def set_up(self): """Ensure virtualenv present, then run all services.""" python_package = hitchpython.PythonPackage( - python_version=self.preconditions['python_version'] + python_version=self.settings['python_version'] ) python_package.build() - python_package.verify() call([ python_package.pip, "install", "-r", path.join(PROJECT_DIRECTORY, "requirements/local.txt") ]) - postgres_package = hitchpostgres.PostgresPackage( - version=self.settings["postgres_version"], - ) + postgres_package = hitchpostgres.PostgresPackage() postgres_package.build() - postgres_package.verify() - redis_package = hitchredis.RedisPackage(version="2.8.4") + redis_package = hitchredis.RedisPackage() redis_package.build() - redis_package.verify() self.services = hitchserve.ServiceBundle( project_directory=PROJECT_DIRECTORY, startup_timeout=float(self.settings["startup_timeout"]), - shutdown_timeout=5.0, + shutdown_timeout=float(self.settings["shutdown_timeout"]), ) postgres_user = hitchpostgres.PostgresUser("{{cookiecutter.repo_name}}", "password") @@ -58,7 +53,6 @@ class ExecutionEngine(hitchtest.ExecutionEngine): self.services['Django'] = hitchpython.DjangoService( python=python_package.python, port=8000, - version=str(self.settings.get("django_version")), settings="config.settings.local", needs=[self.services['Postgres'], ], env_vars=self.settings['environment_variables'], @@ -68,10 +62,9 @@ class ExecutionEngine(hitchtest.ExecutionEngine): redis_package=redis_package, port=16379, ) -{% if cookiecutter.celery_support == "y" %} +{% if cookiecutter.use_celery == "y" %} self.services['Celery'] = hitchpython.CeleryService( python=python_package.python, - version="3.1.18", app="{{cookiecutter.repo_name}}.taskapp", loglevel="INFO", needs=[ self.services['Redis'], self.services['Django'], @@ -80,7 +73,7 @@ class ExecutionEngine(hitchtest.ExecutionEngine): ) {% endif %} self.services['Firefox'] = hitchselenium.SeleniumService( - xvfb=self.settings.get("quiet", False), + xvfb=self.settings.get("xvfb", False), no_libfaketime=True, ) @@ -93,9 +86,23 @@ class ExecutionEngine(hitchtest.ExecutionEngine): self.services.startup(interactive=False) - # Configure selenium driver + # Docs : https://hitchtest.readthedocs.org/en/latest/plugins/hitchselenium.html self.driver = self.services['Firefox'].driver - self.driver.set_window_size(self.settings['window_size']['height'], self.settings['window_size']['width']) + + self.webapp = hitchselenium.SeleniumStepLibrary( + selenium_webdriver=self.driver, + wait_for_timeout=5, + ) + + # Add selenium steps + self.click = self.webapp.click + self.wait_to_appear = self.webapp.wait_to_appear + self.wait_to_contain = self.webapp.wait_to_contain + self.wait_for_any_to_contain = self.webapp.wait_for_any_to_contain + self.click_and_dont_wait_for_page_load = self.webapp.click_and_dont_wait_for_page_load + + # Configure selenium driver + self.driver.set_window_size(self.settings['window_size']['width'], self.settings['window_size']['height']) self.driver.set_window_position(0, 0) self.driver.implicitly_wait(2.0) self.driver.accept_next_alert = True @@ -111,24 +118,23 @@ class ExecutionEngine(hitchtest.ExecutionEngine): def load_website(self): """Navigate to website in Firefox.""" self.driver.get(self.services['Django'].url()) - - def click(self, on): - """Click on HTML id.""" - self.driver.find_element_by_id(on).click() + self.click("djHideToolBarButton") def fill_form(self, **kwargs): """Fill in a form with id=value.""" for element, text in kwargs.items(): self.driver.find_element_by_id(element).send_keys(text) - def click_submit(self): - """Click on a submit button if it exists.""" - self.driver.find_element_by_css_selector("button[type=\"submit\"]").click() - def confirm_emails_sent(self, number): """Count number of emails sent by app.""" assert len(self.services['HitchSMTP'].logs.json()) == int(number) + def click_on_link_in_last_email(self, which=1): + """Click on the nth link in the last email sent.""" + self.driver.get( + self.services['HitchSMTP'].logs.json()[-1]['links'][which - 1] + ) + def wait_for_email(self, containing=None): """Wait for, and return email.""" self.services['HitchSMTP'].logs.out.tail.until_json( diff --git a/{{cookiecutter.repo_name}}/tests/hitchreqs.txt b/{{cookiecutter.repo_name}}/tests/hitchreqs.txt index a353684ba..e4b91c964 100644 --- a/{{cookiecutter.repo_name}}/tests/hitchreqs.txt +++ b/{{cookiecutter.repo_name}}/tests/hitchreqs.txt @@ -1,26 +1,43 @@ -click==5.1 -colorama==0.3.3 +backports-abc==0.4 +click==6.3 +colorama==0.3.6 +decorator==4.0.9 +docopt==0.6.2 faketime==0.9.6.3 hitchcron==0.2 -hitchpostgres==0.6.2 -hitchpython==0.3.2 -hitchredis==0.4.1 -hitchselenium==0.4 -hitchserve==0.4.3 +hitchpostgres==0.7.0 +hitchpython==0.5.3 +hitchredis==0.4.6 +hitchselenium==0.5.1 +hitchserve==0.4.9 hitchsmtp==0.2.1 -hitchtest==0.7.5 +hitchsystem==0.1.3 +hitchtest==0.9.8 humanize==0.5.1 -ipython==4.0.0 +ipython==4.1.2 +ipython-genutils==0.1.0 Jinja2==2.8 +jupyter-client==4.2.0 +jupyter-core==4.0.6 MarkupSafe==0.23 -patool==1.8 -psutil==3.1.1 -python-build==0.2.1 -pyuv==1.1.0 +path.py==8.1.2 +patool==1.12 +pexpect==4.0.1 +pickleshare==0.6 +psutil==4.0.0 +ptyprocess==0.5.1 +pykwalify==1.5.0 +python-build==0.2.13 +python-dateutil==2.5.0 +pyuv==1.2.0 PyYAML==3.11 -requests==2.7.0 -selenium==2.47.1 -six==1.9.0 -tblib==1.1.0 -tornado==4.2.1 +pyzmq==15.2.0 +requests==2.9.1 +selenium==2.52.0 +simplegeneric==0.8.1 +six==1.10.0 +tblib==1.2.0 +tornado==4.3 +traitlets==4.1.0 +unixpackage==0.4.1 xeger==0.3 diff --git a/{{cookiecutter.repo_name}}/tests/register-and-log-in.test b/{{cookiecutter.repo_name}}/tests/register-and-log-in.test new file mode 100644 index 000000000..971c9a6b8 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/register-and-log-in.test @@ -0,0 +1,18 @@ +- name: Sign up and log in + scenario: + - Load website + - Click: sign-up-link + - Fill form: + id_username: testuser + id_email: testuser@domain.com + id_password1: password + id_password2: password + - Click: sign-up-button + - Wait for email: + containing: Please Confirm Your E-mail Address + - Click on link in last email + - Click: confirm-button + - Fill form: + id_login: testuser + id_password: password + - Click: sign-in-button diff --git a/{{cookiecutter.repo_name}}/tests/settings.yml b/{{cookiecutter.repo_name}}/tests/settings.yml deleted file mode 100644 index 7ea14f07e..000000000 --- a/{{cookiecutter.repo_name}}/tests/settings.yml +++ /dev/null @@ -1,59 +0,0 @@ -postgres_version: 9.3.9 -redis_version: 2.8.4 -django_version: 1.8.3 -celery_version: 3.1.18 -pause_on_success: false -pause_on_failure: true -startup_timeout: 45 -environment_variables: - DATABASE_URL: postgres://{{cookiecutter.repo_name}}:password@127.0.0.1:15432/{{cookiecutter.repo_name}} - SECRET_KEY: cj5^uos4tfCdfghjkf5hq$9$(@-79^e9&x$3vyf#igvsfm4d=+ - CELERY_BROKER_URL: redis://localhost:16379 -window_size: - width: 450 - height: 450 -python_versions: - - 2.7.10 -environment: - - approved_platforms: - - linux - - darwin - - freeports: - - 1025 - - 8000 - - 15432 - - 16379 - - brew: - - libtool - - automake - - node - - debs: - - python-setuptools - - python3-dev - - python-virtualenv - - python-pip - - firefox - - automake - - libtool - - libreadline6 - - libreadline6-dev - - libreadline-dev - - libsqlite3-dev - - libpq-dev - - libxml2 - - libxml2-dev - - libssl-dev - - libbz2-dev - - wget - - curl - - llvm - - graphviz-dev - - libtiff4-dev - - libjpeg8-dev - - libfreetype6-dev - - liblcms1-dev - - libwebp-dev - - zlib1g-dev - - gettext - - python-dev - - build-essential diff --git a/{{cookiecutter.repo_name}}/tests/stub.test b/{{cookiecutter.repo_name}}/tests/stub.test deleted file mode 100644 index 88b4a1d0d..000000000 --- a/{{cookiecutter.repo_name}}/tests/stub.test +++ /dev/null @@ -1,10 +0,0 @@ -{% raw %}{% extends "base.yml" %} -{% block test %} -- engine: engine.py:ExecutionEngine - name: Stub {{ python_version }} - preconditions: - python_version: "{{ python_version }}" - scenario: - - Load website - - Pause -{% endblock %}{% endraw %} diff --git a/{{cookiecutter.repo_name}}/tests/system.packages b/{{cookiecutter.repo_name}}/tests/system.packages new file mode 100644 index 000000000..cd0c06880 --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/system.packages @@ -0,0 +1,11 @@ +firefox +libpq-dev +llvm +graphviz-dev +libtiff-dev +libjpeg8-dev +libfreetype6-dev +liblcms-dev +libwebp-dev +zlib1g-dev +gettext diff --git a/{{cookiecutter.repo_name}}/tests/tdd.settings b/{{cookiecutter.repo_name}}/tests/tdd.settings new file mode 100644 index 000000000..0d88ededc --- /dev/null +++ b/{{cookiecutter.repo_name}}/tests/tdd.settings @@ -0,0 +1,9 @@ +# Test driven development settings +# +# Run with : hitch test . --settings tdd.settings +# +# Tests stop on first failure, pause and launch into IPython for debugging/interaction. + +failfast: true +pause_on_failure: true +pause_on_success: true \ No newline at end of file diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/__init__.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/__init__.py index 40a96afc6..4985ac95d 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/__init__.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/__init__.py @@ -1 +1,6 @@ +""" +To understand why this file is here, please read: + +http://cookiecutter-django.readthedocs.org/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django +""" # -*- coding: utf-8 -*- diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/__init__.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/__init__.py index 40a96afc6..4985ac95d 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/__init__.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/__init__.py @@ -1 +1,6 @@ +""" +To understand why this file is here, please read: + +http://cookiecutter-django.readthedocs.org/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django +""" # -*- coding: utf-8 -*- diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/migrations/0002_set_site_domain_and_name.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/migrations/0002_set_site_domain_and_name.py index db6b6a28d..61a6f367a 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/migrations/0002_set_site_domain_and_name.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/migrations/0002_set_site_domain_and_name.py @@ -1,4 +1,10 @@ +""" +To understand why this file is here, please read: + +http://cookiecutter-django.readthedocs.org/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django +""" # -*- coding: utf-8 -*- + from __future__ import unicode_literals from django.conf import settings diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/migrations/__init__.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/migrations/__init__.py index 40a96afc6..4985ac95d 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/migrations/__init__.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/contrib/sites/migrations/__init__.py @@ -1 +1,6 @@ +""" +To understand why this file is here, please read: + +http://cookiecutter-django.readthedocs.org/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django +""" # -*- coding: utf-8 -*- diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/css/project.css b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/css/project.css index 4162d5de5..5f23c427a 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/css/project.css +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/css/project.css @@ -1,34 +1,38 @@ -/* line 5, ../sass/project.scss */ +/* These styles are generated from project.scss. */ + .alert-debug { color: black; background-color: white; border-color: #d6e9c6; } -/* line 11, ../sass/project.scss */ -.alert-info { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -/* line 17, ../sass/project.scss */ -.alert-success { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -/* line 23, ../sass/project.scss */ -.alert-warning { - color: black; - background-color: orange; - border-color: #d6e9c6; -} - -/* line 29, ../sass/project.scss */ .alert-error { color: #b94a48; background-color: #f2dede; border-color: #eed3d7; } + +/* This is a fix for the bootstrap4 alpha release */ +@media (max-width: 47.9em) { + .navbar-nav .nav-item { + float: none; + width: 100%; + display: inline-block; + } + + .navbar-nav .nav-item + .nav-item { + margin-left: 0; + } + + .nav.navbar-nav.pull-xs-right { + float: none !important; + } +} + +/* Display django-debug-toolbar. + See https://github.com/django-debug-toolbar/django-debug-toolbar/issues/742 + and https://github.com/pydanny/cookiecutter-django/issues/317 +*/ +[hidden][style="display: block;"] { + display: block !important; +} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/images/.gitkeep b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/images/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/sass/project.scss b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/sass/project.scss index 99554ec2b..e737d593f 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/sass/project.scss +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/sass/project.scss @@ -1,33 +1,51 @@ // project specific CSS goes here +// Alert colors + +$white: #fff; +$mint-green: #d6e9c6; +$black: #000; +$pink: #f2dede; +$dark-pink: #eed3d7; +$red: #b94a48; + // bootstrap alert CSS, translated to the django-standard levels of // debug, info, success, warning, error + .alert-debug { - color: black; - background-color: white; - border-color: #d6e9c6; -} - -.alert-info { - color: #3a87ad; - background-color: #d9edf7; - border-color: #bce8f1; -} - -.alert-success { - color: #468847; - background-color: #dff0d8; - border-color: #d6e9c6; -} - -.alert-warning { - color: black; - background-color: orange; - border-color: #d6e9c6; + background-color: $white; + border-color: $mint-green; + color: $black; } .alert-error { - color: #b94a48; - background-color: #f2dede; - border-color: #eed3d7; + background-color: $pink; + border-color: $dark-pink; + color: $red; +} + +// This is a fix for the bootstrap4 alpha release + +@media (max-width: 47.9em) { + .navbar-nav .nav-item { + display: inline-block; + float: none; + width: 100%; + } + + .navbar-nav .nav-item + .nav-item { + margin-left: 0; + } + + .nav.navbar-nav.pull-xs-right { + float: none !important; + } +} + +// Display django-debug-toolbar. +// See https://github.com/django-debug-toolbar/django-debug-toolbar/issues/742 +// and https://github.com/pydanny/cookiecutter-django/issues/317 + +[hidden][style="display: block;"] { + display: block !important; } diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/taskapp/celery.py b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/taskapp/celery.py index 33cda5c62..7a089dbd7 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/taskapp/celery.py +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/taskapp/celery.py @@ -5,9 +5,10 @@ from celery import Celery from django.apps import 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") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") # pragma: no cover app = Celery('{{cookiecutter.repo_name}}') @@ -23,11 +24,39 @@ class CeleryConfig(AppConfig): app.config_from_object('django.conf:settings') app.autodiscover_tasks(lambda: settings.INSTALLED_APPS, force=True) + {% if cookiecutter.use_sentry == "y" -%} + if hasattr(settings, 'RAVEN_CONFIG'): + # Celery signal registration + from raven import Client as RavenClient + from raven.contrib.celery import register_signal as raven_register_signal + from raven.contrib.celery import register_logger_signal as raven_register_logger_signal + + raven_client = RavenClient(dsn=settings.RAVEN_CONFIG['DSN']) + raven_register_logger_signal(raven_client) + raven_register_signal(raven_client) + {%- endif %} + + {% if cookiecutter.use_opbeat == "y" -%} + if hasattr(settings, 'OPBEAT'): + from opbeat.contrib.django.models import client as opbeat_client + from opbeat.contrib.django.models import logger as opbeat_logger + from opbeat.contrib.django.models import register_handlers as opbeat_register_handlers + from opbeat.contrib.celery import register_signal as opbeat_register_signal + + try: + opbeat_register_signal(opbeat_client) + except Exception as e: + opbeat_logger.exception('Failed installing celery hook: %s' % e) + + if 'opbeat.contrib.django' in settings.INSTALLED_APPS: + opbeat_register_handlers() + {%- endif %} + @app.task(bind=True) def debug_task(self): - print('Request: {0!r}'.format(self.request)) + print('Request: {0!r}'.format(self.request)) # 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 %} \ No newline at end of file +{% endif -%} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/email.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/email.html index 2dab7e931..e757f21f3 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/email.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/email.html @@ -1,7 +1,6 @@ {% raw %}{% extends "account/base.html" %} {% load i18n %} -{% load url from future %} {% load crispy_forms_tags %} {% block head_title %}{% trans "Account" %}{% endblock %} @@ -13,7 +12,7 @@

{% trans "E-mail Addresses" %}

{% if user.emailaddress_set.all %}

{% trans 'The following e-mail addresses are associated with your account:' %}

- + - {% else %} + {% else %}

{% trans 'Warning:'%} {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}

{% endif %}

{% trans "Add E-mail Address" %}

- +
{% csrf_token %} {{ form|crispy }} @@ -78,4 +77,4 @@ })(); {% endblock %} -{% endraw %} \ No newline at end of file +{% endraw %} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/email_confirm.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/email_confirm.html index b0ab775d6..d7886239a 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/email_confirm.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/email_confirm.html @@ -1,6 +1,5 @@ {% raw %}{% extends "account/base.html" %} -{% load url from future %} {% load i18n %} {% load account %} @@ -16,12 +15,12 @@ {% if confirmation %} {% user_display confirmation.email_address.user as user_display %} - +

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

{% csrf_token %} - +
{% else %} @@ -35,4 +34,4 @@ {% endblock %} -{% endraw %} \ No newline at end of file +{% endraw %} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/login.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/login.html index 19b7a8976..acbc50c21 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/login.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/login.html @@ -2,7 +2,7 @@ {% load i18n %} {% load account %} -{% load url from future %} +{% load socialaccount %} {% load crispy_forms_tags %} {% block head_title %}{% trans "Sign In" %}{% endblock %} @@ -12,8 +12,8 @@

{% trans "Sign In" %}

- - {% if socialaccount.providers %} + {% get_providers as socialaccount_providers %} + {% if socialaccount_providers %}

{% blocktrans with site.name as site_name %}Please sign in with one of your existing third party accounts. Or, sign up for a {{ site_name }} account and sign in below:{% endblocktrans %}

@@ -38,7 +38,7 @@ {% if redirect_field_value %} {% endif %} - + {% trans "Forgot Password?" %}
diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/logout.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/logout.html index cf21b87e6..039dc7be9 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/logout.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/logout.html @@ -1,6 +1,5 @@ {% raw %}{% extends "account/base.html" %} -{% load url from future %} {% load i18n %} {% block head_title %}{% trans "Sign Out" %}{% endblock %} @@ -26,4 +25,4 @@
{% endblock %} -{% endraw %} \ No newline at end of file +{% endraw %} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/password_reset_from_key.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/password_reset_from_key.html index 603c25de7..8e1b39e26 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/password_reset_from_key.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/password_reset_from_key.html @@ -1,6 +1,5 @@ {% raw %}{% extends "account/base.html" %} -{% load url from future %} {% load i18n %} {% load crispy_forms_tags %} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/password_reset_from_key_done.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/password_reset_from_key_done.html index 9fb922e88..015028e4a 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/password_reset_from_key_done.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/password_reset_from_key_done.html @@ -1,6 +1,5 @@ {% raw %}{% extends "account/base.html" %} -{% load url from future %} {% load i18n %} {% block head_title %}{% trans "Change Password" %}{% endblock %} @@ -14,4 +13,4 @@ {% endblock %} -{% endraw %} \ No newline at end of file +{% endraw %} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/signup.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/signup.html index 7fb21cf06..63e57a5fc 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/signup.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/signup.html @@ -1,6 +1,5 @@ {% raw %}{% extends "account/base.html" %} -{% load url from future %} {% load i18n %} {% load crispy_forms_tags %} @@ -21,7 +20,7 @@ {% if redirect_field_value %} {% endif %} - + diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/signup_closed.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/signup_closed.html index 5de5361db..f5976dad5 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/signup_closed.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/signup_closed.html @@ -1,6 +1,5 @@ {% raw %}{% extends "account/base.html" %} -{% load url from future %} {% load i18n %} {% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} @@ -16,4 +15,4 @@ {% endblock %} -{% endraw %} \ No newline at end of file +{% endraw %} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/verified_email_required.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/verified_email_required.html index f6355fb63..7d071c6a6 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/verified_email_required.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/account/verified_email_required.html @@ -1,6 +1,5 @@ {% raw %}{% extends "account/base.html" %} -{% load url from future %} {% load i18n %} {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} @@ -26,4 +25,4 @@ {% endblock %} -{% endraw %} \ No newline at end of file +{% endraw %} diff --git a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/base.html b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/base.html index f4732389e..9c0324c57 100644 --- a/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/base.html +++ b/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/base.html @@ -15,10 +15,7 @@ {% block css %} - - - - + @@ -27,51 +24,59 @@ {% endblock %} {% block angular %} - + {% endblock %} -