diff --git a/.pyup.yml b/.pyup.yml index c503dabf3..b330b22bb 100644 --- a/.pyup.yml +++ b/.pyup.yml @@ -13,8 +13,6 @@ pin: True # default: empty label_prs: update -# Specify requirement files by hand, pyup seems to struggle to -# find the ones in the project_slug folder requirements: - "requirements.txt" - "{{cookiecutter.project_slug}}/requirements/base.txt" diff --git a/.travis.yml b/.travis.yml index 9abf37a18..6039075a0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,3 @@ -dist: xenial - services: - docker diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 5962af75d..32ba496e8 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -65,6 +65,7 @@ Listed in alphabetical order. Anuj Bansal `@ahhda`_ Arcuri Davide `@dadokkio`_ Areski Belaid `@areski`_ + AsheKR `@ashekr`_ Ashley Camba Barclay Gauld `@yunti`_ Bartek `@btknu`_ @@ -251,6 +252,7 @@ Listed in alphabetical order. .. _@archinal: https://github.com/archinal .. _@areski: https://github.com/areski .. _@arruda: https://github.com/arruda +.. _@ashekr: https://github.com/ashekr .. _@bertdemiranda: https://github.com/bertdemiranda .. _@bittner: https://github.com/bittner .. _@blaxpy: https://github.com/blaxpy diff --git a/README.rst b/README.rst index 02f220cf3..cfb253787 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,10 @@ Cookiecutter Django :target: https://travis-ci.org/pydanny/cookiecutter-django?branch=master :alt: Build Status +.. image:: https://readthedocs.org/projects/cookiecutter-django/badge/?version=latest + :target: https://cookiecutter-django.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + .. image:: https://pyup.io/repos/github/pydanny/cookiecutter-django/shield.svg :target: https://pyup.io/repos/github/pydanny/cookiecutter-django/ :alt: Updates diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst index dc146c232..a5d598f0b 100644 --- a/docs/developing-locally.rst +++ b/docs/developing-locally.rst @@ -80,7 +80,7 @@ First things first. or if you're running asynchronously: :: - $ gunicorn config.asgi --bind 0.0.0.0:8000 -k uvicorn.workers.UvicornWorker --reload + $ uvicorn config.asgi:application --host 0.0.0.0 --reload .. _PostgreSQL: https://www.postgresql.org/download/ .. _Redis: https://redis.io/download diff --git a/docs/settings.rst b/docs/settings.rst index 2ae8814e9..949d5f35f 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -45,6 +45,7 @@ DJANGO_AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID n/a 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_AWS_S3_REGION_NAME AWS_S3_REGION_NAME n/a None +DJANGO_AWS_S3_CUSTOM_DOMAIN AWS_S3_CUSTOM_DOMAIN n/a None DJANGO_GCP_STORAGE_BUCKET_NAME GS_BUCKET_NAME n/a raises error GOOGLE_APPLICATION_CREDENTIALS n/a n/a raises error SENTRY_DSN SENTRY_DSN n/a raises error diff --git a/requirements.txt b/requirements.txt index 18439a352..44804e0ee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,13 +5,13 @@ binaryornot==0.4.4 # Code quality # ------------------------------------------------------------------------------ black==19.10b0 -flake8==3.7.9 +flake8==3.8.3 flake8-isort==3.0.0 # Testing # ------------------------------------------------------------------------------ -tox==3.14.6 -pytest==5.4.1 +tox==3.15.2 +pytest==5.4.3 pytest-cookies==0.5.1 -pytest-instafail==0.4.1.post0 +pytest-instafail==0.4.2 pyyaml==5.3.1 diff --git a/setup.py b/setup.py index 6dfd9e50b..6b80bd9a0 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ except ImportError: # 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 = "3.0.5-01" +version = "3.0.7" if sys.argv[-1] == "tag": os.system(f'git tag -a {version} -m "version {version}"') diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index de056cc82..91f515057 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -156,7 +156,7 @@ def test_flake8_passes(cookies, context_override): result = cookies.bake(extra_context=context_override) try: - sh.flake8(str(result.project)) + sh.flake8(_cwd=str(result.project)) except sh.ErrorReturnCode as e: pytest.fail(e.stdout.decode()) @@ -167,7 +167,9 @@ def test_black_passes(cookies, context_override): result = cookies.bake(extra_context=context_override) try: - sh.black("--check", "--diff", "--exclude", "migrations", f"{result.project}/") + sh.black( + "--check", "--diff", "--exclude", "migrations", _cwd=str(result.project) + ) except sh.ErrorReturnCode as e: pytest.fail(e.stdout.decode()) diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index c50fefe94..6aa9207a9 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -3,18 +3,22 @@ default_stages: [commit] fail_fast: true repos: -- repo: https://github.com/pre-commit/pre-commit-hooks + - repo: https://github.com/pre-commit/pre-commit-hooks rev: master hooks: - id: trailing-whitespace - files: (^|/).+\.(py|html|sh|css|js)$ + - id: end-of-file-fixer + - id: check-yaml -- repo: local + - repo: https://github.com/psf/black + rev: 19.10b0 + hooks: + - id: black + + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.3 hooks: - id: flake8 - name: flake8 - entry: flake8 - language: python - types: [python] args: ['--config=setup.cfg'] + additional_dependencies: [flake8-isort] diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index c13f1aebe..3d4f324cd 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -90,6 +90,9 @@ AWS_S3_OBJECT_PARAMETERS = { AWS_DEFAULT_ACL = None # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) +# https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#cloudfront +AWS_S3_CUSTOM_DOMAIN = env("DJANGO_AWS_S3_CUSTOM_DOMAIN", default=None) +aws_s3_domain = AWS_S3_CUSTOM_DOMAIN or f"{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com" {% elif cookiecutter.cloud_provider == 'GCP' %} GS_BUCKET_NAME = env("DJANGO_GCP_STORAGE_BUCKET_NAME") GS_DEFAULT_ACL = "publicRead" @@ -104,7 +107,7 @@ STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" {% elif cookiecutter.cloud_provider == 'AWS' -%} STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootS3Boto3Storage" COLLECTFAST_STRATEGY = "collectfast.strategies.boto3.Boto3Strategy" -STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/" +STATIC_URL = f"https://{aws_s3_domain}/static/" {% elif cookiecutter.cloud_provider == 'GCP' -%} STATICFILES_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.StaticRootGoogleCloudStorage" COLLECTFAST_STRATEGY = "collectfast.strategies.gcloud.GoogleCloudStrategy" @@ -115,7 +118,7 @@ STATIC_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/static/" # ------------------------------------------------------------------------------ {%- if cookiecutter.cloud_provider == 'AWS' %} DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootS3Boto3Storage" -MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/" +MEDIA_URL = f"https://{aws_s3_domain}/media/" {%- elif cookiecutter.cloud_provider == 'GCP' %} DEFAULT_FILE_STORAGE = "{{cookiecutter.project_slug}}.utils.storages.MediaRootGoogleCloudStorage" MEDIA_URL = f"https://storage.googleapis.com/{GS_BUCKET_NAME}/media/" diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index 4f198a8ba..4baf2f376 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -4,13 +4,16 @@ Pillow==7.1.2 # https://github.com/python-pillow/Pillow {%- if cookiecutter.use_compressor == "y" %} 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.2.0 # https://github.com/hynek/argon2_cffi +argon2-cffi==20.1.0 # https://github.com/hynek/argon2_cffi {%- if cookiecutter.use_whitenoise == 'y' %} -whitenoise==5.0.1 # https://github.com/evansd/whitenoise +whitenoise==5.1.0 # https://github.com/evansd/whitenoise +{%- endif %} +redis==3.5.0 # https://github.com/andymccurdy/redis-py +{%- if cookiecutter.use_docker == "y" or cookiecutter.windows == "n" %} +hiredis==1.0.1 # https://github.com/redis/hiredis-py {%- endif %} -redis==3.5.0 # https://github.com/andymccurdy/redis-py {%- if cookiecutter.use_celery == "y" %} -celery==4.4.2 # pyup: < 5.0 # https://github.com/celery/celery +celery==4.4.5 # pyup: < 5.0 # https://github.com/celery/celery django-celery-beat==2.0.0 # https://github.com/celery/django-celery-beat {%- if cookiecutter.use_docker == 'y' %} flower==0.9.4 # https://github.com/mher/flower @@ -22,15 +25,15 @@ uvicorn==0.11.5 # https://github.com/encode/uvicorn # Django # ------------------------------------------------------------------------------ -django==3.0.5 # pyup: < 3.1 # https://www.djangoproject.com/ +django==3.0.7 # pyup: < 3.1 # https://www.djangoproject.com/ django-environ==0.4.5 # https://github.com/joke2k/django-environ django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils -django-allauth==0.41.0 # https://github.com/pennersr/django-allauth -django-crispy-forms==1.9.0 # https://github.com/django-crispy-forms/django-crispy-forms +django-allauth==0.42.0 # https://github.com/pennersr/django-allauth +django-crispy-forms==1.9.1 # https://github.com/django-crispy-forms/django-crispy-forms {%- if cookiecutter.use_compressor == "y" %} django-compressor==2.4 # https://github.com/django-compressor/django-compressor {%- endif %} -django-redis==4.11.0 # https://github.com/jazzband/django-redis +django-redis==4.12.1 # https://github.com/jazzband/django-redis {%- if cookiecutter.use_drf == "y" %} # Django REST Framework djangorestframework==3.11.0 # https://github.com/encode/django-rest-framework diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index 9860c74b2..f1307a226 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -2,7 +2,7 @@ Werkzeug==1.0.1 # https://github.com/pallets/werkzeug ipdb==0.13.2 # https://github.com/gotcha/ipdb -Sphinx==3.0.3 # https://github.com/sphinx-doc/sphinx +Sphinx==3.1.1 # https://github.com/sphinx-doc/sphinx {%- if cookiecutter.use_docker == 'y' %} psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- else %} @@ -14,14 +14,14 @@ watchgod==0.6 # https://github.com/samuelcolvin/watchgod # Testing # ------------------------------------------------------------------------------ -mypy==0.770 # https://github.com/python/mypy +mypy==0.781 # https://github.com/python/mypy django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs -pytest==5.4.1 # https://github.com/pytest-dev/pytest +pytest==5.4.3 # https://github.com/pytest-dev/pytest pytest-sugar==0.9.3 # https://github.com/Frozenball/pytest-sugar # Code quality # ------------------------------------------------------------------------------ -flake8==3.7.9 # https://github.com/PyCQA/flake8 +flake8==3.8.3 # https://github.com/PyCQA/flake8 flake8-isort==3.0.0 # https://github.com/gforcada/flake8-isort coverage==5.1 # https://github.com/nedbat/coveragepy black==19.10b0 # https://github.com/ambv/black @@ -29,7 +29,7 @@ pylint-django==2.0.15 # https://github.com/PyCQA/pylint-django {%- if cookiecutter.use_celery == 'y' %} pylint-celery==0.3 # https://github.com/PyCQA/pylint-celery {%- endif %} -pre-commit==2.3.0 # https://github.com/pre-commit/pre-commit +pre-commit==2.5.1 # https://github.com/pre-commit/pre-commit # Django # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index 83ed366f4..5ec4745cd 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -5,10 +5,13 @@ gunicorn==20.0.4 # https://github.com/benoitc/gunicorn psycopg2==2.8.5 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 {%- if cookiecutter.use_whitenoise == 'n' %} -Collectfast==2.1.0 # https://github.com/antonagestam/collectfast +Collectfast==2.2.0 # https://github.com/antonagestam/collectfast {%- endif %} {%- if cookiecutter.use_sentry == "y" %} -sentry-sdk==0.14.3 # https://github.com/getsentry/sentry-python +sentry-sdk==0.15.1 # https://github.com/getsentry/sentry-python +{%- endif %} +{%- if cookiecutter.use_docker == "n" and cookiecutter.windows == "y" %} +hiredis==1.0.1 # https://github.com/redis/hiredis-py {%- endif %} # Django diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py index 04bf4c85e..8bd39d30e 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py @@ -1,6 +1,7 @@ +from django.contrib.auth import get_user_model from rest_framework import serializers -from {{ cookiecutter.project_slug }}.users.models import User +User = get_user_model() class UserSerializer(serializers.ModelSerializer): diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py index ca0060595..18b8da7a6 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py @@ -1,8 +1,15 @@ import pytest +from django.contrib.auth.models import AnonymousUser +from django.http.response import Http404 from django.test import RequestFactory from {{ cookiecutter.project_slug }}.users.models import User -from {{ cookiecutter.project_slug }}.users.views import UserRedirectView, UserUpdateView +from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory +from {{ cookiecutter.project_slug }}.users.views import ( + UserRedirectView, + UserUpdateView, + user_detail_view, +) pytestmark = pytest.mark.django_db @@ -44,3 +51,29 @@ class TestUserRedirectView: view.request = request assert view.get_redirect_url() == f"/users/{user.username}/" + + +class TestUserDetailView: + def test_authenticated(self, user: User, rf: RequestFactory): + request = rf.get("/fake-url/") + request.user = UserFactory() + + response = user_detail_view(request, username=user.username) + + assert response.status_code == 200 + + def test_not_authenticated(self, user: User, rf: RequestFactory): + request = rf.get("/fake-url/") + request.user = AnonymousUser() # type: ignore + + response = user_detail_view(request, username=user.username) + + assert response.status_code == 302 + assert response.url == "/accounts/login/?next=/fake-url/" + + def test_case_sensitivity(self, rf: RequestFactory): + request = rf.get("/fake-url/") + request.user = UserFactory(username="UserName") + + with pytest.raises(Http404): + user_detail_view(request, username="username")