diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 7c772372b..075b3f329 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -99,6 +99,7 @@ Listed in alphabetical order. Dani Hodovic `@danihodovic`_ Daniel Hepper `@dhepper`_ @danielhepper Daniel Hillier `@danifus`_ + Daniel Sears `@highpost`_ @highpost Daniele Tricoli `@eriol`_ David Díaz `@ddiazpinto`_ @DavidDiazPinto Davit Tovmasyan `@davitovmasyan`_ @@ -140,6 +141,7 @@ Listed in alphabetical order. Jerome Leclanche `@jleclanche`_ @Adys Jimmy Gitonga `@afrowave`_ @afrowave John Cass `@jcass77`_ @cass_john + Jules Cheron `@jules-ch`_ Julien Almarcha `@sladinji`_ Julio Castillo `@juliocc`_ Kaido Kert `@kaidokert`_ @@ -300,6 +302,7 @@ Listed in alphabetical order. .. _@hairychris: https://github.com/hairychris .. _@hanaquadara: https://github.com/hanaquadara .. _@hendrikschneider: https://github.com/hendrikschneider +.. _@highpost: https://github.com/highpost .. _@hjwp: https://github.com/hjwp .. _@howiezhao: https://github.com/howiezhao .. _@IanLee1521: https://github.com/IanLee1521 @@ -313,6 +316,7 @@ Listed in alphabetical order. .. _@jcass77: https://github.com/jcass77 .. _@jeromecaisip: https://github.com/jeromecaisip .. _@jleclanche: https://github.com/jleclanche +.. _@jules-ch: https://github.com/jules-ch .. _@juliocc: https://github.com/juliocc .. _@jvanbrug: https://github.com/jvanbrug .. _@ka7eh: https://github.com/ka7eh diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index 87ae90b1d..8abeda9b9 100644 --- a/docs/project-generation-options.rst +++ b/docs/project-generation-options.rst @@ -73,15 +73,15 @@ cloud_provider: mail_service: Select an email service that Django-Anymail provides - 1. Amazon SES_ - 2. Mailgun_ + 1. Mailgun_ + 2. `Amazon SES`_ 3. Mailjet_ 4. Mandrill_ 5. Postmark_ 6. SendGrid_ 7. SendinBlue_ 8. SparkPost_ - 9. Plain/Vanilla Django-Anymail_ + 9. `Other SMTP`_ use_drf: Indicates whether the project should be configured to use `Django Rest Framework`_. @@ -114,8 +114,8 @@ ci_tool: Select a CI tool for running tests. The choices are: 1. None - 2. Travis_ - 3. Gitlab_ + 2. `Travis CI`_ + 3. `Gitlab CI`_ keep_local_envs_in_vcs: Indicates whether the project's ``.envs/.local/`` should be kept in VCS @@ -145,7 +145,7 @@ debug: .. _AWS: https://aws.amazon.com/s3/ .. _GCP: https://cloud.google.com/storage/ -.. _SES: https://aws.amazon.com/ses/ +.. _Amazon SES: https://aws.amazon.com/ses/ .. _Mailgun: https://www.mailgun.com .. _Mailjet: https://www.mailjet.com .. _Mandrill: http://mandrill.com @@ -153,7 +153,7 @@ debug: .. _SendGrid: https://sendgrid.com .. _SendinBlue: https://www.sendinblue.com .. _SparkPost: https://www.sparkpost.com -.. _Django-Anymail: https://anymail.readthedocs.io/en/stable/ +.. _Other SMTP: https://anymail.readthedocs.io/en/stable/ .. _Django Rest Framework: https://github.com/encode/django-rest-framework/ diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 668a6e278..54334dedd 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -68,3 +68,15 @@ if ( "You should either use Whitenoise or select a Cloud Provider to serve static files" ) sys.exit(1) + +if ( + "{{ cookiecutter.cloud_provider }}" == "GCP" + and "{{ cookiecutter.mail_service }}" == "Amazon SES" +) or ( + "{{ cookiecutter.cloud_provider }}" == "None" + and "{{ cookiecutter.mail_service }}" == "Amazon SES" +): + print( + "You should either use AWS or select a different Mail Service for sending emails." + ) + sys.exit(1) diff --git a/requirements.txt b/requirements.txt index 6894fa8f6..81fbf524a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,4 +14,4 @@ tox==3.14.5 pytest==5.4.1 pytest-cookies==0.5.1 pytest-instafail==0.4.1.post0 -pyyaml==5.3 +pyyaml==5.3.1 diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index 91f44ae5a..2c7e6dc43 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -46,17 +46,33 @@ SUPPORTED_COMBINATIONS = [ {"cloud_provider": "AWS", "use_whitenoise": "n"}, {"cloud_provider": "GCP", "use_whitenoise": "y"}, {"cloud_provider": "GCP", "use_whitenoise": "n"}, - {"cloud_provider": "None", "use_whitenoise": "y"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailgun"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mailjet"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Mandrill"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Postmark"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Sendgrid"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "SendinBlue"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "SparkPost"}, + {"cloud_provider": "None", "use_whitenoise": "y", "mail_service": "Other SMTP"}, # Note: cloud_provider=None AND use_whitenoise=n is not supported - {"mail_service": "Mailgun"}, - {"mail_service": "Amazon SES"}, - {"mail_service": "Mailjet"}, - {"mail_service": "Mandrill"}, - {"mail_service": "Postmark"}, - {"mail_service": "Sendgrid"}, - {"mail_service": "SendinBlue"}, - {"mail_service": "SparkPost"}, - {"mail_service": "Other SMTP"}, + {"cloud_provider": "AWS", "mail_service": "Mailgun"}, + {"cloud_provider": "AWS", "mail_service": "Amazon SES"}, + {"cloud_provider": "AWS", "mail_service": "Mailjet"}, + {"cloud_provider": "AWS", "mail_service": "Mandrill"}, + {"cloud_provider": "AWS", "mail_service": "Postmark"}, + {"cloud_provider": "AWS", "mail_service": "Sendgrid"}, + {"cloud_provider": "AWS", "mail_service": "SendinBlue"}, + {"cloud_provider": "AWS", "mail_service": "SparkPost"}, + {"cloud_provider": "AWS", "mail_service": "Other SMTP"}, + {"cloud_provider": "GCP", "mail_service": "Mailgun"}, + {"cloud_provider": "GCP", "mail_service": "Mailjet"}, + {"cloud_provider": "GCP", "mail_service": "Mandrill"}, + {"cloud_provider": "GCP", "mail_service": "Postmark"}, + {"cloud_provider": "GCP", "mail_service": "Sendgrid"}, + {"cloud_provider": "GCP", "mail_service": "SendinBlue"}, + {"cloud_provider": "GCP", "mail_service": "SparkPost"}, + {"cloud_provider": "GCP", "mail_service": "Other SMTP"}, + # Note: cloud_providers GCP and None with mail_service Amazon SES is not supported {"use_drf": "y"}, {"use_drf": "n"}, {"js_task_runner": "None"}, @@ -86,6 +102,8 @@ SUPPORTED_COMBINATIONS = [ UNSUPPORTED_COMBINATIONS = [ {"cloud_provider": "None", "use_whitenoise": "n"}, + {"cloud_provider": "GCP", "mail_service": "Amazon SES"}, + {"cloud_provider": "None", "mail_service": "Amazon SES"}, ] @@ -163,7 +181,7 @@ def test_travis_invokes_pytest(cookies, context): with open(f"{result.project}/.travis.yml", "r") as travis_yml: try: - assert yaml.load(travis_yml)["script"] == ["pytest"] + assert yaml.load(travis_yml, Loader=yaml.FullLoader)["script"] == ["pytest"] except yaml.YAMLError as e: pytest.fail(e) @@ -179,7 +197,7 @@ def test_gitlab_invokes_flake8_and_pytest(cookies, context): with open(f"{result.project}/.gitlab-ci.yml", "r") as gitlab_yml: try: - gitlab_config = yaml.load(gitlab_yml) + gitlab_config = yaml.load(gitlab_yml, Loader=yaml.FullLoader) assert gitlab_config["flake8"]["script"] == ["flake8"] assert gitlab_config["pytest"]["script"] == ["pytest"] except yaml.YAMLError as e: diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index 4b72ee80a..c50fefe94 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: rev: master hooks: - id: trailing-whitespace - files: (^|/)a/.+\.(py|html|sh|css|js)$ + files: (^|/).+\.(py|html|sh|css|js)$ - repo: local hooks: diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index 7896d81f7..6a2a9c3f2 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -1,20 +1,19 @@ """ Base settings to build other settings files upon. """ +from pathlib import Path import environ -ROOT_DIR = ( - environ.Path(__file__) - 3 -) # ({{ cookiecutter.project_slug }}/config/settings/base.py - 3 = {{ cookiecutter.project_slug }}/) -APPS_DIR = ROOT_DIR.path("{{ cookiecutter.project_slug }}") - +ROOT_DIR = Path(__file__).parents[2] +# {{ cookiecutter.project_slug }}/) +APPS_DIR = ROOT_DIR / "{{ cookiecutter.project_slug }}" env = environ.Env() READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False) if READ_DOT_ENV_FILE: # OS environment variables take precedence over variables from .env - env.read_env(str(ROOT_DIR.path(".env"))) + env.read_env(str(ROOT_DIR / ".env")) # GENERAL # ------------------------------------------------------------------------------ @@ -36,7 +35,7 @@ USE_L10N = True # https://docs.djangoproject.com/en/dev/ref/settings/#use-tz USE_TZ = True # https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths -LOCALE_PATHS = [ROOT_DIR.path("locale")] +LOCALE_PATHS = [str(ROOT_DIR / "locale")] # DATABASES # ------------------------------------------------------------------------------ @@ -151,11 +150,11 @@ MIDDLEWARE = [ # STATIC # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#static-root -STATIC_ROOT = str(ROOT_DIR("staticfiles")) +STATIC_ROOT = str(ROOT_DIR / "staticfiles") # https://docs.djangoproject.com/en/dev/ref/settings/#static-url STATIC_URL = "/static/" # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS -STATICFILES_DIRS = [str(APPS_DIR.path("static"))] +STATICFILES_DIRS = [str(APPS_DIR / "static")] # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders STATICFILES_FINDERS = [ "django.contrib.staticfiles.finders.FileSystemFinder", @@ -165,7 +164,7 @@ STATICFILES_FINDERS = [ # MEDIA # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#media-root -MEDIA_ROOT = str(APPS_DIR("media")) +MEDIA_ROOT = str(APPS_DIR / "media") # https://docs.djangoproject.com/en/dev/ref/settings/#media-url MEDIA_URL = "/media/" @@ -177,7 +176,7 @@ TEMPLATES = [ # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND "BACKEND": "django.template.backends.django.DjangoTemplates", # https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs - "DIRS": [str(APPS_DIR.path("templates"))], + "DIRS": [str(APPS_DIR / "templates")], "OPTIONS": { # https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types @@ -210,7 +209,7 @@ CRISPY_TEMPLATE_PACK = "bootstrap4" # FIXTURES # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs -FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),) +FIXTURE_DIRS = (str(APPS_DIR / "fixtures"),) # SECURITY # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/config/wsgi.py b/{{cookiecutter.project_slug}}/config/wsgi.py index 1899a30ca..fccfea354 100644 --- a/{{cookiecutter.project_slug}}/config/wsgi.py +++ b/{{cookiecutter.project_slug}}/config/wsgi.py @@ -15,15 +15,14 @@ framework. """ import os import sys +from pathlib import Path from django.core.wsgi import get_wsgi_application # This allows easy placement of apps within the interior # {{ cookiecutter.project_slug }} directory. -app_path = os.path.abspath( - os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) -) -sys.path.append(os.path.join(app_path, "{{ cookiecutter.project_slug }}")) +app_path = Path(__file__).parents[1].resolve() +sys.path.append(str(app_path / "{{ cookiecutter.project_slug }}")) # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks # if running multiple sites in the same mod_wsgi process. To fix this, use # mod_wsgi daemon mode with each site in its own daemon process, or use diff --git a/{{cookiecutter.project_slug}}/manage.py b/{{cookiecutter.project_slug}}/manage.py index 7e9c99e04..c44cc826d 100755 --- a/{{cookiecutter.project_slug}}/manage.py +++ b/{{cookiecutter.project_slug}}/manage.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import os import sys +from pathlib import Path if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") @@ -24,7 +25,7 @@ if __name__ == "__main__": # This allows easy placement of apps within the interior # {{ cookiecutter.project_slug }} directory. - current_path = os.path.dirname(os.path.abspath(__file__)) - sys.path.append(os.path.join(current_path, "{{ cookiecutter.project_slug }}")) + current_path = Path(__file__).parent.resolve() + sys.path.append(str(current_path / "{{ cookiecutter.project_slug }}")) execute_from_command_line(sys.argv) diff --git a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py index 4e70e2adb..d1170eff6 100644 --- a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py +++ b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py @@ -1,15 +1,16 @@ import os +from pathlib import Path from typing import Sequence import pytest -ROOT_DIR_PATH = os.path.dirname(os.path.realpath(__file__)) -PRODUCTION_DOTENVS_DIR_PATH = os.path.join(ROOT_DIR_PATH, ".envs", ".production") +ROOT_DIR_PATH = Path(__file__).parent.resolve() +PRODUCTION_DOTENVS_DIR_PATH = ROOT_DIR_PATH / ".envs" / ".production" PRODUCTION_DOTENV_FILE_PATHS = [ - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".django"), - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".postgres"), + PRODUCTION_DOTENVS_DIR_PATH / ".django", + PRODUCTION_DOTENVS_DIR_PATH / ".postgres", ] -DOTENV_FILE_PATH = os.path.join(ROOT_DIR_PATH, ".env") +DOTENV_FILE_PATH = ROOT_DIR_PATH / ".env" def merge( @@ -31,9 +32,9 @@ def main(): @pytest.mark.parametrize("merged_file_count", range(3)) @pytest.mark.parametrize("append_linesep", [True, False]) def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool): - tmp_dir_path = str(tmpdir_factory.getbasetemp()) + tmp_dir_path = Path(str(tmpdir_factory.getbasetemp())) - output_file_path = os.path.join(tmp_dir_path, ".env") + output_file_path = tmp_dir_path / ".env" expected_output_file_content = "" merged_file_paths = [] @@ -41,7 +42,7 @@ def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool): merged_file_ord = i + 1 merged_filename = ".service{}".format(merged_file_ord) - merged_file_path = os.path.join(tmp_dir_path, merged_filename) + merged_file_path = tmp_dir_path / merged_filename merged_file_content = merged_filename * merged_file_ord diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index da9cbc97a..a8bc31dce 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -8,7 +8,7 @@ psycopg2==2.8.4 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 Collectfast==2.1.0 # https://github.com/antonagestam/collectfast {%- endif %} {%- if cookiecutter.use_sentry == "y" %} -sentry-sdk==0.14.2 # https://github.com/getsentry/sentry-python +sentry-sdk==0.14.3 # https://github.com/getsentry/sentry-python {%- endif %} # Django diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py index 61586b5ca..41d5af292 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_tasks.py @@ -4,8 +4,9 @@ from celery.result import EagerResult from {{ cookiecutter.project_slug }}.users.tasks import get_users_count from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory +pytestmark = pytest.mark.django_db + -@pytest.mark.django_db def test_user_count(settings): """A basic test to execute the get_users_count Celery task.""" UserFactory.create_batch(3)