diff --git a/.travis.yml b/.travis.yml index 65b2a28d1..a74090b28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,9 +13,9 @@ before_install: matrix: include: - - name: Test + - name: Tox Test script: tox -e py36 - - name: Black + - name: Black template script: tox -e black - name: Basic Docker script: sh tests/test_docker.sh diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index b7607562a..154fa1ac5 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -106,6 +106,7 @@ Listed in alphabetical order. Garry Cairns `@garry-cairns`_ Garry Polley `@garrypolley`_ Hamish Durkin `@durkode`_ + Hana Quadara `@hanaquadara`_ Harry Percival `@hjwp`_ Hendrik Schneider `@hendrikschneider`_ Henrique G. G. Pereira `@ikkebr`_ @@ -311,6 +312,7 @@ Listed in alphabetical order. .. _@saschalalala: https://github.com/saschalalala .. _@mrcoles: https://github.com/mrcoles .. _@ericgroom: https://github.com/ericgroom +.. _@hanaquadara: https://github.com/hanaquadara Special Thanks ~~~~~~~~~~~~~~ diff --git a/docs/deployment-on-heroku.rst b/docs/deployment-on-heroku.rst index 09953cf88..8cc3a6b56 100644 --- a/docs/deployment-on-heroku.rst +++ b/docs/deployment-on-heroku.rst @@ -17,11 +17,8 @@ Run these commands to deploy the project to Heroku: heroku addons:create heroku-redis:hobby-dev - # If using mailgun: heroku addons:create mailgun:starter - heroku addons:create sentry:f1 - heroku config:set PYTHONHASHSEED=random heroku config:set WEB_CONCURRENCY=4 @@ -52,3 +49,70 @@ Run these commands to deploy the project to Heroku: heroku run python manage.py check --deploy heroku open + + +.. warning:: + + .. include:: mailgun.rst + + +Optional actions +---------------- + +Celery +++++++ + +Celery requires a few extra environment variables to be ready operational. Also, the worker is created, +it's in the ``Procfile``, but is turned off by default: + +.. code-block:: bash + + # Set the broker URL to Redis + heroku config:set CELERY_BROKER_URL=`heroku config:get REDIS_URL` + # Scale dyno to 1 instance + heroku ps:scale worker=1 + +Sentry +++++++ + +If you're opted for Sentry error tracking, you can either install it through the `Sentry add-on`_: + +.. code-block:: bash + + heroku addons:create sentry:f1 + + +Or add the DSN for your account, if you already have one: + +.. code-block:: bash + + heroku config:set SENTRY_DSN=https://xxxx@sentry.io/12345 + +.. _Sentry add-on: https://elements.heroku.com/addons/sentry + + +Gulp & Bootstrap compilation +++++++++++++++++++++++++++++ + +If you've opted for a custom bootstrap build, you'll most likely need to setup +your app to use `multiple buildpacks`_: one for Python & one for Node.js: + +.. code-block:: bash + + heroku buildpacks:add --index 1 heroku/nodejs + +At time of writing, this should do the trick: during deployment, +the Heroku should run ``npm install`` and then ``npm build``, +which runs Gulp in cookiecutter-django. + +If things don't work, please refer to the Heroku docs. + +.. _multiple buildpacks: https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app + +About Heroku & Docker +--------------------- + +Although Heroku has some sort of `Docker support`_, it's not supported by cookiecutter-django. +We invite you to follow Heroku documentation about it. + +.. _Docker support:: https://devcenter.heroku.com/articles/build-docker-images-heroku-yml diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst index 4ab2312ea..aad549327 100644 --- a/docs/deployment-with-docker.rst +++ b/docs/deployment-with-docker.rst @@ -43,6 +43,11 @@ You will probably also need to setup the Mail backend, for example by adding a ` .. _Mailgun: https://mailgun.com +.. warning:: + + .. include:: mailgun.rst + + Optional: Use AWS IAM Role for EC2 instance ------------------------------------------- diff --git a/docs/linters.rst b/docs/linters.rst index e59ff0df7..2d6232181 100644 --- a/docs/linters.rst +++ b/docs/linters.rst @@ -25,7 +25,7 @@ This is included in flake8's checks, but you can also run it separately to see a 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. +* Use the pylint_django plugin. 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 diff --git a/docs/mailgun.rst b/docs/mailgun.rst new file mode 100644 index 000000000..1f34e3c86 --- /dev/null +++ b/docs/mailgun.rst @@ -0,0 +1,13 @@ +If your email server used to send email isn't configured properly (Mailgun by default), +attempting to send an email will cause an Internal Server Error. + +By default, django-allauth is setup to `have emails verifications mandatory`_, +which means it'll send a verification email when an unverified user tries to +log-in or when someone tries to sign-up. + +This may happen just after you've setup your Mailgun account, which is running in a +sandbox subdomain by default. Either add your email to the list of authorized recipients +or verify your domain. + + +.. _have emails verifications mandatory: https://django-allauth.readthedocs.io/en/latest/configuration.html?highlight=ACCOUNT_EMAIL_VERIFICATION diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index d0c0ba434..68db2fb08 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -11,5 +11,7 @@ This page contains some advice about errors and problems commonly encountered du #. New apps not getting created in project root: This is the expected behavior, because cookiecutter-django does not change the way that django startapp works, you'll have to fix this manually (see `#1725`_) +#. .. include:: mailgun.rst + .. _#528: https://github.com/pydanny/cookiecutter-django/issues/528#issuecomment-212650373 .. _#1725: https://github.com/pydanny/cookiecutter-django/issues/1725#issuecomment-407493176 diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index a6b834556..e7bc1e39a 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -30,9 +30,8 @@ if "{{ cookiecutter.use_docker }}".lower() == "n": python_major_version = sys.version_info[0] if python_major_version == 2: print( - WARNING + "Cookiecutter Django does not support Python 2. " - "Stability is guaranteed with Python 3.6+ only, " - "are you sure you want to proceed (y/n)? " + TERMINATOR + WARNING + "You're running cookiecutter under Python 2, but the generated " + "project requires Python 3.6+. Do you want to proceed (y/n)? " + TERMINATOR ) yes_options, no_options = frozenset(["y"]), frozenset(["n"]) while True: diff --git a/requirements.txt b/requirements.txt index 647bccaef..85c7fd03f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,11 +4,13 @@ binaryornot==0.4.4 # Code quality # ------------------------------------------------------------------------------ +black==19.3b0 flake8==3.7.6 # Testing # ------------------------------------------------------------------------------ -tox==3.6.1 -pytest==4.3.1 +tox==3.8.3 +pytest==4.4.0 +pytest_cases==1.5.1 pytest-cookies==0.3.0 pyyaml==5.1 diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index 3010d364f..36395627f 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -1,14 +1,17 @@ import os import re -import sh -import yaml import pytest +from pytest_cases import pytest_fixture_plus +import sh +import yaml from binaryornot.check import is_binary PATTERN = "{{(\s?cookiecutter)[.](.*?)}}" RE_OBJ = re.compile(PATTERN) +YN_CHOICES = ["y", "n"] + @pytest.fixture def context(): @@ -24,6 +27,35 @@ def context(): } +@pytest_fixture_plus +@pytest.mark.parametrize("windows", YN_CHOICES, ids=lambda yn: f"win:{yn}") +@pytest.mark.parametrize("use_docker", YN_CHOICES, ids=lambda yn: f"docker:{yn}") +@pytest.mark.parametrize("use_celery", YN_CHOICES, ids=lambda yn: f"celery:{yn}") +@pytest.mark.parametrize("use_mailhog", YN_CHOICES, ids=lambda yn: f"mailhog:{yn}") +@pytest.mark.parametrize("use_sentry", YN_CHOICES, ids=lambda yn: f"sentry:{yn}") +@pytest.mark.parametrize("use_compressor", YN_CHOICES, ids=lambda yn: f"cmpr:{yn}") +@pytest.mark.parametrize("use_whitenoise", YN_CHOICES, ids=lambda yn: f"wnoise:{yn}") +def context_combination( + windows, + use_docker, + use_celery, + use_mailhog, + use_sentry, + use_compressor, + use_whitenoise, +): + """Fixture that parametrize the function where it's used.""" + return { + "windows": windows, + "use_docker": use_docker, + "use_compressor": use_compressor, + "use_celery": use_celery, + "use_mailhog": use_mailhog, + "use_sentry": use_sentry, + "use_whitenoise": use_whitenoise, + } + + def build_files_list(root_dir): """Build a list containing absolute paths to the generated files.""" return [ @@ -48,8 +80,13 @@ def check_paths(paths): assert match is None, msg.format(path) -def test_default_configuration(cookies, context): - result = cookies.bake(extra_context=context) +def test_project_generation(cookies, context, context_combination): + """ + Test that project is generated and fully rendered. + + This is parametrized for each combination from ``context_combination`` fixture + """ + result = cookies.bake(extra_context={**context, **context_combination}) assert result.exit_code == 0 assert result.exception is None assert result.project.basename == context["project_slug"] @@ -60,33 +97,24 @@ def test_default_configuration(cookies, context): 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_linting_passes(cookies, context_combination): + """ + Generated project should pass flake8 & black. - -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["project_slug"] - 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() + This is parametrized for each combination from ``context_combination`` fixture + """ + result = cookies.bake(extra_context=context_combination) try: sh.flake8(str(result.project)) except sh.ErrorReturnCode as e: pytest.fail(e) + try: + sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/") + except sh.ErrorReturnCode as e: + pytest.fail(e) + def test_travis_invokes_pytest(cookies, context): context.update({"use_travisci": "y"}) diff --git a/{{cookiecutter.project_slug}}/.pylintrc b/{{cookiecutter.project_slug}}/.pylintrc index 1a1992862..a0955f076 100644 --- a/{{cookiecutter.project_slug}}/.pylintrc +++ b/{{cookiecutter.project_slug}}/.pylintrc @@ -1,5 +1,5 @@ [MASTER] -load-plugins=pylint_common, pylint_django{% if cookiecutter.use_celery == "y" %}, pylint_celery {% endif %} +load-plugins=pylint_django{% if cookiecutter.use_celery == "y" %}, pylint_celery {% endif %} [FORMAT] max-line-length=120 diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index 18e571de4..3b0264e33 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -43,9 +43,7 @@ USE_TZ = True DATABASES = {"default": env.db("DATABASE_URL")} {%- else %} DATABASES = { - "default": env.db( - "DATABASE_URL", default="postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}" - ), + "default": env.db("DATABASE_URL", default="postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}") } {%- endif %} DATABASES["default"]["ATOMIC_REQUESTS"] = True diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index 546f2364f..3db964b66 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -182,7 +182,7 @@ COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True) # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE COMPRESS_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL -COMPRESS_URL = STATIC_URL +COMPRESS_URL = STATIC_URL{% if cookiecutter.use_whitenoise == 'y' %} # noqa F405{% endif %} {% endif %} {%- if cookiecutter.use_whitenoise == 'n' -%} # Collectfast diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index e26f65360..e9cade35e 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -1,8 +1,8 @@ pytz==2018.9 # https://github.com/stub42/pytz -python-slugify==3.0.0 # https://github.com/un33k/python-slugify +python-slugify==3.0.2 # 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' and use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin +rcssmin==1.0.6{% if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} --install-option="--without-c-extensions"{% endif %} # https://github.com/ndparker/rcssmin {%- endif %} argon2-cffi==19.1.0 # https://github.com/hynek/argon2_cffi {%- if cookiecutter.use_whitenoise == 'y' %} @@ -10,8 +10,7 @@ whitenoise==4.1.2 # https://github.com/evansd/whitenoise {%- endif %} redis==3.2.1 # https://github.com/antirez/redis {%- if cookiecutter.use_celery == "y" %} -celery==4.2.1 # pyup: < 5.0 # https://github.com/celery/celery -kombu==4.4.0 # https://github.com/celery/kombu +celery==4.3.0 # pyup: < 5.0 # https://github.com/celery/celery {%- if cookiecutter.use_docker == 'y' %} flower==0.9.3 # https://github.com/mher/flower {%- endif %} diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index df1ef0078..028886b02 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -2,7 +2,7 @@ Werkzeug==0.15.1 # https://github.com/pallets/werkzeug ipdb==0.12 # https://github.com/gotcha/ipdb -Sphinx==1.8.5 # https://github.com/sphinx-doc/sphinx +Sphinx==2.0.0 # https://github.com/sphinx-doc/sphinx {%- if cookiecutter.use_docker == 'y' %} psycopg2==2.7.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- else %} @@ -12,7 +12,7 @@ psycopg2-binary==2.7.7 # https://github.com/psycopg/psycopg2 # Testing # ------------------------------------------------------------------------------ mypy==0.670 # https://github.com/python/mypy -pytest==4.3.1 # https://github.com/pytest-dev/pytest +pytest==4.4.0 # https://github.com/pytest-dev/pytest pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar # Code quality @@ -20,6 +20,10 @@ pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar flake8==3.7.5 # https://github.com/PyCQA/flake8 coverage==4.5.3 # https://github.com/nedbat/coveragepy black==19.3b0 # https://github.com/ambv/black +pylint-django==2.0.6 # https://github.com/PyCQA/pylint-django +{%- if cookiecutter.use_celery == 'y' %} +pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery +{%- endif %} # Django # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index 1d842de4b..035cefd7f 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -8,7 +8,7 @@ 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" %} -sentry-sdk==0.7.7 # https://github.com/getsentry/sentry-python +sentry-sdk==0.7.9 # https://github.com/getsentry/sentry-python {%- endif %} # Django