diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 509fb83ae..dc41463f8 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -57,7 +57,9 @@ Listed in alphabetical order. Andrew Mikhnevich `@zcho`_ Andy Rose Anna Callahan `@jazztpt`_ + Anna Sidwell `@takkaria`_ Antonia Blair `@antoniablair`_ @antoniablairart + Anuj Bansal `@ahhda`_ Arcuri Davide `@dadokkio`_ Areski Belaid `@areski`_ Ashley Camba @@ -69,7 +71,9 @@ Listed in alphabetical order. Bo Lopker `@blopker`_ Bouke Haarsma Brent Payne `@brentpayne`_ @brentpayne + Bartek `@btknu` Burhan Khalid            `@burhan`_                   @burhan + Carl Johnson `@carlmjohnson`_ @carlmjohnson Catherine Devlin `@catherinedevlin`_ Cédric Gaspoz `@cgaspoz`_ Charlie Smith `@chuckus`_ @@ -78,6 +82,7 @@ Listed in alphabetical order. Chris Franklin `@hairychris`_ Chris Pappalardo `@ChrisPappalardo`_ Christopher Clarke `@chrisdev`_ + Cole Mackenzie `@cmackenzie1`_ Collederas `@Collederas`_ Cristian Vargas `@cdvv7788`_ Cullen Rhodes `@c-rhodes`_ @@ -121,6 +126,7 @@ Listed in alphabetical order. Krzysztof Szumny `@noisy`_ Krzysztof Żuraw `@krzysztofzuraw`_ Leonardo Jimenez `@xpostudio4`_ + Leo Zhou `@glasslion`_ Lin Xianyi `@iynaix`_ Luis Nell `@originell`_ Lukas Klein @@ -142,6 +148,7 @@ Listed in alphabetical order. Pablo `@oubiga`_ Parbhat Puri `@parbhat`_ Peter Bittner `@bittner`_ + Peter Coles `@mrcoles`_ Pierre Chiquet `@pchiquet`_ Raphael Pierzina `@hackebrot`_ Raony Guimarães Corrêa `@raonyguimaraes`_ @@ -164,6 +171,7 @@ Listed in alphabetical order. Tom Atkins `@knitatoms`_ Tom Offermann Travis McNeill `@Travistock`_ @tavistock_esq + Tubo Shi `@Tubo`_ Umair Ashraf `@umrashrf`_ @fabumair Vitaly Babiy Vivian Guillen `@viviangb`_ @@ -171,6 +179,9 @@ Listed in alphabetical order. William Archinal `@archinal`_ Yaroslav Halchenko Denis Bobrov `@delneg`_ + Philipp Matthies `@canonnervio`_ + Vadim Iskuchekov `@Egregors`_ @egregors + Keith Bailey `@keithjeb`_ ========================== ============================ ============== .. _@a7p: https://github.com/a7p @@ -193,6 +204,7 @@ Listed in alphabetical order. .. _@burhan: https://github.com/burhan .. _@c-rhodes: https://github.com/c-rhodes .. _@caffodian: https://github.com/caffodian +.. _@carlmjohnson: https://github.com/carlmjohnson .. _@catherinedevlin: https://github.com/catherinedevlin .. _@ccurvey: https://github.com/ccurvey .. _@cdvv7788: https://github.com/cdvv7788 @@ -200,6 +212,7 @@ Listed in alphabetical order. .. _@chrisdev: https://github.com/chrisdev .. _@ChrisPappalardo: https://github.com/ChrisPappalardo .. _@chuckus: https://github.com/chuckus +.. _@cmackenzie1: https://github.com/cmackenzie1 .. _@Collederas: https://github.com/Collederas .. _@davitovmasyan: https://github.com/davitovmasyan .. _@ddiazpinto: https://github.com/ddiazpinto @@ -217,6 +230,7 @@ Listed in alphabetical order. .. _@garry-cairns: https://github.com/garry-cairns .. _@garrypolley: https://github.com/garrypolley .. _@goldhand: https://github.com/goldhand +.. _@glasslion: https://github.com/glasslion .. _@hackebrot: https://github.com/hackebrot .. _@hairychris: https://github.com/hairychris .. _@hendrikschneider: https://github.com/hendrikschneider @@ -260,9 +274,11 @@ Listed in alphabetical order. .. _@ssteinerX: https://github.com/ssteinerx .. _@stepmr: https://github.com/stepmr .. _@suledev: https://github.com/suledev +.. _@takkaria: https://github.com/takkaria .. _@timfreund: https://github.com/timfreund .. _@Travistock: https://github.com/Tavistock .. _@trungdong: https://github.com/trungdong +.. _@Tubo: https://github.com/tubo .. _@viviangb: https://github.com/viviangb .. _@xpostudio4: https://github.com/xpostudio4 .. _@yunti: https://github.com/yunti @@ -282,6 +298,9 @@ Listed in alphabetical order. .. _@delneg: https://github.com/delneg .. _@purplediane: https://github.com/purplediane .. _@umrashrf: https://github.com/umrashrf +.. _@ahhda: https://github.com/ahhda +.. _@keithjeb: https://github.com/keithjeb +.. _@btknu: https://github.com/btknu Special Thanks ~~~~~~~~~~~~~~ diff --git a/cookiecutter.json b/cookiecutter.json index 9aff23f22..b5dda0c70 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -1,6 +1,6 @@ { "project_name": "My Awesome Project", - "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_') }}", + "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}", "description": "Behold My Awesome Project!", "author_name": "Daniel Roy Greenfeld", "domain_name": "example.com", diff --git a/docs/deployment-on-heroku.rst b/docs/deployment-on-heroku.rst index d7d95184d..5acb44faa 100644 --- a/docs/deployment-on-heroku.rst +++ b/docs/deployment-on-heroku.rst @@ -10,6 +10,8 @@ Run these commands to deploy the project to Heroku: heroku create --buildpack https://github.com/heroku/heroku-buildpack-python heroku addons:create heroku-postgresql:hobby-dev + # On Windows use double quotes for the time zone, e.g. + # heroku pg:backups schedule --at "02:00 America/Los_Angeles" DATABASE_URL heroku pg:backups schedule --at '02:00 America/Los_Angeles' DATABASE_URL heroku pg:promote DATABASE_URL @@ -45,7 +47,6 @@ Run these commands to deploy the project to Heroku: git push heroku master - heroku run python manage.py migrate heroku run python manage.py createsuperuser heroku run python manage.py collectstatic --no-input diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst index b21668241..f6e21e828 100644 --- a/docs/deployment-with-docker.rst +++ b/docs/deployment-with-docker.rst @@ -59,7 +59,7 @@ SSL (Secure Sockets Layer) is a standard security technology for establishing an It is always better to deploy a site behind HTTPS and will become crucial as the web services extend to the IoT (Internet of Things). For this reason, we have set up a number of security defaults to help make your website secure: -* If you are not using a subdomain of the domain name set in the project, then remember to put the your staging/production IP address in the ``DJANGO_ALLOWED_HOSTS`` environment variable (see :ref:`settings`) before you deploy your website. Failure to do this will mean you will not have access to your website through the HTTP protocol. +* If you are not using a subdomain of the domain name set in the project, then remember to put your staging/production IP address in the ``DJANGO_ALLOWED_HOSTS`` environment variable (see :ref:`settings`) before you deploy your website. Failure to do this will mean you will not have access to your website through the HTTP protocol. * Access to the Django admin is set up by default to require HTTPS in production or once *live*. diff --git a/docs/developing-locally-docker.rst b/docs/developing-locally-docker.rst index 08b25f3bc..895140f9c 100644 --- a/docs/developing-locally-docker.rst +++ b/docs/developing-locally-docker.rst @@ -171,6 +171,16 @@ When developing locally you can go with MailHog_ for email testing provided ``us .. _Mailhog: https://github.com/mailhog/MailHog/ +.. _`CeleryTasks`: + +Celery tasks in local development +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +When not using docker Celery tasks are set to run in Eager mode, so that a full stack is not needed. When using docker the task +scheduler will be used by default. + +If you need tasks to be executed on the main thread during development set CELERY_TASK_ALWAYS_EAGER = True in config/settings/local.py. + +Possible uses could be for testing, or ease of profiling with DJDT. .. _`CeleryFlower`: diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst index 7d5591ac6..3434f68b6 100644 --- a/docs/developing-locally.rst +++ b/docs/developing-locally.rst @@ -29,7 +29,7 @@ First things first. #. Create a new PostgreSQL database using createdb_: :: - $ createdb + $ createdb -U postgres --password .. note:: @@ -118,6 +118,16 @@ In production, we have Mailgun_ configured to have your back! .. _Mailgun: https://www.mailgun.com/ +Celery +------ +If the project is configured to use Celery as a task scheduler then by default tasks are set to run on the main thread +when developing locally. If you have the appropriate setup on your local machine then set + +CELERY_TASK_ALWAYS_EAGER = False + +in /config/settings/local.py + + Sass Compilation & Live Reloading --------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 856b931f4..5cb07b4b0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,7 @@ Contents: developing-locally-docker settings linters + testing deployment-on-pythonanywhere deployment-on-heroku deployment-with-docker diff --git a/docs/testing.rst b/docs/testing.rst new file mode 100644 index 000000000..6ca213883 --- /dev/null +++ b/docs/testing.rst @@ -0,0 +1,56 @@ +.. _testing: + +Testing +======== + +We encourage users to build application tests. As best practice, this should be done immediately after documentation of the application being built, before starting on any coding. + +Pytest +------ + +This project uses the Pytest_, a framework for easily building simple and scalable tests. +After you have set up to `develop locally`_, run the following commands to make sure the testing environment is ready: :: + + $ pytest + +You will get a readout of the `users` app that has already been set up with tests. If you do not want to run the `pytest` on the entire project, you can target a particular app by typing in its location: :: + + $ pytest + +If you set up your project to `develop locally with docker`_, run the following command: :: + + $ docker-compose -f local.yml run django pytest + +Targetting particular apps for testing in ``docker`` follows a similar pattern as previously shown above. + +Coverage +-------- + +You should build your tests to provide the highest level of **code coverage**. You can run the ``pytest`` with code ``coverage`` by typing in the following command: :: + + $ docker-compose -f local.yml run django coverage run -m pytest + +Once the tests are complete, in order to see the code coverage, run the following command: :: + + $ docker-compose -f local.yml run django coverage report + +.. note:: + + At the root of the project folder, you will find the `pytest.ini` file. You can use this to customize_ the ``pytest`` to your liking. + + There is also the `.coveragerc`. This is the configuration file for the ``coverage`` tool. You can find out more about `configuring`_ ``coverage``. + +.. seealso:: + + For unit tests, run: :: + + $ python manage.py test + + Since this is a fresh install, and there are no tests built using the Python `unittest`_ library yet, you should get feedback that says there were no tests carried out. + +.. _Pytest: https://docs.pytest.org/en/latest/example/simple.html +.. _develop locally: ../developing-locally.rst +.. _develop locally with docker: ..../developing-locally-docker.rst +.. _customize: https://docs.pytest.org/en/latest/customize.html +.. _unittest: https://docs.python.org/3/library/unittest.html#module-unittest +.. _configuring: https://coverage.readthedocs.io/en/v4.5.x/config.html \ No newline at end of file diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index 0c827acf4..d0c0ba434 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -9,4 +9,7 @@ This page contains some advice about errors and problems commonly encountered du #. Internal server error on user registration: make sure you have configured the mail backend (e.g. Mailgun) by adding the API key and sender domain +#. New apps not getting created in project root: This is the expected behavior, because cookiecutter-django does not change the way that django startapp works, you'll have to fix this manually (see `#1725`_) + .. _#528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373 +.. _#1725: https://github.com/pydanny/cookiecutter-django/issues/1725#issuecomment-407493176 diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index b43b08a5f..45435dd02 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -71,6 +71,9 @@ def remove_utility_files(): def remove_heroku_files(): file_names = ["Procfile", "runtime.txt", "requirements.txt"] for file_name in file_names: + if file_name == "requirements.txt" and "{{ cookiecutter.use_travisci }}".lower() == "y": + # don't remove the file if we are using travisci but not using heroku + continue os.remove(file_name) diff --git a/requirements.txt b/requirements.txt index b62ff07df..37a96913c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,11 @@ binaryornot==0.4.4 # Code quality # ------------------------------------------------------------------------------ -flake8==3.6.0 +flake8==3.7.6 # Testing # ------------------------------------------------------------------------------ -tox==3.5.3 -pytest==3.9.3 +tox==3.6.1 +pytest==4.3.0 pytest-cookies==0.3.0 +pyyaml==3.13 diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index cf173576b..b2c235a82 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -1,6 +1,7 @@ import os import re import sh +import yaml import pytest from binaryornot.check import is_binary @@ -85,3 +86,19 @@ def test_flake8_compliance(cookies): sh.flake8(str(result.project)) except sh.ErrorReturnCode as e: pytest.fail(e) + + +def test_travis_invokes_pytest(cookies, context): + context.update({"use_travisci": "y"}) + result = cookies.bake(extra_context=context) + + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == context["project_slug"] + assert result.project.isdir() + + with open(f'{result.project}/.travis.yml', 'r') as travis_yml: + try: + assert yaml.load(travis_yml)['script'] == ['pytest'] + except yaml.YAMLError as e: + pytest.fail(e) diff --git a/{{cookiecutter.project_slug}}/.envs/.local/.django b/{{cookiecutter.project_slug}}/.envs/.local/.django index 2ed5fbf3d..919f31185 100644 --- a/{{cookiecutter.project_slug}}/.envs/.local/.django +++ b/{{cookiecutter.project_slug}}/.envs/.local/.django @@ -3,10 +3,11 @@ USE_DOCKER=yes IPYTHONDIR=/app/.ipython +{%- if cookiecutter.use_celery == 'y' %} # Redis # ------------------------------------------------------------------------------ REDIS_URL=redis://redis:6379/0 -{% if cookiecutter.use_celery == 'y' %} + # Celery # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/.travis.yml b/{{cookiecutter.project_slug}}/.travis.yml index 5ca54d000..3072f75f3 100644 --- a/{{cookiecutter.project_slug}}/.travis.yml +++ b/{{cookiecutter.project_slug}}/.travis.yml @@ -9,3 +9,7 @@ before_install: language: python python: - "3.6" +install: + - pip install -r requirements/local.txt +script: + - "pytest" diff --git a/{{cookiecutter.project_slug}}/Procfile b/{{cookiecutter.project_slug}}/Procfile index c77d76d96..d9319f5cc 100644 --- a/{{cookiecutter.project_slug}}/Procfile +++ b/{{cookiecutter.project_slug}}/Procfile @@ -1,3 +1,4 @@ +release: python manage.py migrate web: gunicorn config.wsgi:application {% if cookiecutter.use_celery == "y" -%} worker: celery worker --app={{cookiecutter.project_slug}}.taskapp --loglevel=info diff --git a/{{cookiecutter.project_slug}}/compose/production/django/entrypoint b/{{cookiecutter.project_slug}}/compose/production/django/entrypoint index 4845e343b..0a76b3107 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/entrypoint +++ b/{{cookiecutter.project_slug}}/compose/production/django/entrypoint @@ -5,8 +5,10 @@ set -o pipefail set -o nounset +{% if cookiecutter.use_celery == 'y' %} # N.B. If only .env files supported variable expansion... export CELERY_BROKER_URL="${REDIS_URL}" +{% endif %} if [ -z "${POSTGRES_USER}" ]; then base_postgres_image_default_user='postgres' diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index e4ab28844..950b9ed7f 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -209,6 +209,17 @@ FIXTURE_DIRS = ( str(APPS_DIR.path('fixtures')), ) +# SECURITY +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly +SESSION_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly +CSRF_COOKIE_HTTPONLY = True +# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter +SECURE_BROWSER_XSS_FILTER = True +# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options +X_FRAME_OPTIONS = 'DENY' + # EMAIL # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend diff --git a/{{cookiecutter.project_slug}}/config/settings/local.py b/{{cookiecutter.project_slug}}/config/settings/local.py index 741f324af..6667a2653 100644 --- a/{{cookiecutter.project_slug}}/config/settings/local.py +++ b/{{cookiecutter.project_slug}}/config/settings/local.py @@ -76,8 +76,10 @@ INSTALLED_APPS += ['django_extensions'] # noqa F405 # Celery # ------------------------------------------------------------------------------ +{% if cookiecutter.use_docker == 'n' -%} # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-always-eager CELERY_TASK_ALWAYS_EAGER = True +{%- endif %} # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-eager-propagates CELERY_TASK_EAGER_PROPAGATES = True diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index 87753b552..e77d4304c 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -41,12 +41,8 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') SECURE_SSL_REDIRECT = env.bool('DJANGO_SECURE_SSL_REDIRECT', default=True) # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure SESSION_COOKIE_SECURE = True -# https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly -SESSION_COOKIE_HTTPONLY = True # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure CSRF_COOKIE_SECURE = True -# https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly -CSRF_COOKIE_HTTPONLY = True # https://docs.djangoproject.com/en/dev/topics/security/#ssl-https # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds # TODO: set this to 60 seconds first and then to 518400 once you prove the former works @@ -57,10 +53,6 @@ SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool('DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS SECURE_HSTS_PRELOAD = env.bool('DJANGO_SECURE_HSTS_PRELOAD', default=True) # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff SECURE_CONTENT_TYPE_NOSNIFF = env.bool('DJANGO_SECURE_CONTENT_TYPE_NOSNIFF', default=True) -# https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter -SECURE_BROWSER_XSS_FILTER = True -# https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options -X_FRAME_OPTIONS = 'DENY' # STORAGES # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/gulpfile.js b/{{cookiecutter.project_slug}}/gulpfile.js index bb18a535d..d92678ed2 100644 --- a/{{cookiecutter.project_slug}}/gulpfile.js +++ b/{{cookiecutter.project_slug}}/gulpfile.js @@ -1,63 +1,70 @@ +//////////////////////////////// +// Setup +//////////////////////////////// -//////////////////////////////// - //Setup// -//////////////////////////////// +// Gulp and package +const { src, dest, parallel, series, watch } = require('gulp') +const pjson = require('./package.json') // Plugins -var gulp = require('gulp'), - pjson = require('./package.json'), - gutil = require('gulp-util'), - sass = require('gulp-sass'), - autoprefixer = require('gulp-autoprefixer'), - cssnano = require('gulp-cssnano'), - {% if cookiecutter.custom_bootstrap_compilation == 'y' %} - concat = require('gulp-concat'), - {% endif %} - rename = require('gulp-rename'), - del = require('del'), - plumber = require('gulp-plumber'), - pixrem = require('gulp-pixrem'), - uglify = require('gulp-uglify'), - imagemin = require('gulp-imagemin'), - spawn = require('child_process').spawn, - runSequence = require('run-sequence'), - browserSync = require('browser-sync').create(), - reload = browserSync.reload; - +const autoprefixer = require('autoprefixer') +const browserSync = require('browser-sync').create() +{% if cookiecutter.custom_bootstrap_compilation == 'y' %} +const concat = require('gulp-concat') +{% endif %} +const cssnano = require ('cssnano') +const imagemin = require('gulp-imagemin') +const pixrem = require('pixrem') +const plumber = require('gulp-plumber') +const postcss = require('gulp-postcss') +const reload = browserSync.reload +const rename = require('gulp-rename') +const sass = require('gulp-sass') +const spawn = require('child_process').spawn +const uglify = require('gulp-uglify-es').default // Relative paths function -var pathsConfig = function (appName) { - this.app = "./" + (appName || pjson.name); - var vendorsRoot = 'node_modules/'; +function pathsConfig(appName) { + this.app = `./${pjson.name}` + const vendorsRoot = 'node_modules' return { {% if cookiecutter.custom_bootstrap_compilation == 'y' %} - bootstrapSass: vendorsRoot + '/bootstrap/scss', + bootstrapSass: `${vendorsRoot}/bootstrap/scss`, vendorsJs: [ - vendorsRoot + 'jquery/dist/jquery.slim.js', - vendorsRoot + 'popper.js/dist/umd/popper.js', - vendorsRoot + 'bootstrap/dist/js/bootstrap.js' + `${vendorsRoot}/jquery/dist/jquery.slim.js`, + `${vendorsRoot}/popper.js/dist/umd/popper.js`, + `${vendorsRoot}/bootstrap/dist/js/bootstrap.js`, ], {% endif %} app: this.app, - templates: this.app + '/templates', - css: this.app + '/static/css', - sass: this.app + '/static/sass', - fonts: this.app + '/static/fonts', - images: this.app + '/static/images', - js: this.app + '/static/js' + templates: `${this.app}/templates`, + css: `${this.app}/static/css`, + sass: `${this.app}/static/sass`, + fonts: `${this.app}/static/fonts`, + images: `${this.app}/static/images`, + js: `${this.app}/static/js`, } -}; +} -var paths = pathsConfig(); +var paths = pathsConfig() //////////////////////////////// - //Tasks// +// Tasks //////////////////////////////// // Styles autoprefixing and minification -gulp.task('styles', function() { - return gulp.src(paths.sass + '/project.scss') +function styles() { + var processCss = [ + autoprefixer(), // adds vendor prefixes + pixrem(), // add fallbacks for rem units + ] + + var minifyCss = [ + cssnano({ preset: 'default' }) // minify result + ] + + return src(`${paths.sass}/project.scss`) .pipe(sass({ includePaths: [ {% if cookiecutter.custom_bootstrap_compilation == 'y' %} @@ -67,72 +74,86 @@ gulp.task('styles', function() { ] }).on('error', sass.logError)) .pipe(plumber()) // Checks for errors - .pipe(autoprefixer({browsers: ['last 2 versions']})) // Adds vendor prefixes - .pipe(pixrem()) // add fallbacks for rem units - .pipe(gulp.dest(paths.css)) + .pipe(postcss(processCss)) + .pipe(dest(paths.css)) .pipe(rename({ suffix: '.min' })) - .pipe(cssnano()) // Minifies the result - .pipe(gulp.dest(paths.css)); -}); + .pipe(postcss(minifyCss)) // Minifies the result + .pipe(dest(paths.css)) +} // Javascript minification -gulp.task('scripts', function() { - return gulp.src(paths.js + '/project.js') +function scripts() { + return src(`${paths.js}/project.js`) .pipe(plumber()) // Checks for errors .pipe(uglify()) // Minifies the js .pipe(rename({ suffix: '.min' })) - .pipe(gulp.dest(paths.js)); -}); - + .pipe(dest(paths.js)) +} {% if cookiecutter.custom_bootstrap_compilation == 'y' %} // Vendor Javascript minification -gulp.task('vendor-scripts', function() { - return gulp.src(paths.vendorsJs) +function vendorScripts() { + return src(paths.vendorsJs) .pipe(concat('vendors.js')) - .pipe(gulp.dest(paths.js)) + .pipe(dest(paths.js)) .pipe(plumber()) // Checks for errors .pipe(uglify()) // Minifies the js .pipe(rename({ suffix: '.min' })) - .pipe(gulp.dest(paths.js)); -}); + .pipe(dest(paths.js)) +} {% endif %} // Image compression -gulp.task('imgCompression', function(){ - return gulp.src(paths.images + '/*') +function imgCompression() { + return src(`${paths.images}/*`) .pipe(imagemin()) // Compresses PNG, JPEG, GIF and SVG images - .pipe(gulp.dest(paths.images)) -}); + .pipe(dest(paths.images)) +} // Run django server -gulp.task('runServer', function(cb) { - var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'}); +function runServer(cb) { + var cmd = spawn('python', ['manage.py', 'runserver'], {stdio: 'inherit'}) cmd.on('close', function(code) { - console.log('runServer exited with code ' + code); - cb(code); - }); -}); + console.log('runServer exited with code ' + code) + cb(code) + }) +} // Browser sync server for live reload -gulp.task('browserSync', function() { +function initBrowserSync() { browserSync.init( - [paths.css + "/*.css", paths.js + "*.js", paths.templates + '*.html'], { - proxy: "localhost:8000" - }); -}); + [ + `${paths.css}/*.css`, + `${paths.js}/*.js`, + `${paths.templates}/*.html` + ], { + proxy: "localhost:8000" + } + ) +} // Watch -gulp.task('watch', function() { +function watchPaths() { + watch(`${paths.sass}/*.scss`, styles) + watch(`${paths.templates}/**/*.html`).on("change", reload) + watch([`${paths.js}/*.js`, `!${paths.js}/*.min.js`], scripts).on("change", reload) +} - gulp.watch(paths.sass + '/*.scss', ['styles']); - gulp.watch(paths.js + '/*.js', ['scripts']).on("change", reload); - gulp.watch(paths.images + '/*', ['imgCompression']); - gulp.watch(paths.templates + '/**/*.html').on("change", reload); +// Generate all assets +const generateAssets = parallel( + styles, + scripts, + {% if cookiecutter.custom_bootstrap_compilation == 'y' %}vendorScripts,{% endif %} + imgCompression +) -}); +// Set up dev environment +const dev = parallel( + runServer, + initBrowserSync, + watchPaths +) -// Default task -gulp.task('default', function() { - runSequence(['styles', 'scripts', {% if cookiecutter.custom_bootstrap_compilation == 'y' %}'vendor-scripts', {% endif %}'imgCompression'], ['runServer', 'browserSync', 'watch']); -}); +exports.default = series(generateAssets, dev) +exports["generate-assets"] = generateAssets +exports["dev"] = dev diff --git a/{{cookiecutter.project_slug}}/package.json b/{{cookiecutter.project_slug}}/package.json index b29d52963..a15df941e 100644 --- a/{{cookiecutter.project_slug}}/package.json +++ b/{{cookiecutter.project_slug}}/package.json @@ -6,32 +6,29 @@ {% if cookiecutter.js_task_runner == 'Gulp' -%} {% if cookiecutter.custom_bootstrap_compilation == 'y' -%} "bootstrap": "4.1.1", - {% endif -%} - "browser-sync": "^2.14.0", - "del": "^2.2.2", - "gulp": "^3.9.1", - "gulp-autoprefixer": "^5.0.0", - {% if cookiecutter.custom_bootstrap_compilation == 'y' -%} "gulp-concat": "^2.6.1", - {% endif -%} - "gulp-cssnano": "^2.1.2", - "gulp-imagemin": "^4.1.0", - "gulp-pixrem": "^1.0.0", - "gulp-plumber": "^1.1.0", - "gulp-rename": "^1.2.2", - "gulp-sass": "^3.1.0", - "gulp-uglify": "^3.0.0", - "gulp-util": "^3.0.7", - {% if cookiecutter.custom_bootstrap_compilation == 'y' -%} "jquery": "3.3.1", "popper.js": "1.14.3", {% endif -%} - "run-sequence": "^2.1.1" + "autoprefixer": "^9.4.7", + "browser-sync": "^2.14.0", + "cssnano": "^4.1.10", + "gulp": "^4.0.0", + "gulp-imagemin": "^5.0.3", + "gulp-plumber": "^1.2.1", + "gulp-postcss": "^8.0.0", + "gulp-rename": "^1.2.2", + "gulp-sass": "^4.0.2", + "gulp-uglify-es": "^1.0.4", + "pixrem": "^5.0.0" {%- endif %} }, "engines": { - "node": ">=0.8.0" + "node": ">=8" }, + "browserslist": [ + "last 2 versions" + ], "scripts": { {% if cookiecutter.js_task_runner == 'Gulp' -%} "dev": "gulp" diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index d33801688..e43b70af6 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -1,16 +1,16 @@ -pytz==2018.7 # https://github.com/stub42/pytz -python-slugify==1.2.6 # https://github.com/un33k/python-slugify -Pillow==5.3.0 # https://github.com/python-pillow/Pillow +pytz==2018.9 # https://github.com/stub42/pytz +python-slugify==2.0.1 # https://github.com/un33k/python-slugify +Pillow==5.4.1 # https://github.com/python-pillow/Pillow {%- if cookiecutter.use_compressor == "y" %} rcssmin==1.0.6{% if cookiecutter.windows == 'y' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin {%- endif %} -argon2-cffi==18.3.0 # https://github.com/hynek/argon2_cffi +argon2-cffi==19.1.0 # https://github.com/hynek/argon2_cffi {%- if cookiecutter.use_whitenoise == 'y' %} -whitenoise==4.1 # https://github.com/evansd/whitenoise +whitenoise==4.1.2 # https://github.com/evansd/whitenoise {%- endif %} -redis>=2.10.5 # https://github.com/antirez/redis +redis>=2.10.6, < 3 # pyup: < 3 # https://github.com/antirez/redis {%- if cookiecutter.use_celery == "y" %} -celery==4.2.1 # pyup: <5.0 # https://github.com/celery/celery +celery==4.2.1 # pyup: < 5.0 # https://github.com/celery/celery {%- if cookiecutter.use_docker == 'y' %} flower==0.9.2 # https://github.com/mher/flower {%- endif %} @@ -18,7 +18,7 @@ flower==0.9.2 # https://github.com/mher/flower # Django # ------------------------------------------------------------------------------ -django==2.0.9 # pyup: < 2.1 # https://www.djangoproject.com/ +django==2.0.13 # pyup: < 2.1 # https://www.djangoproject.com/ django-environ==0.4.5 # https://github.com/joke2k/django-environ django-model-utils==3.1.2 # https://github.com/jazzband/django-model-utils django-allauth==0.38.0 # https://github.com/pennersr/django-allauth @@ -26,8 +26,8 @@ django-crispy-forms==1.7.2 # https://github.com/django-crispy-forms/django-cris {%- if cookiecutter.use_compressor == "y" %} django-compressor==2.2 # https://github.com/django-compressor/django-compressor {%- endif %} -django-redis==4.9.0 # https://github.com/niwinz/django-redis +django-redis==4.10.0 # https://github.com/niwinz/django-redis # Django REST Framework -djangorestframework==3.9.0 # https://github.com/encode/django-rest-framework +djangorestframework==3.9.1 # https://github.com/encode/django-rest-framework coreapi==2.3.3 # https://github.com/core-api/python-client diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index fb36662c1..a496135b6 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -2,29 +2,29 @@ Werkzeug==0.14.1 # https://github.com/pallets/werkzeug ipdb==0.11 # https://github.com/gotcha/ipdb -Sphinx==1.8.1 # https://github.com/sphinx-doc/sphinx +Sphinx==1.8.4 # https://github.com/sphinx-doc/sphinx {%- if cookiecutter.use_docker == 'y' %} psycopg2==2.7.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- else %} -psycopg2-binary==2.7.5 # https://github.com/psycopg/psycopg2 +psycopg2-binary==2.7.7 # https://github.com/psycopg/psycopg2 {%- endif %} # Testing # ------------------------------------------------------------------------------ -mypy==0.641 # https://github.com/python/mypy -pytest==3.9.3 # https://github.com/pytest-dev/pytest -pytest-sugar==0.9.1 # https://github.com/Frozenball/pytest-sugar +mypy==0.670 # https://github.com/python/mypy +pytest==4.2.0 # https://github.com/pytest-dev/pytest +pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar # Code quality # ------------------------------------------------------------------------------ -flake8==3.6.0 # https://github.com/PyCQA/flake8 -coverage==4.5.1 # https://github.com/nedbat/coveragepy +flake8==3.7.5 # https://github.com/PyCQA/flake8 +coverage==4.5.2 # https://github.com/nedbat/coveragepy # Django # ------------------------------------------------------------------------------ factory-boy==2.11.1 # https://github.com/FactoryBoy/factory_boy -django-debug-toolbar==1.10.1 # https://github.com/jazzband/django-debug-toolbar -django-extensions==2.1.3 # https://github.com/django-extensions/django-extensions +django-debug-toolbar==1.11 # https://github.com/jazzband/django-debug-toolbar +django-extensions==2.1.5 # https://github.com/django-extensions/django-extensions django-coverage-plugin==1.6.0 # https://github.com/nedbat/django_coverage_plugin -pytest-django==3.4.3 # https://github.com/pytest-dev/pytest-django +pytest-django==3.4.7 # https://github.com/pytest-dev/pytest-django diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index 40af972d2..34877e3fd 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -8,10 +8,10 @@ psycopg2==2.7.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 Collectfast==0.6.2 # https://github.com/antonagestam/collectfast {%- endif %} {%- if cookiecutter.use_sentry == "y" %} -raven==6.9.0 # https://github.com/getsentry/raven-python +raven==6.10.0 # https://github.com/getsentry/raven-python {%- endif %} # Django # ------------------------------------------------------------------------------ django-storages[boto3]==1.7.1 # https://github.com/jschneier/django-storages -django-anymail[mailgun]==4.3 # https://github.com/anymail/django-anymail \ No newline at end of file +django-anymail[mailgun]==5.0 # https://github.com/anymail/django-anymail \ No newline at end of file diff --git a/{{cookiecutter.project_slug}}/runtime.txt b/{{cookiecutter.project_slug}}/runtime.txt index 1935e9778..9fbd3bf0a 100644 --- a/{{cookiecutter.project_slug}}/runtime.txt +++ b/{{cookiecutter.project_slug}}/runtime.txt @@ -1 +1 @@ -python-3.6.6 +python-3.6.8 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py index b3f0a388b..570abc125 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/taskapp/celery.py @@ -11,6 +11,11 @@ if not settings.configured: app = Celery('{{cookiecutter.project_slug}}') +# Using a string here means the worker will not have to +# pickle the object when using Windows. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object('django.conf:settings', namespace='CELERY') class CeleryAppConfig(AppConfig): @@ -18,11 +23,6 @@ class CeleryAppConfig(AppConfig): verbose_name = 'Celery Config' def ready(self): - # Using a string here means the worker will not have to - # pickle the object when using Windows. - # - namespace='CELERY' means all celery-related configuration keys - # should have a `CELERY_` prefix. - app.config_from_object('django.conf:settings', namespace='CELERY') installed_apps = [app_config.name for app_config in apps.get_app_configs()] app.autodiscover_tasks(lambda: installed_apps, force=True) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html index 6865c929d..4470e955a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -36,7 +36,7 @@ -
+