diff --git a/.github/contributors.json b/.github/contributors.json index 8853e76e..84637568 100644 --- a/.github/contributors.json +++ b/.github/contributors.json @@ -1067,5 +1067,20 @@ "name": "vascop", "github_login": "vascop", "twitter_username": "" + }, + { + "name": "PJ Hoberman", + "github_login": "pjhoberman", + "twitter_username": "" + }, + { + "name": "lcd1232", + "github_login": "lcd1232", + "twitter_username": "" + }, + { + "name": "Tames McTigue", + "github_login": "Tamerz", + "twitter_username": "" } ] \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5ee5d0a0..f0b70dd0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,3 +8,5 @@ updates: directory: "/" schedule: interval: "daily" + labels: + - "update" diff --git a/.github/workflows/pre-commit-autoupdate.yml b/.github/workflows/pre-commit-autoupdate.yml index 71738d62..d433e00d 100644 --- a/.github/workflows/pre-commit-autoupdate.yml +++ b/.github/workflows/pre-commit-autoupdate.yml @@ -26,7 +26,7 @@ jobs: run: pre-commit autoupdate - name: Create Pull Request - uses: peter-evans/create-pull-request@v3.7.0 + uses: peter-evans/create-pull-request@v3.8.2 with: token: ${{ secrets.GITHUB_TOKEN }} branch: update/pre-commit-autoupdate diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml index f05f99d6..d4672c47 100644 --- a/.github/workflows/update-changelog.yml +++ b/.github/workflows/update-changelog.yml @@ -28,7 +28,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4.8.0 + uses: stefanzweifel/git-auto-commit-action@v4.9.2 with: commit_message: Update Changelog file_pattern: CHANGELOG.md diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml index 835000b2..0c1c0050 100644 --- a/.github/workflows/update-contributors.yml +++ b/.github/workflows/update-contributors.yml @@ -24,7 +24,7 @@ jobs: run: python scripts/update_contributors.py - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4.8.0 + uses: stefanzweifel/git-auto-commit-action@v4.9.2 with: commit_message: Update Contributors file_pattern: CONTRIBUTORS.md .github/contributors.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c599915..d9559b9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,144 @@ All enhancements and patches to Cookiecutter Django will be documented in this f +## [2021-04-07] +### Changed +- Update django to 3.1.8 ([#3117](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3117)) +### Fixed +- Fix linting via pre-commit on Github CI ([#3077](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3077)) +- Fix gitlab-ci using duplicate key name for image ([#3112](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3112)) +### Updated +- Update sentry-sdk to 1.0.0 ([#3080](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3080)) +- Update gunicorn to 20.1.0 ([#3108](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3108)) +- Update pre-commit to 2.12.0 ([#3118](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3118)) +- Update django-extensions to 3.1.2 ([#3116](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3116)) +- Update pillow to 8.2.0 ([#3113](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3113)) +- Update pytest to 6.2.3 ([#3115](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3115)) + +## [2021-03-26] +### Updated +- Update djangorestframework to 3.12.4 ([#3107](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3107)) + +## [2021-03-25] +### Updated +- Update djangorestframework to 3.12.3 ([#3105](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3105)) + +## [2021-03-22] +### Updated +- Update django-crispy-forms to 1.11.2 ([#3104](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3104)) +- Update sphinx to 3.5.3 ([#3103](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3103)) +- Update ipdb to 0.13.7 ([#3102](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3102)) +- Update sphinx-autobuild to 2021.3.14 ([#3101](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3101)) +- Update isort to 5.8.0 ([#3100](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3100)) +- Update pre-commit to 2.11.1 ([#3089](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3089)) +- Update flake8 to 3.9.0 ([#3096](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3096)) +- Update pillow to 8.1.2 ([#3084](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3084)) +- Auto-update pre-commit hooks ([#3095](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3095)) + +## [2021-03-05] +### Changed +- Updated test_urls.py and views.py to re-use User.get_absolute_url() ([#3070](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3070)) +### Updated +- Bump stefanzweifel/git-auto-commit-action from v4.9.1 to v4.9.2 ([#3082](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3082)) + +## [2021-03-03] +### Updated +- Update tox to 3.23.0 ([#3079](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3079)) +- Update ipdb to 0.13.5 ([#3078](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3078)) + +## [2021-03-02] +### Fixed +- Fixes for pytest job in Github CI workflow ([#3076](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3076)) +### Updated +- Update pillow to 8.1.1 ([#3075](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3075)) +- Update coverage to 5.5 ([#3074](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3074)) + +## [2021-02-24] +### Updated +- Bump stefanzweifel/git-auto-commit-action from v4.9.0 to v4.9.1 ([#3069](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3069)) + +## [2021-02-23] +### Changed +- Update to Django 3.1 ([#3043](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3043)) +- Lint with pre-commit on CI with Github actions ([#3066](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3066)) +- Use exception var in status code pages if available ([#2992](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/2992)) + +## [2021-02-22] +### Changed +- refactor: remove default cache settings in test.py ([#3064](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3064)) +- Update django to 3.0.13 ([#3060](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3060)) +### Fixed +- Fix missing Django Debug toolbar with node container ([#2865](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/2865)) +- Remove Email from User API ([#3055](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3055)) +### Updated +- Bump stefanzweifel/git-auto-commit-action from v4.8.0 to v4.9.0 ([#3065](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3065)) +- Update django-crispy-forms to 1.11.1 ([#3063](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3063)) +- Update uvicorn to 0.13.4 ([#3062](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3062)) +- Update mypy to 0.812 ([#3061](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3061)) +- Update sentry-sdk to 0.20.3 ([#3059](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3059)) +- Update tox to 3.22.0 ([#3057](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3057)) +- Update sphinx to 3.5.1 ([#3056](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3056)) + +## [2021-02-16] +### Updated +- Update sentry-sdk to 0.20.2 ([#3054](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3054)) +- Update sphinx to 3.5.0 ([#3053](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3053)) + +## [2021-02-13] +### Updated +- Update sentry-sdk to 0.20.1 ([#3052](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3052)) + +## [2021-02-12] +### Updated +- Update pre-commit to 2.10.1 ([#3045](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3045)) +- Update sentry-sdk to 0.20.0 ([#3051](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3051)) + +## [2021-02-10] +### Updated +- Bump peter-evans/create-pull-request from v3.8.1 to v3.8.2 ([#3049](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3049)) + +## [2021-02-08] +### Updated +- Update django-extensions to 3.1.1 ([#3047](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3047)) +- Bump peter-evans/create-pull-request from v3.8.0 to v3.8.1 ([#3046](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3046)) + +## [2021-02-06] +### Changed +- Removed Redundant test_case_sensitivity() and made test_not_authenticated() get the LOGIN_URL dynamically. ([#3041](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3041)) +- Refactored users.forms to make the code more readeable ([#3029](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3029)) +- Update django to 3.0.12 ([#3037](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3037)) +### Updated +- Update tox to 3.21.4 ([#3044](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3044)) + +## [2021-02-01] +### Updated +- Update pytz to 2021.1 ([#3035](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3035)) +- Update jinja2 to 2.11.3 ([#3033](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3033)) +- Bump peter-evans/create-pull-request from v3.7.0 to v3.8.0 ([#3034](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3034)) + +## [2021-01-31] +### Changed +- Adding local celery instructions to developing-locally ([#3031](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3031)) +### Updated +- Update django-crispy-forms to 1.11.0 ([#3032](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3032)) + +## [2021-01-28] +### Updated +- Update pre-commit to 2.10.0 ([#3028](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3028)) +- Update django-anymail to 8.2 ([#3027](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3027)) +- Update tox to 3.21.3 ([#3026](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3026)) + +## [2021-01-26] +### Changed +- Bump peter-evans/create-pull-request from v3.6.0 to v3.7.0 ([#3022](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3022)) +- Using SuccessMessageMixin to send success message to django template ([#3021](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3021)) +### Fixed +- Update admin to ignore *_name User attributes ([#3018](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3018)) +### Updated +- Update coverage to 5.4 ([#3024](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3024)) +- Update pytest to 6.2.2 ([#3020](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3020)) +- Update django-cors-headers to 3.7.0 ([#3019](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/3019)) + ## [2021-01-24] ### Changed - Use defer for script tags (Fix #2922) ([#2927](https://api.github.com/repos/pydanny/cookiecutter-django/pulls/2927)) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 818b6cdd..ea870187 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -978,6 +978,13 @@ Listed in alphabetical order. + + lcd1232 + + lcd1232 + + + Leo won @@ -1223,6 +1230,13 @@ Listed in alphabetical order. + + PJ Hoberman + + pjhoberman + + + Raony GuimarĂ£es CorrĂªa @@ -1349,6 +1363,13 @@ Listed in alphabetical order. + + Tames McTigue + + Tamerz + + + Tano Abeleyra diff --git a/README.rst b/README.rst index 105497c4..f5bc708e 100644 --- a/README.rst +++ b/README.rst @@ -40,7 +40,7 @@ production-ready Django projects quickly. Features --------- -* For Django 3.0 +* For Django 3.1 * Works with Python 3.9 * Renders Django projects with 100% starting test coverage * Twitter Bootstrap_ v4 (`maintained Foundation fork`_ also available) diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst index 01df2e44..fcce7e6f 100644 --- a/docs/deployment-with-docker.rst +++ b/docs/deployment-with-docker.rst @@ -124,8 +124,8 @@ To check the logs out, run:: If you want to scale your application, run:: - docker-compose -f production.yml scale django=4 - docker-compose -f production.yml scale celeryworker=2 + docker-compose -f production.yml up --scale django=4 + docker-compose -f production.yml up --scale celeryworker=2 .. warning:: don't try to scale ``postgres``, ``celerybeat``, or ``traefik``. diff --git a/docs/developing-locally.rst b/docs/developing-locally.rst index c8b54ea5..661c9f26 100644 --- a/docs/developing-locally.rst +++ b/docs/developing-locally.rst @@ -143,6 +143,10 @@ when developing locally. If you have the appropriate setup on your local machine in ``config/settings/local.py``:: CELERY_TASK_ALWAYS_EAGER = False + +To run Celery locally, make sure redis-server is installed (instructions are available at https://redis.io/topics/quickstart), run the server in one terminal with `redis-server`, and then start celery in another terminal with the following command:: + + celery -A config.celery_app worker --loglevel=info Sass Compilation & Live Reloading diff --git a/requirements.txt b/requirements.txt index 87161a2e..1e75dbce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,13 +5,14 @@ binaryornot==0.4.4 # Code quality # ------------------------------------------------------------------------------ black==20.8b1 -isort==5.7.0 -flake8==3.8.4 +isort==5.8.0 +flake8==3.9.0 flake8-isort==4.0.0 +pre-commit==2.12.0 # Testing # ------------------------------------------------------------------------------ -tox==3.21.2 +tox==3.23.0 pytest==5.4.3 # pyup: <6 # https://github.com/hackebrot/pytest-cookies/issues/51 pytest-cookies==0.5.1 pytest-instafail==0.4.2 @@ -20,4 +21,4 @@ pyyaml==5.4.1 # Scripting # ------------------------------------------------------------------------------ PyGithub==1.54.1 -jinja2==2.11.2 +jinja2==2.11.3 diff --git a/setup.py b/setup.py index 44ffae96..73ccb863 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.11" +version = "3.1.8" if sys.argv[-1] == "tag": os.system(f'git tag -a {version} -m "version {version}"') diff --git a/tests/test_bare.sh b/tests/test_bare.sh index eae09dc7..1f52d91b 100755 --- a/tests/test_bare.sh +++ b/tests/test_bare.sh @@ -27,5 +27,11 @@ sudo utility/install_os_dependencies.sh install # Install Python deps pip install -r requirements/local.txt +# Lint by running pre-commit on all files +# Needs a git repo to find the project root +git init +git add . +pre-commit run --show-diff-on-failure -a + # run the project's tests pytest diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index af6e4588..7e585ea4 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -231,10 +231,10 @@ def test_gitlab_invokes_flake8_and_pytest( ["use_docker", "expected_test_script"], [ ("n", "pytest"), - ("y", "docker-compose -f local.yml exec -T django pytest"), + ("y", "docker-compose -f local.yml run django pytest"), ], ) -def test_github_invokes_flake8_and_pytest( +def test_github_invokes_linter_and_pytest( cookies, context, use_docker, expected_test_script ): context.update({"ci_tool": "Github", "use_docker": use_docker}) @@ -248,11 +248,11 @@ def test_github_invokes_flake8_and_pytest( with open(f"{result.project}/.github/workflows/ci.yml", "r") as github_yml: try: github_config = yaml.safe_load(github_yml) - flake8_present = False - for action_step in github_config["jobs"]["flake8"]["steps"]: - if action_step.get("run") == "flake8": - flake8_present = True - assert flake8_present + linter_present = False + for action_step in github_config["jobs"]["linter"]["steps"]: + if action_step.get("uses", "NA").startswith("pre-commit"): + linter_present = True + assert linter_present expected_test_script_present = False for action_step in github_config["jobs"]["pytest"]["steps"]: diff --git a/tests/test_docker.sh b/tests/test_docker.sh index 740f8fc2..001ef06d 100755 --- a/tests/test_docker.sh +++ b/tests/test_docker.sh @@ -17,12 +17,16 @@ cd .cache/docker cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y $@ cd my_awesome_project +# Lint by running pre-commit on all files +# Needs a git repo to find the project root +# We don't have git inside Docker, so run it outside +git init +git add . +pre-commit run --show-diff-on-failure -a + # run the project's type checks docker-compose -f local.yml run django mypy my_awesome_project -# Run black with --check option -docker-compose -f local.yml run django black --check --diff --exclude 'migrations' ./ - # run the project's tests docker-compose -f local.yml run django pytest @@ -30,4 +34,4 @@ docker-compose -f local.yml run django pytest docker-compose -f local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; } # Test support for translations -docker-compose -f local.yml run django python manage.py makemessages +docker-compose -f local.yml run django python manage.py makemessages --all diff --git a/{{cookiecutter.project_slug}}/.envs/.local/.django b/{{cookiecutter.project_slug}}/.envs/.local/.django index 919f3118..ef581a1c 100644 --- a/{{cookiecutter.project_slug}}/.envs/.local/.django +++ b/{{cookiecutter.project_slug}}/.envs/.local/.django @@ -14,4 +14,4 @@ REDIS_URL=redis://redis:6379/0 # Flower CELERY_FLOWER_USER=!!!SET CELERY_FLOWER_USER!!! CELERY_FLOWER_PASSWORD=!!!SET CELERY_FLOWER_PASSWORD!!! -{% endif %} +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml index 15eb3daa..70023b53 100644 --- a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -7,16 +7,16 @@ env: on: pull_request: - branches: [ "master" ] + branches: [ "master", "main" ] paths-ignore: [ "docs/**" ] push: - branches: [ "master" ] + branches: [ "master", "main" ] paths-ignore: [ "docs/**" ] jobs: - flake8: + linter: runs-on: ubuntu-latest steps: @@ -28,38 +28,56 @@ jobs: with: python-version: 3.9 - - name: Install flake8 - run: | - python -m pip install --upgrade pip - pip install flake8 + # Run all pre-commit hooks on all the files. + # Getting only staged files can be tricky in case a new PR is opened + # since the action is run on a branch in detached head state + - name: Install and Run Pre-commit + uses: pre-commit/action@v2.0.0 - - name: Lint with flake8 - run: flake8 - -# With no caching at all the entire ci process takes 4m 30s to complete! + # With no caching at all the entire ci process takes 4m 30s to complete! pytest: runs-on: ubuntu-latest + {%- if cookiecutter.use_docker == 'n' %} + + services: + {%- if cookiecutter.use_celery == 'y' %} + redis: + image: redis:5.0 + ports: + - 6379:6379 + {%- endif %} + postgres: + image: postgres:12 + ports: + - 5432:5432 + env: + POSTGRES_PASSWORD: postgres + + env: + {%- if cookiecutter.use_celery == 'y' %} + CELERY_BROKER_URL: "redis://localhost:6379/0" + {%- endif %} + # postgres://user:password@host:port/database + DATABASE_URL: "postgres://postgres:postgres@localhost:5432/postgres" + {%- endif %} + steps: - name: Checkout Code Repository uses: actions/checkout@v2 - {% if cookiecutter.use_docker == 'y' -%} + {%- if cookiecutter.use_docker == 'y' %} - name: Build the Stack run: docker-compose -f local.yml build - - name: Make DB Migrations + - name: Run DB Migrations run: docker-compose -f local.yml run --rm django python manage.py migrate - - name: Run the Stack - run: docker-compose -f local.yml up -d - - name: Run Django Tests - run: docker-compose -f local.yml exec -T django pytest + run: docker-compose -f local.yml run django pytest - name: Tear down the Stack run: docker-compose -f local.yml down - {%- else %} - name: Set up Python 3.9 @@ -71,8 +89,8 @@ jobs: id: pip-cache-location run: | echo "::set-output name=dir::$(pip cache dir)" + {%- raw %} - {% raw %} - name: Cache pip Project Dependencies uses: actions/cache@v2 with: @@ -82,7 +100,7 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('**/local.txt') }} restore-keys: | ${{ runner.os }}-pip- - {% endraw %} + {%- endraw %} - name: Install Dependencies run: | @@ -91,5 +109,4 @@ jobs: - name: Test with pytest run: pytest - {%- endif %} diff --git a/{{cookiecutter.project_slug}}/.gitlab-ci.yml b/{{cookiecutter.project_slug}}/.gitlab-ci.yml index 314aacb6..711bfc39 100644 --- a/{{cookiecutter.project_slug}}/.gitlab-ci.yml +++ b/{{cookiecutter.project_slug}}/.gitlab-ci.yml @@ -21,7 +21,6 @@ flake8: pytest: stage: test - image: python:3.9 {% if cookiecutter.use_docker == 'y' -%} image: docker/compose:latest tags: @@ -35,7 +34,8 @@ pytest: - docker-compose -f local.yml up -d script: - docker-compose -f local.yml run django pytest - {%- else %} + {%- else -%} + image: python:3.9 tags: - python services: diff --git a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml index 5a5b58a6..df71ea61 100644 --- a/{{cookiecutter.project_slug}}/.pre-commit-config.yaml +++ b/{{cookiecutter.project_slug}}/.pre-commit-config.yaml @@ -16,14 +16,13 @@ repos: - id: black - repo: https://github.com/timothycrosley/isort - rev: 5.7.0 + rev: 5.8.0 hooks: - id: isort - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 3.9.0 hooks: - id: flake8 args: ['--config=setup.cfg'] additional_dependencies: [flake8-isort] - diff --git a/{{cookiecutter.project_slug}}/.pylintrc b/{{cookiecutter.project_slug}}/.pylintrc index a0955f07..dff52e75 100644 --- a/{{cookiecutter.project_slug}}/.pylintrc +++ b/{{cookiecutter.project_slug}}/.pylintrc @@ -1,5 +1,5 @@ [MASTER] -load-plugins=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}}/LICENSE b/{{cookiecutter.project_slug}}/LICENSE index c831e030..812fa0fa 100644 --- a/{{cookiecutter.project_slug}}/LICENSE +++ b/{{cookiecutter.project_slug}}/LICENSE @@ -7,7 +7,7 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of 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.open_source_license == 'BSD' %} +{%- elif cookiecutter.open_source_license == 'BSD' %} Copyright (c) {% now 'utc', '%Y' %}, {{ cookiecutter.author_name }} All rights reserved. @@ -35,7 +35,7 @@ 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. -{% elif cookiecutter.open_source_license == 'GPLv3' %} +{%- elif cookiecutter.open_source_license == 'GPLv3' %} Copyright (c) {% now 'utc', '%Y' %}, {{ cookiecutter.author_name }} This program is free software: you can redistribute it and/or modify @@ -50,7 +50,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . -{% elif cookiecutter.open_source_license == 'Apache Software License 2.0' %} +{%- elif cookiecutter.open_source_license == 'Apache Software License 2.0' %} Apache License Version 2.0, January 2004 @@ -242,4 +242,4 @@ along with this program. If not, see . WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -{% endif %} +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/Procfile b/{{cookiecutter.project_slug}}/Procfile index 0becb2cb..8679a066 100644 --- a/{{cookiecutter.project_slug}}/Procfile +++ b/{{cookiecutter.project_slug}}/Procfile @@ -1,10 +1,10 @@ release: python manage.py migrate -{% if cookiecutter.use_async == "y" -%} +{%- if cookiecutter.use_async == "y" -%} web: gunicorn config.asgi:application -k uvicorn.workers.UvicornWorker {%- else %} web: gunicorn config.wsgi:application {%- endif %} -{% if cookiecutter.use_celery == "y" -%} +{%- if cookiecutter.use_celery == "y" -%} worker: celery worker --app=config.celery_app --loglevel=info beat: celery beat --app=config.celery_app --loglevel=info {%- endif %} diff --git a/{{cookiecutter.project_slug}}/README.rst b/{{cookiecutter.project_slug}}/README.rst index 2aae422c..b120f16a 100644 --- a/{{cookiecutter.project_slug}}/README.rst +++ b/{{cookiecutter.project_slug}}/README.rst @@ -9,10 +9,10 @@ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/ambv/black :alt: Black code style -{% if cookiecutter.open_source_license != "Not open source" %} +{%- if cookiecutter.open_source_license != "Not open source" %} :License: {{cookiecutter.open_source_license}} -{% endif %} +{%- endif %} Settings -------- @@ -67,7 +67,7 @@ Moved to `Live reloading and SASS compilation`_. .. _`Live reloading and SASS compilation`: http://cookiecutter-django.readthedocs.io/en/latest/live-reloading-and-sass-compilation.html -{% if cookiecutter.use_celery == "y" %} +{%- if cookiecutter.use_celery == "y" %} Celery ^^^^^^ @@ -83,19 +83,21 @@ To run a celery worker: 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_mailhog == "y" %} +{%- endif %} +{%- if cookiecutter.use_mailhog == "y" %} Email Server ^^^^^^^^^^^^ -{% if cookiecutter.use_docker == 'y' %} +{%- if cookiecutter.use_docker == 'y' %} + In development, it is often nice to be able to see emails that are being sent from your application. For that reason local SMTP server `MailHog`_ with a web interface is available as docker container. Container mailhog will start automatically when you will run all docker containers. Please check `cookiecutter-django Docker documentation`_ for more details how to start all containers. With MailHog running, to view messages that are sent by your application, open your browser and go to ``http://127.0.0.1:8025`` -{% else %} +{%- else %} + 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. #. `Download the latest MailHog release`_ for your OS. @@ -117,10 +119,10 @@ In development, it is often nice to be able to see emails that are being sent fr Now you have your own mail server running locally, ready to receive whatever you send it. .. _`Download the latest MailHog release`: https://github.com/mailhog/MailHog/releases -{% endif %} +{%- endif %} .. _mailhog: https://github.com/mailhog/MailHog -{% endif %} -{% if cookiecutter.use_sentry == "y" %} +{%- endif %} +{%- if cookiecutter.use_sentry == "y" %} Sentry ^^^^^^ @@ -129,13 +131,13 @@ 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 %} +{%- endif %} Deployment ---------- The following details how to deploy this application. -{% if cookiecutter.use_heroku.lower() == "y" %} +{%- if cookiecutter.use_heroku.lower() == "y" %} Heroku ^^^^^^ @@ -143,8 +145,8 @@ Heroku See detailed `cookiecutter-django Heroku documentation`_. .. _`cookiecutter-django Heroku documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-on-heroku.html -{% endif %} -{% if cookiecutter.use_docker.lower() == "y" %} +{%- endif %} +{%- if cookiecutter.use_docker.lower() == "y" %} Docker ^^^^^^ @@ -152,9 +154,8 @@ Docker See detailed `cookiecutter-django Docker documentation`_. .. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html -{% endif %} - -{% if cookiecutter.custom_bootstrap_compilation == "y" %} +{%- endif %} +{%- if cookiecutter.custom_bootstrap_compilation == "y" %} Custom Bootstrap Compilation ^^^^^^ @@ -163,11 +164,11 @@ Bootstrap v4 is installed using npm and customised by tweaking your variables in You can find a list of available variables `in the bootstrap source`_, or get explanations on them in the `Bootstrap docs`_. -{% if cookiecutter.js_task_runner == 'Gulp' %} +{%- if cookiecutter.js_task_runner == 'Gulp' %} Bootstrap's javascript as well as its dependencies is concatenated into a single file: ``static/js/vendors.js``. -{% endif %} +{%- endif %} .. _in the bootstrap source: https://github.com/twbs/bootstrap/blob/v4-dev/scss/_variables.scss .. _Bootstrap docs: https://getbootstrap.com/docs/4.1/getting-started/theming/ -{% endif %} +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile index 210d14fa..f1a489a3 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile +++ b/{{cookiecutter.project_slug}}/compose/local/django/Dockerfile @@ -1,22 +1,57 @@ -FROM python:3.9-slim-buster +ARG PYTHON_VERSION=3.9-slim-buster + +# define an alias for the specfic python version used in this file. +FROM python:${PYTHON_VERSION} as python + +# Python build stage +FROM python as python-build-stage + +ARG BUILD_ENVIRONMENT=local + +# Install apt packages +RUN apt-get update && apt-get install --no-install-recommends -y \ + # dependencies for building Python packages + build-essential \ + # psycopg2 dependencies + libpq-dev + +# Requirements are installed here to ensure they will be cached. +COPY ./requirements . + +# Create Python Dependency and Sub-Dependency Wheels. +RUN pip wheel --wheel-dir /usr/src/app/wheels \ + -r ${BUILD_ENVIRONMENT}.txt + + +# Python 'run' stage +FROM python as python-run-stage + +ARG BUILD_ENVIRONMENT=local +ARG APP_HOME=/app ENV PYTHONUNBUFFERED 1 ENV PYTHONDONTWRITEBYTECODE 1 +ENV BUILD_ENV ${BUILD_ENVIRONMENT} -RUN apt-get update \ - # dependencies for building Python packages - && apt-get install -y build-essential \ +WORKDIR ${APP_HOME} + +# Install required system dependencies +RUN apt-get update && apt-get install --no-install-recommends -y \ # psycopg2 dependencies - && apt-get install -y libpq-dev \ + libpq-dev \ # Translations dependencies - && apt-get install -y gettext \ + gettext \ # cleaning up unused files && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ && rm -rf /var/lib/apt/lists/* -# Requirements are installed here to ensure they will be cached. -COPY ./requirements /requirements -RUN pip install -r /requirements/local.txt +# All absolute dir copies ignore workdir instruction. All relative dir copies are wrt to the workdir instruction +# copy python dependency wheels from python-build-stage +COPY --from=python-build-stage /usr/src/app/wheels /wheels/ + +# use wheels to install python dependencies +RUN pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* \ + && rm -rf /wheels/ COPY ./compose/production/django/entrypoint /entrypoint RUN sed -i 's/\r$//g' /entrypoint @@ -25,6 +60,7 @@ RUN chmod +x /entrypoint COPY ./compose/local/django/start /start RUN sed -i 's/\r$//g' /start RUN chmod +x /start + {% if cookiecutter.use_celery == "y" %} COPY ./compose/local/django/celery/worker/start /start-celeryworker RUN sed -i 's/\r$//g' /start-celeryworker @@ -38,6 +74,8 @@ COPY ./compose/local/django/celery/flower/start /start-flower RUN sed -i 's/\r$//g' /start-flower RUN chmod +x /start-flower {% endif %} -WORKDIR /app + +# copy application code to WORKDIR +COPY . ${APP_HOME} ENTRYPOINT ["/entrypoint"] diff --git a/{{cookiecutter.project_slug}}/compose/local/django/start b/{{cookiecutter.project_slug}}/compose/local/django/start index 660eda34..9cbb6c89 100644 --- a/{{cookiecutter.project_slug}}/compose/local/django/start +++ b/{{cookiecutter.project_slug}}/compose/local/django/start @@ -10,4 +10,4 @@ python manage.py migrate uvicorn config.asgi:application --host 0.0.0.0 --reload {%- else %} python manage.py runserver_plus 0.0.0.0:8000 -{% endif %} +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/download b/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/download index 8d5ea091..0c515935 100644 --- a/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/download +++ b/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/download @@ -21,4 +21,3 @@ export AWS_STORAGE_BUCKET_NAME="${DJANGO_AWS_STORAGE_BUCKET_NAME}" aws s3 cp s3://${AWS_STORAGE_BUCKET_NAME}${BACKUP_DIR_PATH}/${1} ${BACKUP_DIR_PATH}/${1} message_success "Finished downloading ${1}." - diff --git a/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/upload b/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/upload index 4a89dcb5..9446b930 100644 --- a/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/upload +++ b/{{cookiecutter.project_slug}}/compose/production/aws/maintenance/upload @@ -27,4 +27,3 @@ message_info "Cleaning the directory ${BACKUP_DIR_PATH}" rm -rf ${BACKUP_DIR_PATH}/* message_success "Finished uploading and cleaning." - diff --git a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile index 45faa17e..c1b3e97b 100644 --- a/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile +++ b/{{cookiecutter.project_slug}}/compose/production/django/Dockerfile @@ -1,36 +1,75 @@ {% if cookiecutter.js_task_runner == 'Gulp' -%} FROM node:10-stretch-slim as client-builder -WORKDIR /app -COPY ./package.json /app +ARG APP_HOME=/app +WORKDIR ${APP_HOME} + +COPY ./package.json ${APP_HOME} RUN npm install && npm cache clean --force -COPY . /app +COPY . ${APP_HOME} RUN npm run build -# Python build stage {%- endif %} -FROM python:3.9-slim-buster + +ARG PYTHON_VERSION=3.9-slim-buster + +# define an alias for the specfic python version used in this file. +FROM python:${PYTHON_VERSION} as python + +# Python build stage +FROM python as python-build-stage + +ARG BUILD_ENVIRONMENT=production + +# Install apt packages +RUN apt-get update && apt-get install --no-install-recommends -y \ + # dependencies for building Python packages + build-essential \ + # psycopg2 dependencies + libpq-dev + +# Requirements are installed here to ensure they will be cached. +COPY ./requirements . + +# Create Python Dependency and Sub-Dependency Wheels. +RUN pip wheel --wheel-dir /usr/src/app/wheels \ + -r ${BUILD_ENVIRONMENT}.txt + + +# Python 'run' stage +FROM python as python-run-stage + +ARG BUILD_ENVIRONMENT=production +ARG APP_HOME=/app ENV PYTHONUNBUFFERED 1 +ENV PYTHONDONTWRITEBYTECODE 1 +ENV BUILD_ENV ${BUILD_ENVIRONMENT} -RUN apt-get update \ - # dependencies for building Python packages - && apt-get install -y build-essential \ - # psycopg2 dependencies - && apt-get install -y libpq-dev \ - # Translations dependencies - && apt-get install -y gettext \ - # cleaning up unused files - && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ - && rm -rf /var/lib/apt/lists/* +WORKDIR ${APP_HOME} RUN addgroup --system django \ && adduser --system --ingroup django django -# Requirements are installed here to ensure they will be cached. -COPY ./requirements /requirements -RUN pip install --no-cache-dir -r /requirements/production.txt \ - && rm -rf /requirements + +# Install required system dependencies +RUN apt-get update && apt-get install --no-install-recommends -y \ + # psycopg2 dependencies + libpq-dev \ + # Translations dependencies + gettext \ + # cleaning up unused files + && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ + && rm -rf /var/lib/apt/lists/* + +# All absolute dir copies ignore workdir instruction. All relative dir copies are wrt to the workdir instruction +# copy python dependency wheels from python-build-stage +COPY --from=python-build-stage /usr/src/app/wheels /wheels/ + +# use wheels to install python dependencies +RUN pip install --no-cache-dir --no-index --find-links=/wheels/ /wheels/* \ + && rm -rf /wheels/ + COPY --chown=django:django ./compose/production/django/entrypoint /entrypoint RUN sed -i 's/\r$//g' /entrypoint @@ -58,14 +97,17 @@ RUN sed -i 's/\r$//g' /start-flower RUN chmod +x /start-flower {%- endif %} + +# copy application code to WORKDIR {%- if cookiecutter.js_task_runner == 'Gulp' %} -COPY --from=client-builder --chown=django:django /app /app +COPY --from=client-builder --chown=django:django ${APP_HOME} ${APP_HOME} {% else %} -COPY --chown=django:django . /app +COPY --chown=django:django . ${APP_HOME} {%- endif %} +# make django owner of the WORKDIR directory as well. +RUN chown django:django ${APP_HOME} + USER django -WORKDIR /app - ENTRYPOINT ["/entrypoint"] diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml index 7b56063f..cc183cd6 100644 --- a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml +++ b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml @@ -35,7 +35,7 @@ http: web-secure-router: {%- if cookiecutter.domain_name.count('.') == 1 %} rule: "Host(`{{ cookiecutter.domain_name }}`) || Host(`www.{{ cookiecutter.domain_name }}`)" - {% else %} + {%- else %} rule: "Host(`{{ cookiecutter.domain_name }}`)" {%- endif %} entryPoints: diff --git a/{{cookiecutter.project_slug}}/config/settings/base.py b/{{cookiecutter.project_slug}}/config/settings/base.py index f69908c2..640d8b62 100644 --- a/{{cookiecutter.project_slug}}/config/settings/base.py +++ b/{{cookiecutter.project_slug}}/config/settings/base.py @@ -44,7 +44,7 @@ LOCALE_PATHS = [str(ROOT_DIR / "locale")] 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 @@ -230,7 +230,8 @@ X_FRAME_OPTIONS = "DENY" # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend EMAIL_BACKEND = env( - "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend" + "DJANGO_EMAIL_BACKEND", + default="django.core.mail.backends.smtp.EmailBackend", ) # https://docs.djangoproject.com/en/dev/ref/settings/#email-timeout EMAIL_TIMEOUT = 5 diff --git a/{{cookiecutter.project_slug}}/config/settings/local.py b/{{cookiecutter.project_slug}}/config/settings/local.py index 21e6a8df..3ce150e1 100644 --- a/{{cookiecutter.project_slug}}/config/settings/local.py +++ b/{{cookiecutter.project_slug}}/config/settings/local.py @@ -69,6 +69,14 @@ if env("USE_DOCKER") == "yes": hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips] + {%- if cookiecutter.js_task_runner == 'Gulp' %} + try: + _, _, ips = socket.gethostbyname_ex("node") + INTERNAL_IPS.extend(ips) + except socket.gaierror: + # The node container isn't started (yet?) + pass + {%- endif %} {%- endif %} # django-extensions diff --git a/{{cookiecutter.project_slug}}/config/settings/production.py b/{{cookiecutter.project_slug}}/config/settings/production.py index bd0acfbd..59928f82 100644 --- a/{{cookiecutter.project_slug}}/config/settings/production.py +++ b/{{cookiecutter.project_slug}}/config/settings/production.py @@ -146,7 +146,8 @@ DEFAULT_FROM_EMAIL = env( SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix EMAIL_SUBJECT_PREFIX = env( - "DJANGO_EMAIL_SUBJECT_PREFIX", default="[{{cookiecutter.project_name}}]" + "DJANGO_EMAIL_SUBJECT_PREFIX", + default="[{{cookiecutter.project_name}}]", ) # ADMIN diff --git a/{{cookiecutter.project_slug}}/config/settings/test.py b/{{cookiecutter.project_slug}}/config/settings/test.py index 667bb20d..222597ad 100644 --- a/{{cookiecutter.project_slug}}/config/settings/test.py +++ b/{{cookiecutter.project_slug}}/config/settings/test.py @@ -15,16 +15,6 @@ SECRET_KEY = env( # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner TEST_RUNNER = "django.test.runner.DiscoverRunner" -# CACHES -# ------------------------------------------------------------------------------ -# https://docs.djangoproject.com/en/dev/ref/settings/#caches -CACHES = { - "default": { - "BACKEND": "django.core.cache.backends.locmem.LocMemCache", - "LOCATION": "", - } -} - # PASSWORDS # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers diff --git a/{{cookiecutter.project_slug}}/gulpfile.js b/{{cookiecutter.project_slug}}/gulpfile.js index 206f8a01..56a08e8f 100644 --- a/{{cookiecutter.project_slug}}/gulpfile.js +++ b/{{cookiecutter.project_slug}}/gulpfile.js @@ -29,14 +29,14 @@ function pathsConfig(appName) { const vendorsRoot = 'node_modules' return { - {% if cookiecutter.custom_bootstrap_compilation == 'y' %} + {%- if cookiecutter.custom_bootstrap_compilation == 'y' %} bootstrapSass: `${vendorsRoot}/bootstrap/scss`, vendorsJs: [ `${vendorsRoot}/jquery/dist/jquery.slim.js`, `${vendorsRoot}/popper.js/dist/umd/popper.js`, `${vendorsRoot}/bootstrap/dist/js/bootstrap.js`, ], - {% endif %} + {%- endif %} app: this.app, templates: `${this.app}/templates`, css: `${this.app}/static/css`, @@ -67,9 +67,9 @@ function styles() { return src(`${paths.sass}/project.scss`) .pipe(sass({ includePaths: [ - {% if cookiecutter.custom_bootstrap_compilation == 'y' %} + {%- if cookiecutter.custom_bootstrap_compilation == 'y' %} paths.bootstrapSass, - {% endif %} + {%- endif %} paths.sass ] }).on('error', sass.logError)) @@ -90,7 +90,7 @@ function scripts() { .pipe(dest(paths.js)) } -{% if cookiecutter.custom_bootstrap_compilation == 'y' %} +{%- if cookiecutter.custom_bootstrap_compilation == 'y' %} // Vendor Javascript minification function vendorScripts() { return src(paths.vendorsJs) @@ -101,7 +101,7 @@ function vendorScripts() { .pipe(rename({ suffix: '.min' })) .pipe(dest(paths.js)) } -{% endif %} +{%- endif %} // Image compression function imgCompression() { @@ -110,7 +110,7 @@ function imgCompression() { .pipe(dest(paths.images)) } -{% if cookiecutter.use_async == 'y' -%} +{%- if cookiecutter.use_async == 'y' -%} // Run django server function asyncRunServer() { var cmd = spawn('gunicorn', [ @@ -143,7 +143,7 @@ function initBrowserSync() { // https://www.browsersync.io/docs/options/#option-proxy {%- if cookiecutter.use_docker == 'n' %} proxy: 'localhost:8000' - {% else %} + {%- else %} proxy: { target: 'django:8000', proxyReq: [ @@ -172,7 +172,7 @@ function watchPaths() { const generateAssets = parallel( styles, scripts, - {% if cookiecutter.custom_bootstrap_compilation == 'y' %}vendorScripts,{% endif %} + {%- if cookiecutter.custom_bootstrap_compilation == 'y' %}vendorScripts,{% endif %} imgCompression ) diff --git a/{{cookiecutter.project_slug}}/local.yml b/{{cookiecutter.project_slug}}/local.yml index e285f349..6241ceed 100644 --- a/{{cookiecutter.project_slug}}/local.yml +++ b/{{cookiecutter.project_slug}}/local.yml @@ -52,7 +52,6 @@ services: ports: - "7000:7000" command: /start-docs - {%- if cookiecutter.use_mailhog == 'y' %} mailhog: @@ -75,7 +74,7 @@ services: depends_on: - redis - postgres - {% if cookiecutter.use_mailhog == 'y' -%} + {%- if cookiecutter.use_mailhog == 'y' %} - mailhog {%- endif %} ports: [] @@ -88,7 +87,7 @@ services: depends_on: - redis - postgres - {% if cookiecutter.use_mailhog == 'y' -%} + {%- if cookiecutter.use_mailhog == 'y' %} - mailhog {%- endif %} ports: [] diff --git a/{{cookiecutter.project_slug}}/production.yml b/{{cookiecutter.project_slug}}/production.yml index 93b61b13..3cccdb65 100644 --- a/{{cookiecutter.project_slug}}/production.yml +++ b/{{cookiecutter.project_slug}}/production.yml @@ -64,10 +64,9 @@ services: <<: *django image: {{ cookiecutter.project_slug }}_production_flower command: /start-flower - {%- endif %} + {%- if cookiecutter.cloud_provider == 'AWS' %} - {% if cookiecutter.cloud_provider == 'AWS' %} awscli: build: context: . diff --git a/{{cookiecutter.project_slug}}/requirements/base.txt b/{{cookiecutter.project_slug}}/requirements/base.txt index a035aeed..a4ab01b0 100644 --- a/{{cookiecutter.project_slug}}/requirements/base.txt +++ b/{{cookiecutter.project_slug}}/requirements/base.txt @@ -1,6 +1,6 @@ -pytz==2020.5 # https://github.com/stub42/pytz +pytz==2021.1 # https://github.com/stub42/pytz python-slugify==4.0.1 # https://github.com/un33k/python-slugify -Pillow==8.1.0 # https://github.com/python-pillow/Pillow +Pillow==8.2.0 # https://github.com/python-pillow/Pillow {%- if cookiecutter.use_compressor == "y" %} {%- if cookiecutter.windows == 'y' and cookiecutter.use_docker == 'n' %} rcssmin==1.0.6 --install-option="--without-c-extensions" # https://github.com/ndparker/rcssmin @@ -24,22 +24,22 @@ flower==0.9.7 # https://github.com/mher/flower {%- endif %} {%- endif %} {%- if cookiecutter.use_async == 'y' %} -uvicorn[standard]==0.13.3 # https://github.com/encode/uvicorn +uvicorn[standard]==0.13.4 # https://github.com/encode/uvicorn {%- endif %} # Django # ------------------------------------------------------------------------------ -django==3.0.11 # pyup: < 3.1 # https://www.djangoproject.com/ +django==3.1.8 # pyup: < 3.2 # https://www.djangoproject.com/ django-environ==0.4.5 # https://github.com/joke2k/django-environ django-model-utils==4.1.1 # https://github.com/jazzband/django-model-utils django-allauth==0.44.0 # https://github.com/pennersr/django-allauth -django-crispy-forms==1.10.0 # https://github.com/django-crispy-forms/django-crispy-forms +django-crispy-forms==1.11.2 # 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.12.1 # https://github.com/jazzband/django-redis {%- if cookiecutter.use_drf == "y" %} # Django REST Framework -djangorestframework==3.12.2 # https://github.com/encode/django-rest-framework +djangorestframework==3.12.4 # https://github.com/encode/django-rest-framework django-cors-headers==3.7.0 # https://github.com/adamchainz/django-cors-headers {%- endif %} diff --git a/{{cookiecutter.project_slug}}/requirements/local.txt b/{{cookiecutter.project_slug}}/requirements/local.txt index c34862a9..801c2d2d 100644 --- a/{{cookiecutter.project_slug}}/requirements/local.txt +++ b/{{cookiecutter.project_slug}}/requirements/local.txt @@ -1,7 +1,7 @@ -r base.txt Werkzeug==1.0.1 # https://github.com/pallets/werkzeug -ipdb==0.13.4 # https://github.com/gotcha/ipdb +ipdb==0.13.7 # https://github.com/gotcha/ipdb {%- if cookiecutter.use_docker == 'y' %} psycopg2==2.8.6 # https://github.com/psycopg/psycopg2 {%- else %} @@ -13,33 +13,33 @@ watchgod==0.6 # https://github.com/samuelcolvin/watchgod # Testing # ------------------------------------------------------------------------------ -mypy==0.800 # https://github.com/python/mypy +mypy==0.812 # https://github.com/python/mypy django-stubs==1.7.0 # https://github.com/typeddjango/django-stubs -pytest==6.2.2 # https://github.com/pytest-dev/pytest +pytest==6.2.3 # https://github.com/pytest-dev/pytest pytest-sugar==0.9.4 # https://github.com/Frozenball/pytest-sugar # Documentation # ------------------------------------------------------------------------------ -sphinx==3.4.3 # https://github.com/sphinx-doc/sphinx -sphinx-autobuild==2020.9.1 # https://github.com/GaretJax/sphinx-autobuild +sphinx==3.5.3 # https://github.com/sphinx-doc/sphinx +sphinx-autobuild==2021.3.14 # https://github.com/GaretJax/sphinx-autobuild # Code quality # ------------------------------------------------------------------------------ -flake8==3.8.4 # https://github.com/PyCQA/flake8 +flake8==3.9.0 # https://github.com/PyCQA/flake8 flake8-isort==4.0.0 # https://github.com/gforcada/flake8-isort -coverage==5.3.1 # https://github.com/nedbat/coveragepy +coverage==5.5 # https://github.com/nedbat/coveragepy black==20.8b1 # https://github.com/ambv/black pylint-django==2.4.2 # 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.9.3 # https://github.com/pre-commit/pre-commit +pre-commit==2.12.0 # https://github.com/pre-commit/pre-commit # Django # ------------------------------------------------------------------------------ factory-boy==3.2.0 # https://github.com/FactoryBoy/factory_boy django-debug-toolbar==3.2 # https://github.com/jazzband/django-debug-toolbar -django-extensions==3.1.0 # https://github.com/django-extensions/django-extensions +django-extensions==3.1.2 # https://github.com/django-extensions/django-extensions django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin pytest-django==4.1.0 # https://github.com/pytest-dev/pytest-django diff --git a/{{cookiecutter.project_slug}}/requirements/production.txt b/{{cookiecutter.project_slug}}/requirements/production.txt index 4a610d0c..6e566ecc 100644 --- a/{{cookiecutter.project_slug}}/requirements/production.txt +++ b/{{cookiecutter.project_slug}}/requirements/production.txt @@ -2,13 +2,13 @@ -r base.txt -gunicorn==20.0.4 # https://github.com/benoitc/gunicorn +gunicorn==20.1.0 # https://github.com/benoitc/gunicorn psycopg2==2.8.6 # https://github.com/psycopg/psycopg2 {%- if cookiecutter.use_whitenoise == 'n' %} Collectfast==2.2.0 # https://github.com/antonagestam/collectfast {%- endif %} {%- if cookiecutter.use_sentry == "y" %} -sentry-sdk==0.19.5 # https://github.com/getsentry/sentry-python +sentry-sdk==1.0.0 # https://github.com/getsentry/sentry-python {%- endif %} {%- if cookiecutter.use_docker == "n" and cookiecutter.windows == "y" %} hiredis==1.1.0 # https://github.com/redis/hiredis-py @@ -22,21 +22,21 @@ django-storages[boto3]==1.11.1 # https://github.com/jschneier/django-storages django-storages[google]==1.11.1 # https://github.com/jschneier/django-storages {%- endif %} {%- if cookiecutter.mail_service == 'Mailgun' %} -django-anymail[mailgun]==8.1 # https://github.com/anymail/django-anymail +django-anymail[mailgun]==8.2 # https://github.com/anymail/django-anymail {%- elif cookiecutter.mail_service == 'Amazon SES' %} -django-anymail[amazon_ses]==8.1 # https://github.com/anymail/django-anymail +django-anymail[amazon_ses]==8.2 # https://github.com/anymail/django-anymail {%- elif cookiecutter.mail_service == 'Mailjet' %} -django-anymail[mailjet]==8.1 # https://github.com/anymail/django-anymail +django-anymail[mailjet]==8.2 # https://github.com/anymail/django-anymail {%- elif cookiecutter.mail_service == 'Mandrill' %} -django-anymail[mandrill]==8.1 # https://github.com/anymail/django-anymail +django-anymail[mandrill]==8.2 # https://github.com/anymail/django-anymail {%- elif cookiecutter.mail_service == 'Postmark' %} -django-anymail[postmark]==8.1 # https://github.com/anymail/django-anymail +django-anymail[postmark]==8.2 # https://github.com/anymail/django-anymail {%- elif cookiecutter.mail_service == 'Sendgrid' %} -django-anymail[sendgrid]==8.1 # https://github.com/anymail/django-anymail +django-anymail[sendgrid]==8.2 # https://github.com/anymail/django-anymail {%- elif cookiecutter.mail_service == 'SendinBlue' %} -django-anymail[sendinblue]==8.1 # https://github.com/anymail/django-anymail +django-anymail[sendinblue]==8.2 # https://github.com/anymail/django-anymail {%- elif cookiecutter.mail_service == 'SparkPost' %} -django-anymail[sparkpost]==8.1 # https://github.com/anymail/django-anymail +django-anymail[sparkpost]==8.2 # https://github.com/anymail/django-anymail {%- elif cookiecutter.mail_service == 'Other SMTP' %} -django-anymail==8.1 # https://github.com/anymail/django-anymail +django-anymail==8.2 # https://github.com/anymail/django-anymail {%- endif %} diff --git a/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh b/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh index 51793484..e09ebf6f 100755 --- a/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh +++ b/{{cookiecutter.project_slug}}/utility/install_python_dependencies.sh @@ -33,9 +33,8 @@ if [ -z "$VIRTUAL_ENV" ]; then echo >&2 -e "\n" exit 1; else - pip install -r $PROJECT_DIR/requirements/local.txt - {% if cookiecutter.use_heroku == "y" -%} + {%- if cookiecutter.use_heroku == "y" -%} pip install -r $PROJECT_DIR/requirements.txt {%- endif %} fi diff --git a/{{cookiecutter.project_slug}}/utility/requirements-focal.apt b/{{cookiecutter.project_slug}}/utility/requirements-focal.apt new file mode 100644 index 00000000..fe6f21e4 --- /dev/null +++ b/{{cookiecutter.project_slug}}/utility/requirements-focal.apt @@ -0,0 +1,23 @@ +##basic build dependencies of various Django apps for Ubuntu Focal 20.04 +#build-essential metapackage install: make, gcc, g++, +build-essential +#required to translate +gettext +python3-dev + +##shared dependencies of: +##Pillow, pylibmc +zlib1g-dev + +##Postgresql and psycopg2 dependencies +libpq-dev + +##Pillow dependencies +libtiff5-dev +libjpeg8-dev +libfreetype6-dev +liblcms2-dev +libwebp-dev + +##django-extensions +graphviz-dev diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0004_alter_options_ordering_domain.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0004_alter_options_ordering_domain.py new file mode 100644 index 00000000..00c9a74d --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0004_alter_options_ordering_domain.py @@ -0,0 +1,17 @@ +# Generated by Django 3.1.7 on 2021-02-04 14:49 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('sites', '0003_set_site_domain_and_name'), + ] + + operations = [ + migrations.AlterModelOptions( + name='site', + options={'ordering': ['domain'], 'verbose_name': 'site', 'verbose_name_plural': 'sites'}, + ), + ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html index c02bd4e9..4c4745f7 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html @@ -5,5 +5,6 @@ {% block content %}

Forbidden (403)

-

CSRF verification failed. Request aborted.

-{% endblock content %}{% endraw %} +

{% if exception %}{{ exception }}{% else %}You're not allowed to access this page.{% endif %}

+{% endblock content %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html index 1687ef31..d9824185 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html @@ -5,5 +5,6 @@ {% block content %}

Page not found

-

This is not the page you were looking for.

-{% endblock content %}{% endraw %} +

{% if exception %}{{ exception }}{% else %}This is not the page you were looking for.{% endif %}

+{% endblock content %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html index 122e0813..481bb2d0 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html @@ -9,5 +9,4 @@

We track these errors automatically, but if the problem persists feel free to contact us. In the meantime, try refreshing.

{% endblock content %} - -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html index fe9b6807..0713ff11 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html @@ -9,4 +9,4 @@

{% trans "This account is inactive." %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html index cd07bbab..03c86724 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html @@ -8,4 +8,4 @@ {% endblock %} -{% endraw %} \ No newline at end of file +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html index 2b7e1278..055904ae 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html @@ -77,4 +77,4 @@ window.addEventListener('DOMContentLoaded',function() { $('.form-group').removeClass('row'); {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html index eb45cf60..2a0722a1 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html @@ -29,4 +29,4 @@ {% endif %} {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html index a7193222..247ca690 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html @@ -45,4 +45,4 @@ for a {{ site_name }} account and sign in below:{% endblocktrans %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html index baa8183c..6659724e 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html @@ -16,7 +16,5 @@ {% endif %} - - {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html index 62bbbc13..7f2fbed0 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html @@ -14,4 +14,4 @@ {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html index b9869fb2..474b0f4a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html @@ -23,4 +23,4 @@

{% blocktrans %}Please contact us if you have any trouble resetting your password.{% endblocktrans %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html index cf2129b1..94ea2dfb 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html @@ -7,11 +7,11 @@ {% block inner %}

{% trans "Password Reset" %}

- + {% if user.is_authenticated %} {% include "account/snippets/already_logged_in.html" %} {% endif %} - +

{% blocktrans %}We have sent you an e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html index 671eb12c..66ea9ba6 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html @@ -22,4 +22,4 @@ {% endif %} {% endif %} {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html index 925b7aa2..7defcc86 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html @@ -7,4 +7,4 @@

{% trans "Change Password" %}

{% trans 'Your password is now changed.' %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html index 563a0b18..435ed073 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html @@ -14,4 +14,4 @@ {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html index 80490a2e..7a1e29a4 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html @@ -20,4 +20,4 @@ {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html index eca9b156..08b93772 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html @@ -9,4 +9,4 @@

{% trans "We are sorry, but the sign up is currently closed." %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html index ccc8d9a1..4394e9c6 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html @@ -10,4 +10,4 @@

{% blocktrans %}We have sent an e-mail to you for verification. Follow the link provided to finalize the signup process. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

{% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html index f3078b68..8eafcbed 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html @@ -18,7 +18,5 @@ verification. Please click on the link inside this e-mail. Please contact us if you do not receive it within a few minutes.{% endblocktrans %}

{% blocktrans %}Note: you can still change your e-mail address.{% endblocktrans %}

- - {% endblock %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html index 12706887..c3679404 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html @@ -16,43 +16,43 @@ {% block css %} - {% endraw %}{% if cookiecutter.custom_bootstrap_compilation == "n" %}{% raw %} + {%- endraw %}{% if cookiecutter.custom_bootstrap_compilation == "n" %}{% raw %} - {% endraw %}{% endif %}{% raw %} + {%- endraw %}{% endif %}{% raw %} - {% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress css %}{% endraw %}{% endif %}{% raw %} + {%- endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress css %}{% endraw %}{% endif %}{% raw %} - {% endraw %}{% if cookiecutter.js_task_runner == "Gulp" and cookiecutter.use_compressor == "n" %}{% raw %} + {%- endraw %}{% if cookiecutter.js_task_runner == "Gulp" and cookiecutter.use_compressor == "n" %}{% raw %} - {% endraw %}{% else %}{% raw %} + {%- endraw %}{% else %}{% raw %} - {% endraw %}{% endif %}{% raw %} - {% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %} + {%- endraw %}{% endif %}{% raw %} + {%- endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %} {% endblock %} {# Placed at the top of the document so pages load faster with defer #} {% block javascript %} - {% endraw %}{% if cookiecutter.custom_bootstrap_compilation == "y" and cookiecutter.js_task_runner == "Gulp" %}{% raw %} + {%- endraw %}{% if cookiecutter.custom_bootstrap_compilation == "y" and cookiecutter.js_task_runner == "Gulp" %}{% raw %} - {% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress js %}{% endraw %}{% endif %}{% raw %} + {%- endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress js %}{% endraw %}{% endif %}{% raw %} - {% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %} - {% endraw %}{% else %}{% raw %} + {%- endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %} + {%- endraw %}{% else %}{% raw %} - {% endraw %}{% endif %}{% raw %} + {%- endraw %}{% endif %}{% raw %} - {% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress js %}{% endraw %}{% endif %}{% raw %} + {%- endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress js %}{% endraw %}{% endif %}{% raw %} - {% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %} + {%- endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% endcompress %}{% endraw %}{% endif %}{% raw %} {% endblock javascript %} @@ -121,4 +121,4 @@ {% endblock inline_javascript %} - {% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html index 94beff9c..8968a3d4 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html @@ -1 +1 @@ -{% raw %}{% extends "base.html" %}{% endraw %} \ No newline at end of file +{% raw %}{% extends "base.html" %}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html index 94beff9c..8968a3d4 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html @@ -1 +1 @@ -{% raw %}{% extends "base.html" %}{% endraw %} \ No newline at end of file +{% raw %}{% extends "base.html" %}{% endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html index 47b61122..eed39ca3 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html @@ -30,7 +30,6 @@ {% endif %} - {% endblock content %} -{% endraw %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html index e9da0d48..53e14a53 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html @@ -14,4 +14,5 @@ -{% endblock %}{% endraw %} +{% endblock %} +{%- endraw %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py index a68a94a9..8e8e3eb2 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py @@ -1,6 +1,7 @@ from django.contrib import admin from django.contrib.auth import admin as auth_admin from django.contrib.auth import get_user_model +from django.utils.translation import gettext_lazy as _ from {{ cookiecutter.project_slug }}.users.forms import UserChangeForm, UserCreationForm @@ -12,8 +13,22 @@ class UserAdmin(auth_admin.UserAdmin): form = UserChangeForm add_form = UserCreationForm - fieldsets = (("User", {"fields": ("name",)}),) + tuple( - auth_admin.UserAdmin.fieldsets + fieldsets = ( + (None, {"fields": ("username", "password")}), + (_("Personal info"), {"fields": ("name", "email")}), + ( + _("Permissions"), + { + "fields": ( + "is_active", + "is_staff", + "is_superuser", + "groups", + "user_permissions", + ), + }, + ), + (_("Important dates"), {"fields": ("last_login", "date_joined")}), ) list_display = ["username", "name", "is_superuser"] search_fields = ["name"] 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 8bd39d30..b5ccabba 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/api/serializers.py @@ -7,7 +7,7 @@ User = get_user_model() class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ["username", "email", "name", "url"] + fields = ["username", "name", "url"] extra_kwargs = { "url": {"view_name": "api:user-detail", "lookup_field": "username"} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py index 7d3a296b..80cd97ae 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py @@ -1,6 +1,5 @@ from django.contrib.auth import forms as admin_forms from django.contrib.auth import get_user_model -from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ User = get_user_model() @@ -12,20 +11,9 @@ class UserChangeForm(admin_forms.UserChangeForm): class UserCreationForm(admin_forms.UserCreationForm): - - error_message = admin_forms.UserCreationForm.error_messages.update( - {"duplicate_username": _("This username has already been taken.")} - ) - class Meta(admin_forms.UserCreationForm.Meta): model = User - def clean_username(self): - username = self.cleaned_data["username"] - - try: - User.objects.get(username=username) - except User.DoesNotExist: - return username - - raise ValidationError(self.error_messages["duplicate_username"]) + error_messages = { + "username": {"unique": _("This username has already been taken.")} + } diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py new file mode 100644 index 00000000..c50a4be4 --- /dev/null +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py @@ -0,0 +1,40 @@ +import pytest +from django.urls import reverse + +from {{ cookiecutter.project_slug }}.users.models import User + +pytestmark = pytest.mark.django_db + + +class TestUserAdmin: + def test_changelist(self, admin_client): + url = reverse("admin:users_user_changelist") + response = admin_client.get(url) + assert response.status_code == 200 + + def test_search(self, admin_client): + url = reverse("admin:users_user_changelist") + response = admin_client.get(url, data={"q": "test"}) + assert response.status_code == 200 + + def test_add(self, admin_client): + url = reverse("admin:users_user_add") + response = admin_client.get(url) + assert response.status_code == 200 + + response = admin_client.post( + url, + data={ + "username": "test", + "password1": "My_R@ndom-P@ssw0rd", + "password2": "My_R@ndom-P@ssw0rd", + }, + ) + assert response.status_code == 302 + assert User.objects.filter(username="test").exists() + + def test_view_user(self, admin_client): + user = User.objects.get(username="admin") + url = reverse("admin:users_user_change", kwargs={"object_id": user.pk}) + response = admin_client.get(url) + assert response.status_code == 200 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py index 60944ad3..92457308 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_drf_views.py @@ -28,7 +28,6 @@ class TestUserViewSet: assert response.data == { "username": user.username, - "email": user.email, "name": user.name, "url": f"http://testserver/api/users/{user.username}/", } diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py index dfa5da52..66118f49 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py @@ -1,40 +1,39 @@ +""" +Module for all Form Tests. +""" import pytest +from django.utils.translation import ugettext_lazy as _ from {{ cookiecutter.project_slug }}.users.forms import UserCreationForm -from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory +from {{ cookiecutter.project_slug }}.users.models import User pytestmark = pytest.mark.django_db class TestUserCreationForm: - def test_clean_username(self): - # A user with proto_user params does not exist yet. - proto_user = UserFactory.build() + """ + Test class for all tests related to the UserCreationForm + """ - form = UserCreationForm( - { - "username": proto_user.username, - "password1": proto_user._password, - "password2": proto_user._password, - } - ) + def test_username_validation_error_msg(self, user: User): + """ + Tests UserCreation Form's unique validator functions correctly by testing: + 1) A new user with an existing username cannot be added. + 2) Only 1 error is raised by the UserCreation Form + 3) The desired error message is raised + """ - assert form.is_valid() - assert form.clean_username() == proto_user.username - - # Creating a user. - form.save() - - # The user with proto_user params already exists, + # The user already exists, # hence cannot be created. form = UserCreationForm( { - "username": proto_user.username, - "password1": proto_user._password, - "password2": proto_user._password, + "username": user.username, + "password1": user.password, + "password2": user.password, } ) assert not form.is_valid() assert len(form.errors) == 1 assert "username" in form.errors + assert form.errors["username"][0] == _("This username has already been taken.") 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 c2fe8b51..a9349bf3 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,10 +1,11 @@ import pytest +from django.conf import settings from django.contrib import messages from django.contrib.auth.models import AnonymousUser from django.contrib.messages.middleware import MessageMiddleware from django.contrib.sessions.middleware import SessionMiddleware -from django.http.response import Http404 from django.test import RequestFactory +from django.urls import reverse from {{ cookiecutter.project_slug }}.users.forms import UserChangeForm from {{ cookiecutter.project_slug }}.users.models import User @@ -90,13 +91,7 @@ class TestUserDetailView: request.user = AnonymousUser() response = user_detail_view(request, username=user.username) + login_url = reverse(settings.LOGIN_URL) 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") + assert response.url == f"{login_url}?next=/fake-url/" diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py index 2b077d42..c7b846c0 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py @@ -25,7 +25,7 @@ class UserUpdateView(LoginRequiredMixin, SuccessMessageMixin, UpdateView): success_message = _("Information successfully updated") def get_success_url(self): - return reverse("users:detail", kwargs={"username": self.request.user.username}) + return self.request.user.get_absolute_url() # type: ignore [union-attr] def get_object(self): return self.request.user