├── .dependabot └── config.yml ├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── bug.md │ ├── feature.md │ ├── improvement.md │ └── regression.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .pyup.yml ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTING.rst ├── CONTRIBUTORS.md ├── README.md ├── cookiecutter.json ├── hooks └── pre_gen_project.py ├── installers ├── ubuntu │ ├── install_postgresql_12_ubuntu.sh │ ├── install_postgresql_13_ubuntu.sh │ └── install_vscode_ubuntu.sh └── windows │ └── install_chocolatey_windows10.bat ├── pyproject.toml ├── pytest.ini ├── requirements-on-book.txt ├── requirements.txt ├── setup.py ├── tests ├── test_bare.sh └── test_cookiecutter_generation.py ├── tox.ini └── {{cookiecutter.project_slug}} ├── .coveragerc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitlab-ci.yml ├── .pre-commit-config.yaml ├── .pylintrc ├── .travis.yml ├── CONTRIBUTORS.md ├── COPYING ├── README.md ├── config ├── __init__.py ├── asgi.py ├── settings │ ├── __init__.py │ ├── base.py │ ├── local.py │ ├── production.py │ └── test.py ├── urls.py └── wsgi.py ├── env.sample.mac_or_linux ├── env.sample.windows ├── locale └── README.rst ├── manage.py ├── pytest.ini ├── requirements.txt ├── requirements ├── base.txt ├── local.txt └── production.txt ├── setup.cfg └── {{cookiecutter.project_slug}} ├── __init__.py ├── conftest.py ├── contrib ├── __init__.py └── sites │ ├── __init__.py │ └── migrations │ ├── 0001_initial.py │ ├── 0002_alter_domain_unique.py │ ├── 0003_set_site_domain_and_name.py │ └── __init__.py ├── static ├── css │ └── project.css ├── fonts │ └── .gitkeep ├── images │ └── favicons │ │ └── favicon.ico ├── js │ └── project.js └── sass │ ├── custom_bootstrap_vars.scss │ └── project.scss ├── templates ├── 403.html ├── 404.html ├── 500.html ├── account │ ├── account_inactive.html │ ├── base.html │ ├── email.html │ ├── email_confirm.html │ ├── login.html │ ├── logout.html │ ├── password_change.html │ ├── password_reset.html │ ├── password_reset_done.html │ ├── password_reset_from_key.html │ ├── password_reset_from_key_done.html │ ├── password_set.html │ ├── signup.html │ ├── signup_closed.html │ ├── verification_sent.html │ └── verified_email_required.html ├── base.html ├── pages │ ├── about.html │ └── home.html └── users │ ├── user_detail.html │ └── user_form.html ├── users ├── __init__.py ├── adapters.py ├── admin.py ├── apps.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── tests │ ├── __init__.py │ ├── factories.py │ ├── test_forms.py │ ├── test_models.py │ ├── test_urls.py │ └── test_views.py ├── urls.py └── views.py └── utils ├── __init__.py └── context_processors.py /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | update_configs: 3 | - package_manager: "python" 4 | directory: "/" 5 | update_schedule: "weekly" 6 | default_labels: 7 | - "dependency update" 8 | default_assignees: 9 | - "luzfcb" 10 | - package_manager: "python" 11 | directory: "/{{cookiecutter.project_slug}}/requirements" 12 | update_schedule: "weekly" 13 | default_labels: 14 | - "dependency update" 15 | default_assignees: 16 | - "luzfcb" 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.{html,css,scss,json,yml}] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [Makefile] 23 | indent_style = tab 24 | 25 | [nginx.conf] 26 | indent_style = space 27 | indent_size = 2 28 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## [Make sure to follow one of the issue templates we've got](https://github.com/pydanny/cookiecutter-rwd/issues/new/choose), otherwise the issue might be closed immeditely 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug 4 | --- 5 | 6 | ## What happened? 7 | 8 | 9 | 10 | 11 | ## What should've happened instead? 12 | 13 | 14 | 15 | 16 | ## Steps to reproduce 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Feature Proposal 3 | about: Propose a new feature 4 | --- 5 | 6 | ## Description 7 | 8 | 9 | 10 | 11 | 12 | 13 | ## Rationale 14 | 15 | 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/improvement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Improvement Suggestion 3 | about: Let us know how we could improve 4 | --- 5 | 6 | ## Description 7 | 8 | 9 | 10 | ## Rationale 11 | 12 | 13 | 14 | ## Use case(s) / visualization(s) 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/regression.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Regression Report 3 | about: Let us know if something that'd been working has broke 4 | --- 5 | 6 | ## What happened before? 7 | 8 | 9 | 10 | 11 | ## What happens now? 12 | 13 | 14 | 15 | 16 | ## Last stable commit / Since when? 17 | 18 | 19 | 20 | 21 | ## Steps to reproduce 22 | 23 | [//]: # (Any or all of the following:) 24 | [//]: # (* Host system configuration: OS, Docker & friends' versions etc.) 25 | [//]: # (* Project generation options) 26 | [//]: # (* Logs) 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | [//]: # (Thank you for helping us out: your efforts mean great deal to the project and the community as a whole!) 2 | 3 | [//]: # (Before you proceed:) 4 | 5 | [//]: # (1. Make sure to add yourself to `CONTRIBUTORS.md` through this PR provided you're contributing here for the first time) 6 | [//]: # (2. Don't forget to update the `docs/` presuming others would benefit from a concise description of whatever that you're proposing) 7 | 8 | 9 | ## Description 10 | 11 | [//]: # (What's it you're proposing?) 12 | 13 | 14 | 15 | 16 | ## Rationale 17 | 18 | [//]: # (Why does the project need that?) 19 | 20 | 21 | 22 | 23 | ## Use case(s) / visualization(s) 24 | 25 | [//]: # ("Better to see something once than to hear about it a thousand times.") 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage.* 42 | .cache/ 43 | nosetests.xml 44 | coverage.xml 45 | *.cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # pyenv 62 | .python-version 63 | 64 | # celery beat schedule file 65 | celerybeat-schedule 66 | 67 | # Environments 68 | .env 69 | .venv 70 | env/ 71 | venv/ 72 | ENV/ 73 | 74 | # Rope project settings 75 | .ropeproject 76 | 77 | # mkdocs documentation 78 | /site 79 | 80 | # mypy 81 | .mypy_cache/ 82 | 83 | 84 | ### Linux template 85 | *~ 86 | 87 | # temporary files which can be created if a process still has a handle open of a deleted file 88 | .fuse_hidden* 89 | 90 | # KDE directory preferences 91 | .directory 92 | 93 | # Linux trash folder which might appear on any partition or disk 94 | .Trash-* 95 | 96 | # .nfs files are created when an open file is removed but is still being accessed 97 | .nfs* 98 | 99 | 100 | ### VisualStudioCode template 101 | .vscode/* 102 | !.vscode/settings.json 103 | !.vscode/tasks.json 104 | !.vscode/launch.json 105 | !.vscode/extensions.json 106 | 107 | 108 | ### Windows template 109 | # Windows thumbnail cache files 110 | Thumbs.db 111 | ehthumbs.db 112 | ehthumbs_vista.db 113 | 114 | # Dump file 115 | *.stackdump 116 | 117 | # Folder config file 118 | Desktop.ini 119 | 120 | # Recycle Bin used on file shares 121 | $RECYCLE.BIN/ 122 | 123 | # Windows Installer files 124 | *.cab 125 | *.msi 126 | *.msm 127 | *.msp 128 | 129 | # Windows shortcuts 130 | *.lnk 131 | 132 | 133 | ### SublimeText template 134 | # Cache files for Sublime Text 135 | *.tmlanguage.cache 136 | *.tmPreferences.cache 137 | *.stTheme.cache 138 | 139 | # Workspace files are user-specific 140 | *.sublime-workspace 141 | 142 | # Project files should be checked into the repository, unless a significant 143 | # proportion of contributors will probably not be using Sublime Text 144 | # *.sublime-project 145 | 146 | # SFTP configuration file 147 | sftp-config.json 148 | 149 | # Package control specific files 150 | Package Control.last-run 151 | Package Control.ca-list 152 | Package Control.ca-bundle 153 | Package Control.system-ca-bundle 154 | Package Control.cache/ 155 | Package Control.ca-certs/ 156 | Package Control.merged-ca-bundle 157 | Package Control.user-ca-bundle 158 | oscrypto-ca-bundle.crt 159 | bh_unicode_properties.cache 160 | 161 | # Sublime-github package stores a github token in this file 162 | # https://packagecontrol.io/packages/sublime-github 163 | GitHub.sublime-settings 164 | 165 | 166 | ### macOS template 167 | # General 168 | *.DS_Store 169 | .AppleDouble 170 | .LSOverride 171 | 172 | # Icon must end with two \r 173 | Icon 174 | 175 | # Thumbnails 176 | ._* 177 | 178 | # Files that might appear in the root of a volume 179 | .DocumentRevisions-V100 180 | .fseventsd 181 | .Spotlight-V100 182 | .TemporaryItems 183 | .Trashes 184 | .VolumeIcon.icns 185 | .com.apple.timemachine.donotpresent 186 | 187 | # Directories potentially created on remote AFP share 188 | .AppleDB 189 | .AppleDesktop 190 | Network Trash Folder 191 | Temporary Items 192 | .apdisk 193 | 194 | 195 | ### Vim template 196 | # Swap 197 | [._]*.s[a-v][a-z] 198 | [._]*.sw[a-p] 199 | [._]s[a-v][a-z] 200 | [._]sw[a-p] 201 | 202 | # Session 203 | Session.vim 204 | 205 | # Temporary 206 | .netrwhist 207 | # Auto-generated tag files 208 | tags 209 | 210 | 211 | ### VirtualEnv template 212 | # Virtualenv 213 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 214 | [Bb]in 215 | [Ii]nclude 216 | [Ll]ib 217 | [Ll]ib64 218 | [Ss]cripts 219 | pyvenv.cfg 220 | pip-selfcheck.json 221 | 222 | 223 | # Even though the project might be opened and edited 224 | # in any of the JetBrains IDEs, it makes no sence whatsoever 225 | # to 'run' anything within it since any particular cookiecutter 226 | # is declarative by nature. 227 | .idea/ 228 | 229 | .pytest_cache/ 230 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # autogenerated pyup.io config file 2 | # see https://pyup.io/docs/configuration/ for all available options 3 | 4 | update: true 5 | #schedule: "every week" 6 | label_prs: "dependency update" 7 | assignees: 8 | - luzfcb 9 | 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: bionic 3 | 4 | services: 5 | - docker 6 | 7 | language: python 8 | 9 | python: 3.8 10 | 11 | before_install: 12 | - docker-compose -v 13 | - docker -v 14 | 15 | jobs: 16 | include: 17 | - name: Test results 18 | script: tox -e py38 19 | - name: Black template 20 | script: tox -e black-template 21 | - name: Bare metal with Postgresql 22 | script: sh tests/test_bare.sh database=PostgreSQL windows=n 23 | before_script: 24 | - psql -c 'create database everycheese;' -U postgres 25 | services: 26 | - postgresql 27 | - redis 28 | - name: Bare metal with SQLite 29 | script: sh tests/test_bare.sh database=SQLite windows=n 30 | services: 31 | - redis 32 | 33 | install: 34 | - pip install tox 35 | 36 | notifications: 37 | email: 38 | on_success: change 39 | on_failure: always 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All enhancements and patches to Cookiecutter Django will be documented in this file. 3 | 4 | ## [2020-09-28] 5 | ### Changed 6 | - Updated pytest to 6.1.0 - [@luzfcb](https://github.com/luzfcb) 7 | 8 | ## [2020-09-24] 9 | ### Changed 10 | - Added a small script to install Postgresql 13 on Ubuntu - [@luzfcb](https://github.com/luzfcb) 11 | 12 | ## [2020-09-22] 13 | ### Changed 14 | - Updated django-debug-toolbar to 3.1 - [@luzfcb](https://github.com/luzfcb) 15 | - Updated django-extensions to 3.0.9 - [@luzfcb](https://github.com/luzfcb) 16 | 17 | 18 | ## [2020-09-16] 19 | ### Changed 20 | - Updated coverage to 5.3 - [@luzfcb](https://github.com/luzfcb) 21 | - Removed the coverage workaround on config/settings/test.py. The coverage 5.3 fixed the issue - [@luzfcb](https://github.com/luzfcb) 22 | - Updated pytest-django to 3.10.0 - [@luzfcb](https://github.com/luzfcb) 23 | - Updated django-redis to 4.12.1 - [@luzfcb](https://github.com/luzfcb) 24 | 25 | ## [2020-09-15] 26 | ### Changed 27 | - Updated django-anymail to 8.0 - [@luzfcb](https://github.com/luzfcb) 28 | - Updated pytest to 6.0.2 - [@luzfcb](https://github.com/luzfcb) 29 | 30 | ## [2020-09-11] 31 | ### Changed 32 | - Updated factory-boy to 3.0.1 - [@luzfcb](https://github.com/luzfcb) 33 | - Updated project compatibility with factory-boy v3.0.1 - [@luzfcb](https://github.com/luzfcb) 34 | - Updated black to 20.8b1 - [@luzfcb](https://github.com/luzfcb) 35 | - Updated ipdb to 0.13.3 - [@luzfcb](https://github.com/luzfcb) 36 | - Updated psycopg2-binary to 2.8.6 - [@luzfcb](https://github.com/luzfcb) 37 | - Updated pytest to 6.0.1 - [@luzfcb](https://github.com/luzfcb) 38 | - Updated pytest-sugar to 0.9.4 - [@luzfcb](https://github.com/luzfcb) 39 | - Updated flake8 to 3.8.3 - [@luzfcb](https://github.com/luzfcb) 40 | - Updated coverage to 5.2.1 - [@luzfcb](https://github.com/luzfcb) 41 | - Updated pylint-django to 2.3.0 - [@luzfcb](https://github.com/luzfcb) 42 | - Updated pre-commit to 2.7.1 - [@luzfcb](https://github.com/luzfcb) 43 | - Updated django-extensions to 3.0.8 - [@luzfcb](https://github.com/luzfcb) 44 | 45 | ## [2020-09-10] 46 | ### Changed 47 | - Added a workaround on config/settings/test.py to fix the coverage.py + django_coverage_plugin "Can't add file tracer data for unmeasured file" warning message (https://github.com/feldroy/django-crash-course/issues/329) - [@luzfcb](https://github.com/luzfcb) 48 | 49 | ## [2020-09-09] 50 | ### Changed 51 | - Removed env.sample file - [@luzfcb](https://github.com/luzfcb) 52 | - Updated basic instructions on the README.md - [@luzfcb](https://github.com/luzfcb) 53 | - Updated project to Django 3.1 - [@luzfcb](https://github.com/luzfcb) 54 | - Synced contrib/sites app 0001_initial migration to follow Django 3.1 changes - [@luzfcb](https://github.com/luzfcb) 55 | - Synced users app 0001_initial migration to follow Django 3.1 changes - [@luzfcb](https://github.com/luzfcb) 56 | - Updated black to 20.8b1 - [@luzfcb](https://github.com/luzfcb) 57 | - Updated project compatibility with black v20.8b1 - [@luzfcb](https://github.com/luzfcb) 58 | - Updated tox to 3.20.0 - [@luzfcb](https://github.com/luzfcb) 59 | - Updated pytest-xdist to 2.1.0 - [@luzfcb](https://github.com/luzfcb) 60 | - Updated pytest to 6.0.1 - [@luzfcb](https://github.com/luzfcb) 61 | - Updated psycopg2 to 2.8.6 - [@luzfcb](https://github.com/luzfcb) 62 | - Updated sh to 1.14.0 - [@luzfcb](https://github.com/luzfcb) 63 | 64 | 65 | ## [2020-09-08] 66 | ### Changed 67 | - Added env.sample.mac_or_linux and env.sample.windows files - [@pydanny](https://github.com/pydanny) 68 | - Added .env file on gitignore - [@pydanny](https://github.com/pydanny) 69 | 70 | ## [2020-08-28] 71 | ### Changed 72 | - Added basic instructions on the README.md - [@luzfcb](https://github.com/luzfcb) 73 | 74 | ## [2020-08-10] 75 | ### Changed 76 | - Updated whitenoise to 5.2.0 - [@luzfcb](https://github.com/luzfcb) 77 | - Updated tox to 3.19.0 - [@luzfcb](https://github.com/luzfcb) 78 | - Updated django-anymail to 7.2.1 - [@luzfcb](https://github.com/luzfcb) 79 | 80 | 81 | ## [2020-08-03] 82 | ### Changed 83 | - Updated Django to 3.0.9 - [@luzfcb](https://github.com/luzfcb) 84 | - Updated tox to 3.18.1 - [@luzfcb](https://github.com/luzfcb) 85 | - Updated pytest-xdist to 1.34.0 - [@luzfcb](https://github.com/luzfcb) 86 | 87 | ## [2020-07-27] 88 | ### Changed 89 | - Updated django-autoslug to 1.9.8 - [@luzfcb](https://github.com/luzfcb) 90 | - Updated tox to 3.18.0 - [@luzfcb](https://github.com/luzfcb) 91 | - Updated django-anymail to 7.2 - [@luzfcb](https://github.com/luzfcb) 92 | - Updated django-autoslug to 1.9.8 - [@luzfcb](https://github.com/luzfcb) 93 | 94 | ## [2020-07-17] 95 | ### Changed 96 | - Ignore sqlite database on git - [@luzfcb](https://github.com/luzfcb) 97 | 98 | ## [2020-07-09] 99 | ### Changed 100 | - Fix the Sqlite database filename when generate the project with `windows=y` - [@luzfcb](https://github.com/luzfcb) 101 | 102 | ## [2020-07-08] 103 | ### Changed 104 | - Explicitly define which settings variables should be included in the template context - [@pydanny](https://github.com/pydanny) 105 | 106 | ## [2020-07-06] 107 | ### Changed 108 | - Automatically read an .env file, if it exists in the project's root path - [@luzfcb](https://github.com/luzfcb) 109 | - Updated tox to 3.16.1 - [@luzfcb](https://github.com/luzfcb) 110 | - Updated pillow to 7.2.0 - [@luzfcb](https://github.com/luzfcb) 111 | - Updated python-slugify to 4.0.1 - [@luzfcb](https://github.com/luzfcb) 112 | - Updated Django to 3.0.8 - [@luzfcb](https://github.com/luzfcb) 113 | 114 | ## [2020-07-02] 115 | ### Changed 116 | - Migrate to pathlib - [@luzfcb](https://github.com/luzfcb) 117 | 118 | ## [2020-06-29] 119 | ### Changed 120 | - Updated tox to 3.16.0 - [@luzfcb](https://github.com/luzfcb) 121 | 122 | ## [2020-06-22] 123 | ### Changed 124 | - Switch to dark navbar - [@pydanny](https://github.com/pydanny) 125 | 126 | ## [2020-06-18] 127 | ### Changed 128 | - Updated flake8 to 3.8.3 - [@luzfcb](https://github.com/luzfcb) 129 | - Updated pytest to 5.4.3 - [@luzfcb](https://github.com/luzfcb) 130 | 131 | ## [2020-06-12] 132 | ### Changed 133 | - Small fixes in the .coveragerc file - [@pydanny](https://github.com/pydanny) 134 | 135 | 136 | ## [2020-06-01] 137 | ### Changed 138 | - Fixed the project generation when select database=SQLite and windows=n - [@luzfcb](https://github.com/luzfcb) 139 | - Applyed black formatting on production.py - [@luzfcb](https://github.com/luzfcb) 140 | - Small fixes in the project generation tests - [@luzfcb](https://github.com/luzfcb) 141 | - Updated pytest to 5.4.2 - [@luzfcb](https://github.com/luzfcb) 142 | - Updated flake8 to 3.8.2 - [@luzfcb](https://github.com/luzfcb) 143 | - Updated pylint-django to 2.0.15 - [@luzfcb](https://github.com/luzfcb) 144 | - Updated pre-commit to 2.4.0 - [@luzfcb](https://github.com/luzfcb) 145 | 146 | ## [2020-05-09] 147 | ### Changed 148 | - Windows must use Sqlite - [@pydanny](https://github.com/pydanny) 149 | 150 | ## [2020-04-29] 151 | ### Changed 152 | - Added a small batch script to install Chocolatey on Windows - [@luzfcb](https://github.com/luzfcb) 153 | - Fixed a small code style issue on UserDetailView [@luzfcb](https://github.com/luzfcb) 154 | - Added comments about slug_field and slug_url_kwarg usage on UserDetailView class - [@pydanny](https://github.com/pydanny) 155 | - Updated mypy to 0.770 [@luzfcb](https://github.com/luzfcb) 156 | - Removed django-compressor to due install problems on Windows 10 [@luzfcb](https://github.com/luzfcb) 157 | 158 | ## [2020-04-03] 159 | ### Changed 160 | - Added a small script to install Visual Studio Code on Ubuntu [@luzfcb](https://github.com/luzfcb) 161 | - Added a small script to install Postgresql 12 on Ubuntu [@luzfcb](https://github.com/luzfcb) 162 | - Explained the behavior when DATABASE_URL is not found [@luzfcb](https://github.com/luzfcb) 163 | - Renamed ROOT_DIR to BASE_DIR to follow the django `startproject` nomenclature [@luzfcb](https://github.com/luzfcb) 164 | - Replaced the usage of ugettext_lazy by gettext_lazy [@luzfcb](https://github.com/luzfcb) 165 | - Added Django 3.0 asgi.py file [@luzfcb](https://github.com/luzfcb) 166 | - Added pyproject.toml to configure black [@luzfcb](https://github.com/luzfcb) 167 | - Formatted the code with black following the rules defined on pyproject.toml [@luzfcb](https://github.com/luzfcb) 168 | - Fixed some flake8 formatting issues [@luzfcb](https://github.com/luzfcb) 169 | - Fixed and improve the test suite [@luzfcb](https://github.com/luzfcb) 170 | - Updated .travis.yml to follow the new Travis cfg standard [@luzfcb](https://github.com/luzfcb) 171 | - Configured travis-ci to use Ubuntu 18.04 instead of 16.04 [@luzfcb](https://github.com/luzfcb) 172 | - Fixed formatting issues on DATABASES section to be able to generate project compliant with pep8 [@luzfcb](https://github.com/luzfcb) 173 | - Added Travis-ci Badge [@luzfcb](https://github.com/luzfcb) 174 | - Updated project configurations to use Python 3.8 [@luzfcb](https://github.com/luzfcb) 175 | - Updated django 3.0.4 [@luzfcb](https://github.com/luzfcb) 176 | - Updated pre-commit to 2.2.0 [@luzfcb](https://github.com/luzfcb) 177 | - Updated mypy to 0.661 [@luzfcb](https://github.com/luzfcb) 178 | - Updated werkzeug to 1.0.0 [@luzfcb](https://github.com/luzfcb) 179 | - Updated django-debug-toolbar to 2.2 [@luzfcb](https://github.com/luzfcb) 180 | - Updated pytest to 5.3.5 [@luzfcb](https://github.com/luzfcb) 181 | - Updated pylint-django to 2.0.14 [@luzfcb](https://github.com/luzfcb) 182 | - Updated django-crispy-forms to 1.9.0 [@luzfcb](https://github.com/luzfcb) 183 | - Updated django-debug-toolbar to 2.2 [@luzfcb](https://github.com/luzfcb) 184 | - Updated django-extensions to 2.2.8 [@luzfcb](https://github.com/luzfcb) 185 | - Updated ipdb to 0.13.2 [@luzfcb](https://github.com/luzfcb) 186 | - Updated collectfast to 2.1.0 [@luzfcb](https://github.com/luzfcb) 187 | 188 | ## [2020-03-05] 189 | ### Changed 190 | - Fixed misformatted unidecode version number - [@luzfcb](https://github.com/luzfcb) 191 | - Added a link to django-autoslug repository on the base.txt - [@luzfcb](https://github.com/luzfcb) 192 | - Added dependabot configuration file - [@luzfcb](https://github.com/luzfcb) 193 | - Removed PyUP configuration file - [@luzfcb](https://github.com/luzfcb) 194 | 195 | ## [2020-02-18] 196 | ### Changed 197 | - Fixed UserUpdateView - [@quique](https://github.com/quique) 198 | 199 | ## [2020-01-30] 200 | ### Changed 201 | - Added missing imports, and got SQLite to run - [@pydanny](https://github.com/pydanny) 202 | - Converted CONTRIBUTORS.rst to markdown - [@pydanny](https://github.com/pydanny) 203 | - Removed bio from models - [@edvm](https://github.com) 204 | 205 | ## [2020-01-28] 206 | ### Changed 207 | - Hard fork of Cookiecutter-Django project - [@pydanny](https://github.com/pydanny) 208 | 209 | ## Before Hard Fork of CookiecutterDjango 210 | 211 | [Cookiecutter Django Changelog](https://github.com/pydanny/cookiecutter-django/blob/master/CHANGELOG.md) 212 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | 4 | Always happy to get issues identified and pull requests! 5 | 6 | ## Getting your pull request merged in 7 | 8 | 1. New features will generally be rejected. This is a Cookiecutter 9 | template for a tutorial book, so we\'re keeping the scope small. 10 | 2. Keep it small. The smaller the pull request the more likely I\'ll 11 | pull it in. 12 | 3. Pull requests that fix a current issue get priority for review. 13 | 4. If you\'re not already in the [CONTRIBUTORS.md]{.title-ref} file, 14 | add yourself! 15 | 16 | ## Testing 17 | 18 | ### Installation 19 | 20 | Please install [tox](https://tox.readthedocs.io/en/latest/), which is a 21 | generic virtualenv management and test command line tool. 22 | 23 | [tox](https://tox.readthedocs.io/en/latest/) is available for download 24 | from [PyPI](https://pypi.python.org/pypi) via 25 | [pip](https://pypi.python.org/pypi/pip/): 26 | 27 | pip install tox 28 | 29 | It will automatically create a fresh virtual environment and install our 30 | test dependencies, such as 31 | [pytest-cookies](https://pypi.python.org/pypi/pytest-cookies/) and 32 | [flake8](https://pypi.python.org/pypi/flake8/). 33 | 34 | ### Run the Tests 35 | 36 | Tox uses py.test under the hood, hence it supports the same syntax for 37 | selecting tests. 38 | 39 | For further information please consult the [pytest usage 40 | docs](https://pytest.org/latest/usage.html#specifying-tests-selecting-tests). 41 | 42 | To run all tests using various versions of python in virtualenvs defined 43 | in tox.ini, just run tox.: 44 | 45 | tox 46 | 47 | It is possible to test with a specific version of python. To do this, 48 | the command is: 49 | 50 | tox -e py38 51 | 52 | This will run py.test with the python3.7 interpreter, for example. 53 | 54 | To run a particular test with tox for against your current Python 55 | version: 56 | 57 | tox -e py -- -k test_default_configuration 58 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | How to Contribute 2 | ================= 3 | 4 | Always happy to get issues identified and pull requests! 5 | 6 | Getting your pull request merged in 7 | ------------------------------------ 8 | 9 | #. New features will generally be rejected. This is a Cookiecutter template for a tutorial book, so we're keeping the scope small. 10 | #. Keep it small. The smaller the pull request the more likely I'll pull it in. 11 | #. Pull requests that fix a current issue get priority for review. 12 | #. If you're not already in the `CONTRIBUTORS.md` file, add yourself! 13 | 14 | Testing 15 | ------- 16 | 17 | Installation 18 | ~~~~~~~~~~~~ 19 | 20 | Please install `tox`_, which is a generic virtualenv management and test command line tool. 21 | 22 | `tox`_ is available for download from `PyPI`_ via `pip`_:: 23 | 24 | pip install tox 25 | 26 | It will automatically create a fresh virtual environment and install our test dependencies, 27 | such as `pytest-cookies`_ and `flake8`_. 28 | 29 | Run the Tests 30 | ~~~~~~~~~~~~~ 31 | 32 | Tox uses py.test under the hood, hence it supports the same syntax for selecting tests. 33 | 34 | For further information please consult the `pytest usage docs`_. 35 | 36 | To run all tests using various versions of python in virtualenvs defined in tox.ini, just run tox.:: 37 | 38 | tox 39 | 40 | It is possible to test with a specific version of python. To do this, the command 41 | is:: 42 | 43 | tox -e py38 44 | 45 | This will run py.test with the python3.7 interpreter, for example. 46 | 47 | To run a particular test with tox for against your current Python version:: 48 | 49 | tox -e py -- -k test_default_configuration 50 | 51 | .. _`pytest usage docs`: https://pytest.org/latest/usage.html#specifying-tests-selecting-tests 52 | .. _`tox`: https://tox.readthedocs.io/en/latest/ 53 | .. _`pip`: https://pypi.python.org/pypi/pip/ 54 | .. _`pytest-cookies`: https://pypi.python.org/pypi/pytest-cookies/ 55 | .. _`flake8`: https://pypi.python.org/pypi/flake8/ 56 | .. _`PyPI`: https://pypi.python.org/pypi 57 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | ## Core Developers 4 | 5 | These contributors have commit flags for the repository, and are able to accept and merge pull requests. 6 | 7 | |Name|Github| 8 | |---|---| 9 | |Daniel Feldroy|[\@pydanny](https://github.com/pydanny)| 10 | |Audrey Feldroy|[\@audreyr](https://github.com/audreyr)| 11 | 12 | *Audrey is also the creator of Cookiecutter. Audrey and Daniel are on the Cookiecutter core team.* 13 | 14 | ## Other Code Contributors 15 | 16 | - [Modasser Billah](https://github.com/modasserbillah) 17 | - [Enrique Matías Sánchez](https://github.com/quique) 18 | 19 | Is this you? 20 | 21 | 22 | ## Issue Contributors 23 | 24 | These individuals report issues that were closed after code changes were 25 | made. 26 | 27 | - [edvm](https://github.com/edvm) 28 | - [bmcneill](https://github.com/bfmcneill) 29 | - [Nikita Shupeyko](https://github.com/webyneter) 30 | 31 | From before the fork, the entire [Cookiecutter-Django team](https://github.com/pydanny/cookiecutter-django). 32 | 33 | |Emiliano Dalla Verde Marcozzi|[\@edvm](https://github.com/edvm)| 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | django-crash-starter 2 | ==================== 3 | 4 | [![Build Status](https://travis-ci.com/feldroy/django-crash-starter.svg?branch=master)](https://travis-ci.com/feldroy/django-crash-starter) 5 | 6 | 7 | 8 | Powered by [Cookiecutter](https://github.com/cookiecutter/cookiecutter), django-crash-starter is the project template 9 | for the [Django Crash Course tutorial book](https://www.feldroy.com/products/django-crash-course) by Daniel and Audrey Feldroy. 10 | 11 | [![Django Crash Course: Covers Django 3.0 and Python 3.8](https://cdn.shopify.com/s/files/1/0304/6901/files/Django-Crash-Course-300x436.jpg)](https://www.feldroy.com/products/django-crash-course) 12 | 13 | Features 14 | -------- 15 | 16 | - For Django 3+ 17 | - Works with Python 3.8+ 18 | - Renders Django projects with 100% starting test coverage 19 | - Twitter [Bootstrap](https://github.com/twbs/bootstrap) v4 20 | - [12-Factor](http://12factor.net/) based settings via 21 | [django-environ](https://github.com/joke2k/django-environ) 22 | - Secure by default. We believe in SSL/TLS. 23 | - Optimized development and production settings 24 | - Registration via 25 | [django-allauth](https://github.com/pennersr/django-allauth) 26 | - Comes with custom user model ready to go 27 | - Media storage using whitenoise 28 | - Run tests with unittest or pytest 29 | - PostgreSQL / SQLite3 30 | - Default integration with 31 | [pre-commit](https://github.com/pre-commit/pre-commit) for 32 | identifying simple issues before submission to code review 33 | 34 | Constraints 35 | ----------- 36 | 37 | - Very small scope. New feature pull requests will generally be rejected. 38 | - Only maintained 3rd party libraries are used. 39 | - Environment variables for configuration 40 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_name": "EveryCheese", 3 | "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}", 4 | "description": "The Ultimate Cheese Index!", 5 | "author_name": "Your name here", 6 | "domain_name": "everycheese.com", 7 | "email": "{{ cookiecutter.author_name.lower()|replace(' ', '-') }}@example.com", 8 | "timezone": "UTC", 9 | "windows": "n", 10 | "database": [ 11 | "PostgreSQL", 12 | "SQLite" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /hooks/pre_gen_project.py: -------------------------------------------------------------------------------- 1 | project_slug = "{{ cookiecutter.project_slug }}" 2 | if hasattr(project_slug, "isidentifier"): 3 | assert ( 4 | project_slug.isidentifier() 5 | ), "'{}' project slug is not a valid Python identifier.".format( 6 | project_slug 7 | ) 8 | 9 | assert ( 10 | project_slug == project_slug.lower() 11 | ), "'{}' project slug should be all lowercase".format(project_slug) 12 | 13 | assert ( 14 | "\\" not in "{{ cookiecutter.author_name }}" 15 | ), "Don't include backslashes in author name." 16 | -------------------------------------------------------------------------------- /installers/ubuntu/install_postgresql_12_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Based on https://www.postgresql.org/download/linux/ubuntu/ 5 | # 6 | 7 | sudo echo "" && wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - \ 8 | && sudo sh -c 'echo "deb [arch=amd64] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' \ 9 | && sudo apt-get update \ 10 | && sudo apt-get install --yes postgresql-12 libpq-dev 11 | -------------------------------------------------------------------------------- /installers/ubuntu/install_postgresql_13_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Based on https://www.postgresql.org/download/linux/ubuntu/ 5 | # 6 | 7 | sudo echo "" && wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - \ 8 | && sudo sh -c 'echo "deb [arch=amd64] http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' \ 9 | && sudo apt-get update \ 10 | && sudo apt-get install --yes postgresql-13 libpq-dev 11 | -------------------------------------------------------------------------------- /installers/ubuntu/install_vscode_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Based on https://code.visualstudio.com/docs/setup/linux#_debian-and-ubuntu-based-distributions 5 | # 6 | sudo echo "" && sudo apt-get update \ 7 | && sudo apt-get install --yes apt-transport-https curl \ 8 | && wget --quiet -O - https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg \ 9 | && sudo install -o root -g root -m 644 packages.microsoft.gpg /usr/share/keyrings/ \ 10 | && sudo sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list' \ 11 | && sudo apt-get update \ 12 | && sudo apt-get install --yes code 13 | -------------------------------------------------------------------------------- /installers/windows/install_chocolatey_windows10.bat: -------------------------------------------------------------------------------- 1 | 2 | 3 | @"%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -NoProfile -InputFormat None -ExecutionPolicy Bypass -Command "iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))" && SET "PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin" 4 | 5 | 6 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 67 3 | skip-string-normalization = true 4 | target-version = ['py38'] 5 | exclude = "(.*/migrations/.*)" 6 | 7 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = -v -x --tb=short 3 | python_paths = . 4 | norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/* 5 | -------------------------------------------------------------------------------- /requirements-on-book.txt: -------------------------------------------------------------------------------- 1 | # Dependencies that was manually added on the book 2 | django-countries==6.1.3 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cookiecutter==1.7.2 2 | sh==1.14.0 3 | binaryornot==0.4.4 4 | 5 | # Code quality 6 | # ------------------------------------------------------------------------------ 7 | black==20.8b1 8 | flake8==3.8.3 9 | 10 | # Testing 11 | # ------------------------------------------------------------------------------ 12 | tox==3.20.0 13 | pytest==6.1.0 14 | pytest-cookies==0.5.1 15 | pytest-xdist==2.1.0 16 | pyyaml==5.3.1 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | 6 | try: 7 | from setuptools import setup 8 | except ImportError: 9 | from distutils.core import setup 10 | 11 | # Our version ALWAYS matches the version of Django we support 12 | # If Django has a new release, we branch, tag, then update this setting after the tag. 13 | version = "3" 14 | 15 | if sys.argv[-1] == "tag": 16 | os.system(f'git tag -a {version} -m "version {version}"') 17 | os.system("git push --tags") 18 | sys.exit() 19 | 20 | with open("README.rst") as readme_file: 21 | long_description = readme_file.read() 22 | 23 | setup( 24 | name="django-crash-starter", 25 | version=version, 26 | description="django-crash-starter", 27 | long_description=long_description, 28 | author="Daniel Feldroy", 29 | author_email="hi@feldroy.com", 30 | url="https://github.com/feldroy/django-crash-starter", 31 | packages=[], 32 | license="BSD", 33 | zip_safe=False, 34 | classifiers=[ 35 | "Development Status :: 4 - Beta", 36 | "Environment :: Console", 37 | "Framework :: Django :: 2.2", 38 | "Intended Audience :: Developers", 39 | "Natural Language :: English", 40 | "License :: OSI Approved :: BSD License", 41 | "Programming Language :: Python", 42 | "Programming Language :: Python :: 3", 43 | "Programming Language :: Python :: 3.8", 44 | "Programming Language :: Python :: Implementation :: CPython", 45 | "Topic :: Software Development", 46 | ], 47 | keywords=( 48 | "cookiecutter, Python, projects, project templates, django, " 49 | "skeleton, scaffolding, project directory, setup.py" 50 | ), 51 | ) 52 | -------------------------------------------------------------------------------- /tests/test_bare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # this is a very simple script that tests the docker configuration for cookiecutter-django 3 | # it is meant to be run from the root directory of the repository, eg: 4 | # sh tests/test_docker.sh 5 | 6 | set -o errexit 7 | 8 | # install test requirements 9 | pip install -r requirements.txt 10 | 11 | # create a cache directory 12 | mkdir -p .cache/bare 13 | cd .cache/bare 14 | 15 | # create the project using the default settings in cookiecutter.json 16 | cookiecutter ../../ --no-input --overwrite-if-exists $@ 17 | cd everycheese 18 | 19 | 20 | # Install Python deps 21 | pip install -r requirements/local.txt 22 | 23 | # run the project's tests 24 | echo "Running pytest:" 25 | pytest || { echo "ERROR: Pytest report some issues"; exit 1; } 26 | 27 | echo "Checking missing migrations:" 28 | # return non-zero status code if there are migrations that have not been created 29 | 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; } 30 | -------------------------------------------------------------------------------- /tests/test_cookiecutter_generation.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import pathlib 4 | 5 | import pytest 6 | from cookiecutter.exceptions import FailedHookException 7 | import sh 8 | import yaml 9 | from binaryornot.check import is_binary 10 | 11 | PATTERN = r"{{(\s?cookiecutter)[.](.*?)}}" 12 | RE_OBJ = re.compile(PATTERN) 13 | 14 | PROJECT_DIR = pathlib.Path(__file__).resolve().parent.parent 15 | PYPROJECT_TOML = PROJECT_DIR / "pyproject.toml" 16 | 17 | 18 | @pytest.fixture 19 | def context(): 20 | return { 21 | "project_name": "My Test Project", 22 | "project_slug": "my_test_project", 23 | "description": "A short description of the project.", 24 | "author_name": "Test Author", 25 | "domain_name": "example.com", 26 | "email": "test@example.com", 27 | "timezone": "UTC", 28 | } 29 | 30 | 31 | SUPPORTED_COMBINATIONS = [ 32 | {"windows": "y", "database": "PostgreSQL"}, 33 | {"windows": "y", "database": "SQLite"}, 34 | {"windows": "n", "database": "PostgreSQL"}, 35 | {"windows": "n", "database": "SQLite"}, 36 | ] 37 | 38 | 39 | def _fixture_id(ctx): 40 | """Helper to get a user friendly test name from the parametrized context.""" 41 | return "-".join(f"{key}:{value}" for key, value in ctx.items()) 42 | 43 | 44 | def build_files_list(base_dir): 45 | """Build a list containing absolute paths to the generated files.""" 46 | return [ 47 | os.path.join(dirpath, file_path) 48 | for dirpath, subdirs, files in os.walk(base_dir) 49 | for file_path in files 50 | ] 51 | 52 | 53 | def check_paths(paths): 54 | """Method to check all paths have correct substitutions.""" 55 | # Assert that no match is found in any of the files 56 | for path in paths: 57 | if is_binary(path): 58 | continue 59 | 60 | for line in open(path, "r"): 61 | match = RE_OBJ.search(line) 62 | msg = "cookiecutter variable not replaced in {}" 63 | assert match is None, msg.format(path) 64 | 65 | 66 | @pytest.mark.parametrize( 67 | "context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id 68 | ) 69 | def test_project_generation(cookies, context, context_override): 70 | """Test that project is generated and fully rendered.""" 71 | result = cookies.bake( 72 | extra_context={**context, **context_override} 73 | ) 74 | assert result.exit_code == 0 75 | assert result.exception is None 76 | assert result.project.basename == context["project_slug"] 77 | assert result.project.isdir() 78 | 79 | paths = build_files_list(str(result.project)) 80 | assert paths 81 | check_paths(paths) 82 | 83 | 84 | @pytest.mark.parametrize( 85 | "context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id 86 | ) 87 | def test_flake8_passes(cookies, context_override): 88 | """Generated project should pass flake8.""" 89 | result = cookies.bake(extra_context=context_override) 90 | result_project_dir = f"{result._project_dir}" 91 | 92 | try: 93 | sh.flake8(str(result.project), _cwd=result_project_dir) 94 | except sh.ErrorReturnCode as e: 95 | pytest.fail(e.stdout.decode()) 96 | 97 | 98 | @pytest.mark.parametrize( 99 | "context_override", SUPPORTED_COMBINATIONS, ids=_fixture_id 100 | ) 101 | def test_black_passes(cookies, context_override): 102 | """Generated project should pass black.""" 103 | result = cookies.bake(extra_context=context_override) 104 | result_project_dir = f"{result._project_dir}" 105 | try: 106 | sh.black( 107 | "--config", 108 | str(PYPROJECT_TOML), 109 | "--check", 110 | "--diff", 111 | "--exclude", 112 | "migrations", 113 | f"{result.project}/", 114 | _cwd=result_project_dir, 115 | ) 116 | except sh.ErrorReturnCode as e: 117 | pytest.fail(e.stdout.decode()) 118 | 119 | 120 | def test_travis_invokes_pytest(cookies, context): 121 | result = cookies.bake(extra_context=context) 122 | 123 | assert result.exit_code == 0 124 | assert result.exception is None 125 | assert result.project.basename == context["project_slug"] 126 | assert result.project.isdir() 127 | 128 | with open(f"{result.project}/.travis.yml", "r") as travis_yml: 129 | try: 130 | assert yaml.safe_load(travis_yml)["script"] == [ 131 | "pytest" 132 | ] 133 | except yaml.YAMLError as e: 134 | pytest.fail(e) 135 | 136 | 137 | def test_gitlab_invokes_flake8_and_pytest(cookies, context): 138 | result = cookies.bake(extra_context=context) 139 | 140 | assert result.exit_code == 0 141 | assert result.exception is None 142 | assert result.project.basename == context["project_slug"] 143 | assert result.project.isdir() 144 | 145 | with open( 146 | f"{result.project}/.gitlab-ci.yml", "r" 147 | ) as gitlab_yml: 148 | try: 149 | gitlab_config = yaml.safe_load(gitlab_yml) 150 | assert gitlab_config["flake8"]["script"] == ["flake8"] 151 | assert gitlab_config["pytest"]["script"] == ["pytest"] 152 | except yaml.YAMLError as e: 153 | pytest.fail(e) 154 | 155 | 156 | @pytest.mark.parametrize("slug", ["project slug", "Project_Slug"]) 157 | def test_invalid_slug(cookies, context, slug): 158 | """Invalid slug should failed pre-generation hook.""" 159 | context.update({"project_slug": slug}) 160 | 161 | result = cookies.bake(extra_context=context) 162 | 163 | assert result.exit_code != 0 164 | assert isinstance(result.exception, FailedHookException) 165 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = true 3 | envlist = py38,black-template 4 | 5 | [testenv] 6 | deps = -rrequirements.txt 7 | commands = pytest {posargs:./tests} 8 | 9 | [testenv:black-template] 10 | deps = black 11 | commands = black --config {toxinidir}/pyproject.toml --check hooks tests setup.py 12 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | include = {{cookiecutter.project_slug}}/* 3 | omit = *migrations*, *tests* 4 | disable_warnings = already-imported 5 | plugins = 6 | django_coverage_plugin 7 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.{py,rst,ini}] 12 | indent_style = space 13 | indent_size = 4 14 | 15 | [*.py] 16 | line_length = 120 17 | known_first_party = {{ cookiecutter.project_slug }} 18 | multi_line_output = 3 19 | default_section = THIRDPARTY 20 | recursive = true 21 | skip = venv/ 22 | skip_glob = **/migrations/*.py 23 | include_trailing_comma = true 24 | force_grid_wrap = 0 25 | use_parentheses = true 26 | 27 | [*.{html,css,scss,json,yml}] 28 | indent_style = space 29 | indent_size = 2 30 | 31 | [*.md] 32 | trim_trailing_whitespace = false 33 | 34 | [Makefile] 35 | indent_style = tab 36 | 37 | [nginx.conf] 38 | indent_style = space 39 | indent_size = 2 40 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | staticfiles/ 55 | 56 | # Sphinx documentation 57 | docs/_build/ 58 | 59 | # PyBuilder 60 | target/ 61 | 62 | # pyenv 63 | .python-version 64 | 65 | # Environments 66 | .venv 67 | venv/ 68 | ENV/ 69 | 70 | # For security reasons, the .env file should never be included in git 71 | .env 72 | 73 | # Rope project settings 74 | .ropeproject 75 | 76 | # mkdocs documentation 77 | /site 78 | 79 | # mypy 80 | .mypy_cache/ 81 | 82 | 83 | ### Node template 84 | # Logs 85 | logs 86 | *.log 87 | npm-debug.log* 88 | yarn-debug.log* 89 | yarn-error.log* 90 | 91 | # Runtime data 92 | pids 93 | *.pid 94 | *.seed 95 | *.pid.lock 96 | 97 | # Directory for instrumented libs generated by jscoverage/JSCover 98 | lib-cov 99 | 100 | # Coverage directory used by tools like istanbul 101 | coverage 102 | 103 | # nyc test coverage 104 | .nyc_output 105 | 106 | # Bower dependency directory (https://bower.io/) 107 | bower_components 108 | 109 | # node-waf configuration 110 | .lock-wscript 111 | 112 | # Compiled binary addons (http://nodejs.org/api/addons.html) 113 | build/Release 114 | 115 | # Dependency directories 116 | node_modules/ 117 | jspm_packages/ 118 | 119 | # Typescript v1 declaration files 120 | typings/ 121 | 122 | # Optional npm cache directory 123 | .npm 124 | 125 | # Optional eslint cache 126 | .eslintcache 127 | 128 | # Optional REPL history 129 | .node_repl_history 130 | 131 | # Output of 'npm pack' 132 | *.tgz 133 | 134 | # Yarn Integrity file 135 | .yarn-integrity 136 | 137 | 138 | ### Linux template 139 | *~ 140 | 141 | # temporary files which can be created if a process still has a handle open of a deleted file 142 | .fuse_hidden* 143 | 144 | # KDE directory preferences 145 | .directory 146 | 147 | # Linux trash folder which might appear on any partition or disk 148 | .Trash-* 149 | 150 | # .nfs files are created when an open file is removed but is still being accessed 151 | .nfs* 152 | 153 | 154 | ### VisualStudioCode template 155 | .vscode/* 156 | !.vscode/settings.json 157 | !.vscode/tasks.json 158 | !.vscode/launch.json 159 | !.vscode/extensions.json 160 | 161 | 162 | # Provided default Pycharm Run/Debug Configurations should be tracked by git 163 | # In case of local modifications made by Pycharm, use update-index command 164 | # for each changed file, like this: 165 | # git update-index --assume-unchanged .idea/{{cookiecutter.project_slug}}.iml 166 | ### JetBrains template 167 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 168 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 169 | 170 | # User-specific stuff: 171 | .idea/**/workspace.xml 172 | .idea/**/tasks.xml 173 | .idea/dictionaries 174 | 175 | # Sensitive or high-churn files: 176 | .idea/**/dataSources/ 177 | .idea/**/dataSources.ids 178 | .idea/**/dataSources.xml 179 | .idea/**/dataSources.local.xml 180 | .idea/**/sqlDataSources.xml 181 | .idea/**/dynamic.xml 182 | .idea/**/uiDesigner.xml 183 | 184 | # Gradle: 185 | .idea/**/gradle.xml 186 | .idea/**/libraries 187 | 188 | # CMake 189 | cmake-build-debug/ 190 | 191 | # Mongo Explorer plugin: 192 | .idea/**/mongoSettings.xml 193 | 194 | ## File-based project format: 195 | *.iws 196 | 197 | ## Plugin-specific files: 198 | 199 | # IntelliJ 200 | out/ 201 | 202 | # mpeltonen/sbt-idea plugin 203 | .idea_modules/ 204 | 205 | # JIRA plugin 206 | atlassian-ide-plugin.xml 207 | 208 | # Cursive Clojure plugin 209 | .idea/replstate.xml 210 | 211 | # Crashlytics plugin (for Android Studio and IntelliJ) 212 | com_crashlytics_export_strings.xml 213 | crashlytics.properties 214 | crashlytics-build.properties 215 | fabric.properties 216 | 217 | 218 | ### Windows template 219 | # Windows thumbnail cache files 220 | Thumbs.db 221 | ehthumbs.db 222 | ehthumbs_vista.db 223 | 224 | # Dump file 225 | *.stackdump 226 | 227 | # Folder config file 228 | Desktop.ini 229 | 230 | # Recycle Bin used on file shares 231 | $RECYCLE.BIN/ 232 | 233 | # Windows Installer files 234 | *.cab 235 | *.msi 236 | *.msm 237 | *.msp 238 | 239 | # Windows shortcuts 240 | *.lnk 241 | 242 | 243 | ### macOS template 244 | # General 245 | *.DS_Store 246 | .AppleDouble 247 | .LSOverride 248 | 249 | # Icon must end with two \r 250 | Icon 251 | 252 | # Thumbnails 253 | ._* 254 | 255 | # Files that might appear in the root of a volume 256 | .DocumentRevisions-V100 257 | .fseventsd 258 | .Spotlight-V100 259 | .TemporaryItems 260 | .Trashes 261 | .VolumeIcon.icns 262 | .com.apple.timemachine.donotpresent 263 | 264 | # Directories potentially created on remote AFP share 265 | .AppleDB 266 | .AppleDesktop 267 | Network Trash Folder 268 | Temporary Items 269 | .apdisk 270 | 271 | 272 | ### SublimeText template 273 | # Cache files for Sublime Text 274 | *.tmlanguage.cache 275 | *.tmPreferences.cache 276 | *.stTheme.cache 277 | 278 | # Workspace files are user-specific 279 | *.sublime-workspace 280 | 281 | # Project files should be checked into the repository, unless a significant 282 | # proportion of contributors will probably not be using Sublime Text 283 | # *.sublime-project 284 | 285 | # SFTP configuration file 286 | sftp-config.json 287 | 288 | # Package control specific files 289 | Package Control.last-run 290 | Package Control.ca-list 291 | Package Control.ca-bundle 292 | Package Control.system-ca-bundle 293 | Package Control.cache/ 294 | Package Control.ca-certs/ 295 | Package Control.merged-ca-bundle 296 | Package Control.user-ca-bundle 297 | oscrypto-ca-bundle.crt 298 | bh_unicode_properties.cache 299 | 300 | # Sublime-github package stores a github token in this file 301 | # https://packagecontrol.io/packages/sublime-github 302 | GitHub.sublime-settings 303 | 304 | 305 | ### Vim template 306 | # Swap 307 | [._]*.s[a-v][a-z] 308 | [._]*.sw[a-p] 309 | [._]s[a-v][a-z] 310 | [._]sw[a-p] 311 | 312 | # Session 313 | Session.vim 314 | 315 | # Temporary 316 | .netrwhist 317 | 318 | # Auto-generated tag files 319 | tags 320 | 321 | ### Project template 322 | {{ cookiecutter.project_slug }}/media/ 323 | 324 | .pytest_cache/ 325 | 326 | # ignore sqlite databases 327 | *.db 328 | *.sqlite 329 | *.sqlite3 330 | 331 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - lint 3 | - test 4 | 5 | variables: 6 | POSTGRES_USER: '{{ cookiecutter.project_slug }}' 7 | POSTGRES_PASSWORD: '' 8 | POSTGRES_DB: 'test_{{ cookiecutter.project_slug }}' 9 | 10 | flake8: 11 | stage: lint 12 | image: python:3.8-alpine 13 | before_script: 14 | - pip install -q flake8 15 | script: 16 | - flake8 17 | 18 | pytest: 19 | stage: test 20 | image: python:3.8 21 | tags: 22 | - docker 23 | services: 24 | - postgres:11 25 | variables: 26 | DATABASE_URL: pgsql://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres/$POSTGRES_DB 27 | 28 | before_script: 29 | - pip install -r requirements/local.txt 30 | 31 | script: 32 | - pytest 33 | 34 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | exclude: 'docs|node_modules|migrations|.git|.tox' 2 | default_stages: [commit] 3 | fail_fast: true 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: master 8 | hooks: 9 | - id: trailing-whitespace 10 | files: (^|/)a/.+\.(py|html|sh|css|js)$ 11 | 12 | - repo: local 13 | hooks: 14 | - id: flake8 15 | name: flake8 16 | entry: flake8 17 | language: python 18 | types: [python] 19 | 20 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | load-plugins=pylint_django, pylint_celery 3 | 4 | [FORMAT] 5 | max-line-length=120 6 | 7 | [MESSAGES CONTROL] 8 | disable=missing-docstring,invalid-name 9 | 10 | [DESIGN] 11 | max-parents=13 12 | 13 | [TYPECHECK] 14 | generated-members=REQUEST,acl_users,aq_parent,"[a-zA-Z]+_set{1,2}",save,delete 15 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | services: 3 | - postgresql 4 | before_install: 5 | - sudo apt-get update -qq 6 | - sudo apt-get install -qq build-essential gettext python-dev zlib1g-dev libpq-dev xvfb 7 | - sudo apt-get install -qq libjpeg8-dev libfreetype6-dev libwebp-dev 8 | - sudo apt-get install -qq graphviz-dev python-setuptools python3-dev python-virtualenv python-pip 9 | - sudo apt-get install -qq firefox automake libtool libreadline6 libreadline6-dev libreadline-dev 10 | - sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm 11 | language: python 12 | python: 13 | - "3.8" 14 | install: 15 | - pip install -r requirements/local.txt 16 | script: 17 | - "pytest" 18 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | {{ cookiecutter.author_name }} 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/README.md: -------------------------------------------------------------------------------- 1 | {{cookiecutter.project_name}} 2 | ============================== 3 | 4 | {{cookiecutter.description}} 5 | 6 | ### Quick setup 7 | 8 | > The next steps assume that conda is already installed 9 | 10 | 1 - Create a conda environment: 11 | 12 | 13 | ```bash 14 | conda create python=3.8 -n {{cookiecutter.project_slug}} 15 | ``` 16 | 2 - Activate the conda environment 17 | 18 | ```bash 19 | conda activate {{cookiecutter.project_slug}} 20 | ``` 21 | 22 | 3 - Install the project basic dependencies and development dependencies 23 | 24 | > Make sure you are inside the root project directory before executing the next commands. 25 | > 26 | > The root project directory is the directory that contains the `manage.py` file 27 | 28 | On Linux and Mac 29 | 30 | ```bash 31 | pip install -r requirements/local.txt 32 | ``` 33 | 34 | On Windows 35 | 36 | ```bash 37 | pip install -r requirements\local.txt 38 | ``` 39 | 40 | 4 - Configure the database connection string on the .env 41 | 42 | On Linux and Mac 43 | 44 | ```bash 45 | cp env.sample.mac_or_linux .env 46 | ``` 47 | 48 | On Windows 49 | 50 | ```bash 51 | copy env.sample.windows .env 52 | ``` 53 | 54 | Change the value of the variable `DATABASE_URL` inside the file` .env` with the information of the database we want to connect. 55 | 56 | Note: Several project settings have been configured so that they can be easily manipulated using environment variables or a plain text configuration file, such as the `.env` file. 57 | This is done with the help of a library called django-environ. We can see the formats expected by `DATABASE_URL` at https://github.com/jacobian/dj-database-url#url-schema. 58 | 59 | 5 - Use the django-extension's `sqlcreate` management command to help to create the database 60 | 61 | On Linux: 62 | 63 | ```bash 64 | python manage.py sqlcreate | sudo -u postgres psql -U postgres 65 | ``` 66 | 67 | On Mac: 68 | 69 | ```bash 70 | python manage.py sqlcreate | psql 71 | ``` 72 | 73 | On Windows: 74 | 75 | Since [there is no official support for PostgreSQL 12 on Windows 10](https://www.postgresql.org/download/windows/) (officially PostgreSQL 12 is only supported on Windows Server), we choose to use SQLite3 on Windows 76 | 77 | 6 - Run the `migrations` to finish configuring the database to able to run the project 78 | 79 | 80 | ```bash 81 | python manage.py migrate 82 | ``` 83 | 84 | 85 | ### Running the tests and coverage test 86 | 87 | 88 | ```bash 89 | coverage run -m pytest 90 | ``` 91 | 92 | 93 | ## Troubleshooting 94 | 95 | If for some reason you get an error similar to bellow, is because the DATABASE_URL is configured to `postgres:///{{cookiecutter.project_slug}}` and because of it the generated `DATABASES` settings are configured to connect on PostgreSQL using the socket mode. 96 | In that case, you must create the database manually because the `sqlcreate` is not capable to correctly generate the SQL query in this case. 97 | 98 | ```sql 99 | ERROR: syntax error at or near "WITH" 100 | LINE 1: CREATE USER WITH ENCRYPTED PASSWORD '' CREATEDB; 101 | ^ 102 | ERROR: zero-length delimited identifier at or near """" 103 | LINE 1: CREATE DATABASE {{cookiecutter.project_slug}} WITH ENCODING 'UTF-8' OWNER ""; 104 | ^ 105 | ERROR: syntax error at or near ";" 106 | LINE 1: GRANT ALL PRIVILEGES ON DATABASE {{cookiecutter.project_slug}} TO ; 107 | ``` 108 | 109 | 110 | 111 | ```sql 112 | ERROR: role "myuser" already exists 113 | ERROR: database "{{cookiecutter.project_slug}}" already exists 114 | GRANT 115 | ``` 116 | 117 | You can delete the database and the user with the commands below and then [perform step 5 again](#step-5). 118 | 119 | > :warning: **Be very careful here!**: The commands below erase data, and should only be executed on your local development machine and **NEVER** on a production server. 120 | 121 | 122 | On Linux: 123 | 124 | ```bash 125 | sudo -u postgres dropdb -U postgres --if-exists {{cookiecutter.project_slug}} 126 | sudo -u postgres dropuser -U postgres --if-exists myuser 127 | ``` 128 | 129 | On Mac: 130 | 131 | ```bash 132 | dropdb --if-exists {{cookiecutter.project_slug}} 133 | dropuser --if-exists myuser 134 | ``` 135 | 136 | 137 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/__init__.py: -------------------------------------------------------------------------------- 1 | # This will make sure the app is always imported when 2 | # Django starts so that shared_task will use this app. 3 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for {{ cookiecutter.project_name }} project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/dev/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | import sys 12 | from pathlib import Path 13 | 14 | from django.core.asgi import get_asgi_application 15 | 16 | 17 | # This allows easy placement of apps within the interior 18 | # {{ cookiecutter.project_slug }} directory. 19 | ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent 20 | sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}")) 21 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 22 | # if running multiple sites in the same mod_wsgi process. To fix this, use 23 | # mod_wsgi daemon mode with each site in its own daemon process, or use 24 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" 25 | os.environ.setdefault( 26 | "DJANGO_SETTINGS_MODULE", "config.settings.production" 27 | ) 28 | 29 | # This application object is used by any ASGI server configured to use this 30 | # file. 31 | application = get_asgi_application() 32 | # Apply ASGI middleware here. 33 | # from helloworld.asgi import HelloWorldApplication 34 | # application = HelloWorldApplication(application) 35 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feldroy/django-crash-starter/b1a5b3f6d0a4c453c249de66f1c7027d769b0ea6/{{cookiecutter.project_slug}}/config/settings/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/settings/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | Base settings to build other settings files upon. 3 | """ 4 | 5 | from pathlib import Path 6 | 7 | import environ 8 | 9 | # {{ cookiecutter.project_slug }}/ 10 | BASE_DIR = Path(__file__).resolve(strict=True).parent.parent.parent 11 | APPS_DIR = BASE_DIR / "{{ cookiecutter.project_slug }}" 12 | 13 | env = environ.Env() 14 | 15 | ENV_FILE = BASE_DIR / ".env" 16 | if Path(ENV_FILE).exists(): 17 | # OS environment variables take precedence over variables from .env 18 | env.read_env(str(ENV_FILE)) 19 | 20 | # GENERAL 21 | # ------------------------------------------------------------------------------ 22 | # https://docs.djangoproject.com/en/dev/ref/settings/#debug 23 | DEBUG = env.bool("DJANGO_DEBUG", False) 24 | # Local time zone. Choices are 25 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 26 | # though not all of them may be available with every OS. 27 | # In Windows, this must be set to your system time zone. 28 | TIME_ZONE = "{{ cookiecutter.timezone }}" 29 | # https://docs.djangoproject.com/en/dev/ref/settings/#language-code 30 | LANGUAGE_CODE = "en-us" 31 | # https://docs.djangoproject.com/en/dev/ref/settings/#site-id 32 | SITE_ID = 1 33 | # https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n 34 | USE_I18N = True 35 | # https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n 36 | USE_L10N = True 37 | # https://docs.djangoproject.com/en/dev/ref/settings/#use-tz 38 | USE_TZ = True 39 | # https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths 40 | LOCALE_PATHS = [str(BASE_DIR / "locale")] 41 | 42 | # DATABASES 43 | # ------------------------------------------------------------------------------ 44 | # https://docs.djangoproject.com/en/dev/ref/settings/#databases 45 | {% if cookiecutter.windows == 'y' or cookiecutter.database == "SQLite" -%} 46 | DATABASES = { 47 | "default": env.db( 48 | "DATABASE_URL", 49 | default=f"sqlite:///{str(BASE_DIR / '{{cookiecutter.project_slug}}.db')}", 50 | ) 51 | } 52 | {%- else -%} 53 | DATABASES = { 54 | # Raises ImproperlyConfigured Exception 55 | # if DATABASE_URL Not in os.environ and 56 | # the "default" argument is not defined. 57 | # The DATABASE_URL environment variables 58 | # expect a value in the following format: 59 | # DATABASE_URL=postgres://user:password@hostname_or_ip:port/database_name 60 | "default": env.db( 61 | "DATABASE_URL", 62 | default="postgres:///{{cookiecutter.project_slug}}", 63 | ) 64 | } 65 | {%- endif %} 66 | DATABASES["default"]["ATOMIC_REQUESTS"] = True 67 | 68 | # URLS 69 | # ------------------------------------------------------------------------------ 70 | # https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf 71 | ROOT_URLCONF = "config.urls" 72 | # https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application 73 | WSGI_APPLICATION = "config.wsgi.application" 74 | 75 | # APPS 76 | # ------------------------------------------------------------------------------ 77 | DJANGO_APPS = [ 78 | "django.contrib.auth", 79 | "django.contrib.contenttypes", 80 | "django.contrib.sessions", 81 | "django.contrib.sites", 82 | "django.contrib.messages", 83 | "django.contrib.staticfiles", 84 | # "django.contrib.humanize", # Handy template tags 85 | "django.contrib.admin", 86 | "django.forms", 87 | ] 88 | THIRD_PARTY_APPS = [ 89 | "crispy_forms", 90 | "allauth", 91 | "allauth.account", 92 | "allauth.socialaccount", 93 | ] 94 | 95 | LOCAL_APPS = [ 96 | "{{ cookiecutter.project_slug }}.users.apps.UsersConfig", 97 | # Your stuff: custom apps go here 98 | ] 99 | # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps 100 | INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS 101 | 102 | # MIGRATIONS 103 | # ------------------------------------------------------------------------------ 104 | # https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules 105 | MIGRATION_MODULES = { 106 | "sites": "{{ cookiecutter.project_slug }}.contrib.sites.migrations" 107 | } 108 | 109 | # AUTHENTICATION 110 | # ------------------------------------------------------------------------------ 111 | # https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends 112 | AUTHENTICATION_BACKENDS = [ 113 | "django.contrib.auth.backends.ModelBackend", 114 | "allauth.account.auth_backends.AuthenticationBackend", 115 | ] 116 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model 117 | AUTH_USER_MODEL = "users.User" 118 | # https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url 119 | LOGIN_REDIRECT_URL = "users:redirect" 120 | # https://docs.djangoproject.com/en/dev/ref/settings/#login-url 121 | LOGIN_URL = "account_login" 122 | 123 | # PASSWORDS 124 | # ------------------------------------------------------------------------------ 125 | # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers 126 | PASSWORD_HASHERS = [ 127 | "django.contrib.auth.hashers.PBKDF2PasswordHasher", 128 | "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", 129 | "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", 130 | ] 131 | # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators 132 | AUTH_PASSWORD_VALIDATORS = [ 133 | { 134 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" 135 | }, 136 | { 137 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator" 138 | }, 139 | { 140 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator" 141 | }, 142 | { 143 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator" 144 | }, 145 | ] 146 | 147 | # MIDDLEWARE 148 | # ------------------------------------------------------------------------------ 149 | # https://docs.djangoproject.com/en/dev/ref/settings/#middleware 150 | MIDDLEWARE = [ 151 | "django.middleware.security.SecurityMiddleware", 152 | "whitenoise.middleware.WhiteNoiseMiddleware", 153 | "django.contrib.sessions.middleware.SessionMiddleware", 154 | "django.middleware.locale.LocaleMiddleware", 155 | "django.middleware.common.CommonMiddleware", 156 | "django.middleware.csrf.CsrfViewMiddleware", 157 | "django.contrib.auth.middleware.AuthenticationMiddleware", 158 | "django.contrib.messages.middleware.MessageMiddleware", 159 | "django.middleware.common.BrokenLinkEmailsMiddleware", 160 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 161 | ] 162 | 163 | # STATIC 164 | # ------------------------------------------------------------------------------ 165 | # https://docs.djangoproject.com/en/dev/ref/settings/#static-root 166 | STATIC_ROOT = str(BASE_DIR / "staticfiles") 167 | # https://docs.djangoproject.com/en/dev/ref/settings/#static-url 168 | STATIC_URL = "/static/" 169 | # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS 170 | STATICFILES_DIRS = [str(APPS_DIR / "static")] 171 | # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders 172 | STATICFILES_FINDERS = [ 173 | "django.contrib.staticfiles.finders.FileSystemFinder", 174 | "django.contrib.staticfiles.finders.AppDirectoriesFinder", 175 | ] 176 | 177 | # MEDIA 178 | # ------------------------------------------------------------------------------ 179 | # https://docs.djangoproject.com/en/dev/ref/settings/#media-root 180 | MEDIA_ROOT = str(APPS_DIR / "media") 181 | # https://docs.djangoproject.com/en/dev/ref/settings/#media-url 182 | MEDIA_URL = "/media/" 183 | 184 | # TEMPLATES 185 | # ------------------------------------------------------------------------------ 186 | # https://docs.djangoproject.com/en/dev/ref/settings/#templates 187 | TEMPLATES = [ 188 | { 189 | # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND 190 | "BACKEND": "django.template.backends.django.DjangoTemplates", 191 | # https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs 192 | "DIRS": [str(APPS_DIR / "templates")], 193 | "OPTIONS": { 194 | # https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders 195 | # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types 196 | "loaders": [ 197 | "django.template.loaders.filesystem.Loader", 198 | "django.template.loaders.app_directories.Loader", 199 | ], 200 | # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors 201 | "context_processors": [ 202 | "django.template.context_processors.debug", 203 | "django.template.context_processors.request", 204 | "django.contrib.auth.context_processors.auth", 205 | "django.template.context_processors.i18n", 206 | "django.template.context_processors.media", 207 | "django.template.context_processors.static", 208 | "django.template.context_processors.tz", 209 | "django.contrib.messages.context_processors.messages", 210 | "{{ cookiecutter.project_slug }}.utils.context_processors.settings_context", 211 | ], 212 | }, 213 | } 214 | ] 215 | 216 | # https://docs.djangoproject.com/en/dev/ref/settings/#form-renderer 217 | FORM_RENDERER = "django.forms.renderers.TemplatesSetting" 218 | 219 | # http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs 220 | CRISPY_TEMPLATE_PACK = "bootstrap4" 221 | 222 | # FIXTURES 223 | # ------------------------------------------------------------------------------ 224 | # https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs 225 | FIXTURE_DIRS = (str(APPS_DIR / "fixtures"),) 226 | 227 | # SECURITY 228 | # ------------------------------------------------------------------------------ 229 | # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-httponly 230 | SESSION_COOKIE_HTTPONLY = True 231 | # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-httponly 232 | CSRF_COOKIE_HTTPONLY = True 233 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter 234 | SECURE_BROWSER_XSS_FILTER = True 235 | # https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options 236 | X_FRAME_OPTIONS = "DENY" 237 | 238 | # EMAIL 239 | # ------------------------------------------------------------------------------ 240 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 241 | EMAIL_BACKEND = env( 242 | "DJANGO_EMAIL_BACKEND", 243 | default="django.core.mail.backends.smtp.EmailBackend", 244 | ) 245 | # https://docs.djangoproject.com/en/2.2/ref/settings/#email-timeout 246 | EMAIL_TIMEOUT = 5 247 | 248 | # ADMIN 249 | # ------------------------------------------------------------------------------ 250 | # Django Admin URL. 251 | ADMIN_URL = "admin/" 252 | # https://docs.djangoproject.com/en/dev/ref/settings/#admins 253 | ADMINS = [("{{cookiecutter.author_name}}", "{{cookiecutter.email}}")] 254 | # https://docs.djangoproject.com/en/dev/ref/settings/#managers 255 | MANAGERS = ADMINS 256 | 257 | # LOGGING 258 | # ------------------------------------------------------------------------------ 259 | # https://docs.djangoproject.com/en/dev/ref/settings/#logging 260 | # See https://docs.djangoproject.com/en/dev/topics/logging for 261 | # more details on how to customize your logging configuration. 262 | LOGGING = { 263 | "version": 1, 264 | "disable_existing_loggers": False, 265 | "formatters": { 266 | "verbose": { 267 | "format": "%(levelname)s %(asctime)s %(module)s " 268 | "%(process)d %(thread)d %(message)s" 269 | } 270 | }, 271 | "handlers": { 272 | "console": { 273 | "level": "DEBUG", 274 | "class": "logging.StreamHandler", 275 | "formatter": "verbose", 276 | } 277 | }, 278 | "root": {"level": "INFO", "handlers": ["console"]}, 279 | } 280 | 281 | # django-allauth 282 | # ------------------------------------------------------------------------------ 283 | ACCOUNT_ALLOW_REGISTRATION = env.bool( 284 | "DJANGO_ACCOUNT_ALLOW_REGISTRATION", True 285 | ) 286 | # https://django-allauth.readthedocs.io/en/latest/configuration.html 287 | ACCOUNT_AUTHENTICATION_METHOD = "username" 288 | # https://django-allauth.readthedocs.io/en/latest/configuration.html 289 | ACCOUNT_EMAIL_REQUIRED = True 290 | # https://django-allauth.readthedocs.io/en/latest/configuration.html 291 | ACCOUNT_EMAIL_VERIFICATION = "mandatory" 292 | # https://django-allauth.readthedocs.io/en/latest/configuration.html 293 | ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter" 294 | # https://django-allauth.readthedocs.io/en/latest/configuration.html 295 | SOCIALACCOUNT_ADAPTER = ( 296 | "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter" 297 | ) 298 | 299 | # Your stuff... 300 | # ------------------------------------------------------------------------------ 301 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/settings/local.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | from .base import env 3 | 4 | # GENERAL 5 | # ------------------------------------------------------------------------------ 6 | # https://docs.djangoproject.com/en/dev/ref/settings/#debug 7 | DEBUG = True 8 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 9 | SECRET_KEY = env( 10 | "DJANGO_SECRET_KEY", 11 | default="!!!SET DJANGO_SECRET_KEY!!!", 12 | ) 13 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts 14 | ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] 15 | 16 | # CACHES 17 | # ------------------------------------------------------------------------------ 18 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches 19 | CACHES = { 20 | "default": { 21 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache", 22 | "LOCATION": "", 23 | } 24 | } 25 | 26 | # EMAIL 27 | # ------------------------------------------------------------------------------ 28 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 29 | EMAIL_BACKEND = env( 30 | "DJANGO_EMAIL_BACKEND", 31 | default="django.core.mail.backends.console.EmailBackend", 32 | ) 33 | 34 | # WhiteNoise 35 | # ------------------------------------------------------------------------------ 36 | # http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development 37 | INSTALLED_APPS = [ # noqa: F405 38 | "whitenoise.runserver_nostatic" 39 | ] + INSTALLED_APPS # noqa: F405 40 | # django-debug-toolbar 41 | # ------------------------------------------------------------------------------ 42 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites 43 | INSTALLED_APPS += ["debug_toolbar"] # noqa: F405 44 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware 45 | MIDDLEWARE += [ # noqa: F405 46 | "debug_toolbar.middleware.DebugToolbarMiddleware" 47 | ] 48 | # https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config 49 | DEBUG_TOOLBAR_CONFIG = { 50 | "DISABLE_PANELS": [ 51 | "debug_toolbar.panels.redirects.RedirectsPanel" 52 | ], 53 | "SHOW_TEMPLATE_CONTEXT": True, 54 | } 55 | # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips 56 | INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"] 57 | 58 | # django-extensions 59 | # ------------------------------------------------------------------------------ 60 | # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration 61 | INSTALLED_APPS += ["django_extensions"] # noqa: F405 62 | 63 | # Your stuff... 64 | # ------------------------------------------------------------------------------ 65 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/settings/production.py: -------------------------------------------------------------------------------- 1 | from .base import * # noqa 2 | from .base import env 3 | 4 | # GENERAL 5 | # ------------------------------------------------------------------------------ 6 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 7 | SECRET_KEY = env("DJANGO_SECRET_KEY") 8 | # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts 9 | ALLOWED_HOSTS = env.list( 10 | "DJANGO_ALLOWED_HOSTS", default=["{{ cookiecutter.domain_name }}"] 11 | ) 12 | 13 | # DATABASES 14 | # ------------------------------------------------------------------------------ 15 | DATABASES["default"] = env.db("DATABASE_URL") # noqa: F405 16 | DATABASES["default"]["ATOMIC_REQUESTS"] = True # noqa: F405 17 | DATABASES["default"]["CONN_MAX_AGE"] = env.int( # noqa: F405 18 | "CONN_MAX_AGE", default=60 19 | ) 20 | 21 | # CACHES 22 | # ------------------------------------------------------------------------------ 23 | CACHES = { 24 | "default": { 25 | "BACKEND": "django_redis.cache.RedisCache", 26 | "LOCATION": env("REDIS_URL"), 27 | "OPTIONS": { 28 | "CLIENT_CLASS": "django_redis.client.DefaultClient", 29 | # Mimicing memcache behavior. 30 | # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior 31 | "IGNORE_EXCEPTIONS": True, 32 | }, 33 | } 34 | } 35 | 36 | # SECURITY 37 | # ------------------------------------------------------------------------------ 38 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header 39 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") 40 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect 41 | SECURE_SSL_REDIRECT = env.bool( 42 | "DJANGO_SECURE_SSL_REDIRECT", default=True 43 | ) 44 | # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure 45 | SESSION_COOKIE_SECURE = True 46 | # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure 47 | CSRF_COOKIE_SECURE = True 48 | # https://docs.djangoproject.com/en/dev/topics/security/#ssl-https 49 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-seconds 50 | # TODO: set this to 60 seconds first and then to 518400 once you prove the former works 51 | SECURE_HSTS_SECONDS = 60 52 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains 53 | SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( 54 | "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True 55 | ) 56 | # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload 57 | SECURE_HSTS_PRELOAD = env.bool( 58 | "DJANGO_SECURE_HSTS_PRELOAD", default=True 59 | ) 60 | # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff 61 | SECURE_CONTENT_TYPE_NOSNIFF = env.bool( 62 | "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True 63 | ) 64 | 65 | 66 | STATICFILES_STORAGE = ( 67 | "whitenoise.storage.CompressedManifestStaticFilesStorage" 68 | ) 69 | 70 | # https://github.com/antonagestam/collectfast#upload-strategies 71 | COLLECTFAST_STRATEGY = ( 72 | "collectfast.strategies.filesystem.FileSystemStrategy" 73 | ) 74 | 75 | # MEDIA 76 | # ------------------------------------------------------------------------------ 77 | 78 | # TODO in later extensions 79 | 80 | # TEMPLATES 81 | # ------------------------------------------------------------------------------ 82 | # https://docs.djangoproject.com/en/dev/ref/settings/#templates 83 | TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa: F405 84 | ( 85 | "django.template.loaders.cached.Loader", 86 | [ 87 | "django.template.loaders.filesystem.Loader", 88 | "django.template.loaders.app_directories.Loader", 89 | ], 90 | ) 91 | ] 92 | 93 | # EMAIL 94 | # ------------------------------------------------------------------------------ 95 | # https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email 96 | DEFAULT_FROM_EMAIL = env( 97 | "DJANGO_DEFAULT_FROM_EMAIL", 98 | default="{{cookiecutter.project_name}} ", 99 | ) 100 | # https://docs.djangoproject.com/en/dev/ref/settings/#server-email 101 | SERVER_EMAIL = env( 102 | "DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL 103 | ) 104 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix 105 | EMAIL_SUBJECT_PREFIX = env( 106 | "DJANGO_EMAIL_SUBJECT_PREFIX", default="[{{cookiecutter.project_name}}]" 107 | ) 108 | 109 | # ADMIN 110 | # ------------------------------------------------------------------------------ 111 | # Django Admin URL regex. 112 | ADMIN_URL = env("DJANGO_ADMIN_URL") 113 | 114 | # Anymail (Mailgun) 115 | # ------------------------------------------------------------------------------ 116 | # https://anymail.readthedocs.io/en/stable/installation/#installing-anymail 117 | INSTALLED_APPS += ["anymail"] # noqa: F405 118 | EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" 119 | # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference 120 | ANYMAIL = { 121 | "MAILGUN_API_KEY": env("MAILGUN_API_KEY"), 122 | "MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"), 123 | "MAILGUN_API_URL": env( 124 | "MAILGUN_API_URL", default="https://api.mailgun.net/v3" 125 | ), 126 | } 127 | 128 | 129 | # Collectfast 130 | # ------------------------------------------------------------------------------ 131 | # https://github.com/antonagestam/collectfast#installation 132 | INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS # noqa: F405 133 | # LOGGING 134 | # ------------------------------------------------------------------------------ 135 | # https://docs.djangoproject.com/en/dev/ref/settings/#logging 136 | # See https://docs.djangoproject.com/en/dev/topics/logging for 137 | # more details on how to customize your logging configuration. 138 | 139 | # A sample logging configuration. The only tangible logging 140 | # performed by this configuration is to send an email to 141 | # the site admins on every HTTP 500 error when DEBUG=False. 142 | LOGGING = { 143 | "version": 1, 144 | "disable_existing_loggers": False, 145 | "filters": { 146 | "require_debug_false": { 147 | "()": "django.utils.log.RequireDebugFalse" 148 | } 149 | }, 150 | "formatters": { 151 | "verbose": { 152 | "format": "%(levelname)s %(asctime)s %(module)s " 153 | "%(process)d %(thread)d %(message)s" 154 | } 155 | }, 156 | "handlers": { 157 | "mail_admins": { 158 | "level": "ERROR", 159 | "filters": ["require_debug_false"], 160 | "class": "django.utils.log.AdminEmailHandler", 161 | }, 162 | "console": { 163 | "level": "DEBUG", 164 | "class": "logging.StreamHandler", 165 | "formatter": "verbose", 166 | }, 167 | }, 168 | "root": {"level": "INFO", "handlers": ["console"]}, 169 | "loggers": { 170 | "django.request": { 171 | "handlers": ["mail_admins"], 172 | "level": "ERROR", 173 | "propagate": True, 174 | }, 175 | "django.security.DisallowedHost": { 176 | "level": "ERROR", 177 | "handlers": ["console", "mail_admins"], 178 | "propagate": True, 179 | }, 180 | }, 181 | } 182 | 183 | # Your stuff... 184 | # ------------------------------------------------------------------------------ 185 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/settings/test.py: -------------------------------------------------------------------------------- 1 | """ 2 | With these settings, tests run faster. 3 | """ 4 | 5 | from .base import * # noqa 6 | from .base import env 7 | 8 | # GENERAL 9 | # ------------------------------------------------------------------------------ 10 | # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key 11 | SECRET_KEY = env( 12 | "DJANGO_SECRET_KEY", 13 | default="!!!SET DJANGO_SECRET_KEY!!!", 14 | ) 15 | # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner 16 | TEST_RUNNER = "django.test.runner.DiscoverRunner" 17 | 18 | # CACHES 19 | # ------------------------------------------------------------------------------ 20 | # https://docs.djangoproject.com/en/dev/ref/settings/#caches 21 | CACHES = { 22 | "default": { 23 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache", 24 | "LOCATION": "", 25 | } 26 | } 27 | 28 | # PASSWORDS 29 | # ------------------------------------------------------------------------------ 30 | # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers 31 | PASSWORD_HASHERS = [ 32 | "django.contrib.auth.hashers.MD5PasswordHasher" 33 | ] 34 | 35 | # TEMPLATES 36 | # ------------------------------------------------------------------------------ 37 | TEMPLATES[-1]["OPTIONS"]["loaders"] = [ # type: ignore[index] # noqa: F405 38 | ( 39 | "django.template.loaders.cached.Loader", 40 | [ 41 | "django.template.loaders.filesystem.Loader", 42 | "django.template.loaders.app_directories.Loader", 43 | ], 44 | ) 45 | ] 46 | 47 | # EMAIL 48 | # ------------------------------------------------------------------------------ 49 | # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 50 | EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" 51 | 52 | # Your stuff... 53 | # ------------------------------------------------------------------------------ 54 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.urls import include, path 3 | from django.conf.urls.static import static 4 | from django.contrib import admin 5 | from django.views.generic import TemplateView 6 | from django.views import defaults as default_views 7 | 8 | urlpatterns = [ 9 | path( 10 | "", 11 | TemplateView.as_view(template_name="pages/home.html"), 12 | name="home", 13 | ), 14 | path( 15 | "about/", 16 | TemplateView.as_view(template_name="pages/about.html"), 17 | name="about", 18 | ), 19 | # Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %} 20 | path(settings.ADMIN_URL, admin.site.urls), 21 | # User management 22 | path( 23 | "users/", 24 | include("{{ cookiecutter.project_slug }}.users.urls", namespace="users"), 25 | ), 26 | path("accounts/", include("allauth.urls")), 27 | # Your stuff: custom urls includes go here 28 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 29 | 30 | if settings.DEBUG: 31 | # This allows the error pages to be debugged during development, just visit 32 | # these url in browser to see how these error pages look like. 33 | urlpatterns += [ 34 | path( 35 | "400/", 36 | default_views.bad_request, 37 | kwargs={"exception": Exception("Bad Request!")}, 38 | ), 39 | path( 40 | "403/", 41 | default_views.permission_denied, 42 | kwargs={"exception": Exception("Permission Denied")}, 43 | ), 44 | path( 45 | "404/", 46 | default_views.page_not_found, 47 | kwargs={"exception": Exception("Page not Found")}, 48 | ), 49 | path("500/", default_views.server_error), 50 | ] 51 | if "debug_toolbar" in settings.INSTALLED_APPS: 52 | import debug_toolbar 53 | 54 | urlpatterns = [ 55 | path("__debug__/", include(debug_toolbar.urls)) 56 | ] + urlpatterns 57 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for {{ cookiecutter.project_name }} project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | import sys 18 | from pathlib import Path 19 | 20 | from django.core.wsgi import get_wsgi_application 21 | 22 | # This allows easy placement of apps within the interior 23 | # {{ cookiecutter.project_slug }} directory. 24 | ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent 25 | sys.path.append(str(ROOT_DIR / "{{ cookiecutter.project_slug }}")) 26 | # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks 27 | # if running multiple sites in the same mod_wsgi process. To fix this, use 28 | # mod_wsgi daemon mode with each site in its own daemon process, or use 29 | # os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" 30 | os.environ.setdefault( 31 | "DJANGO_SETTINGS_MODULE", "config.settings.production" 32 | ) 33 | 34 | # This application object is used by any WSGI server configured to use this 35 | # file. This includes Django's development server, if the WSGI_APPLICATION 36 | # setting points here. 37 | application = get_wsgi_application() 38 | # Apply WSGI middleware here. 39 | # from helloworld.wsgi import HelloWorldApplication 40 | # application = HelloWorldApplication(application) 41 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/env.sample.mac_or_linux: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://myuser:mypasswd@localhost:5432/everycheese 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/env.sample.windows: -------------------------------------------------------------------------------- 1 | # Sample DATABASE URL for Windows until PostgreSQL 12 finally receive official support for Windows 10 2 | DATABASE_URL=sqlite:///everycheese.db 3 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/locale/README.rst: -------------------------------------------------------------------------------- 1 | Translations 2 | ============ 3 | 4 | Translations will be placed in this folder when running:: 5 | 6 | python manage.py makemessages 7 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | from pathlib import Path 5 | 6 | if __name__ == "__main__": 7 | os.environ.setdefault( 8 | "DJANGO_SETTINGS_MODULE", "config.settings.local" 9 | ) 10 | 11 | try: 12 | from django.core.management import ( 13 | execute_from_command_line, 14 | ) 15 | except ImportError: 16 | # The above import may fail for some other reason. Ensure that the 17 | # issue is really that Django is missing to avoid masking other 18 | # exceptions on Python 2. 19 | try: 20 | import django # noqa 21 | except ImportError: 22 | raise ImportError( 23 | "Couldn't import Django. Are you sure it's installed and " 24 | "available on your PYTHONPATH environment variable? Did you " 25 | "forget to activate a virtual environment?" 26 | ) 27 | 28 | raise 29 | 30 | # This allows easy placement of apps within the interior 31 | # {{ cookiecutter.project_slug }} directory. 32 | current_path = Path(__file__).parent.resolve() 33 | sys.path.append(str(current_path / "{{ cookiecutter.project_slug }}")) 34 | 35 | execute_from_command_line(sys.argv) 36 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | addopts = --ds=config.settings.test --reuse-db -p no:warnings 3 | python_files = tests.py test_*.py 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is expected by Heroku. 2 | 3 | -r requirements/production.txt 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements/base.txt: -------------------------------------------------------------------------------- 1 | pytz==2020.1 # https://github.com/stub42/pytz 2 | python-slugify==4.0.1 # https://github.com/un33k/python-slugify 3 | Pillow==7.2.0 # https://github.com/python-pillow/Pillow 4 | whitenoise==5.2.0 # https://github.com/evansd/whitenoise 5 | unidecode==1.1.1 # https://pypi.org/project/Unidecode/ 6 | 7 | # Django 8 | # ------------------------------------------------------------------------------ 9 | django==3.1.1 # https://www.djangoproject.com/ 10 | django-environ==0.4.5 # https://github.com/joke2k/django-environ 11 | django-model-utils==4.0.0 # https://github.com/jazzband/django-model-utils 12 | django-allauth==0.42.0 # https://github.com/pennersr/django-allauth 13 | django-crispy-forms==1.9.2 # https://github.com/django-crispy-forms/django-crispy-forms 14 | django-autoslug==1.9.8 # https://github.com/justinmayer/django-autoslug/ 15 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements/local.txt: -------------------------------------------------------------------------------- 1 | -r ./base.txt 2 | 3 | Werkzeug==1.0.1 # https://github.com/pallets/werkzeug 4 | ipdb==0.13.3 # https://github.com/gotcha/ipdb 5 | {% if cookiecutter.windows == 'n' -%} 6 | psycopg2-binary==2.8.6 # https://github.com/psycopg/psycopg2 7 | {%- endif %} 8 | 9 | # Testing 10 | # ------------------------------------------------------------------------------ 11 | mypy==0.770, <0.780 # https://github.com/python/mypy 12 | # mypy >0.770 and <0.780 is required by django-stubs==1.5.0 13 | django-stubs==1.5.0 # https://github.com/typeddjango/django-stubs 14 | pytest==6.1.0 # https://github.com/pytest-dev/pytest 15 | pytest-sugar==0.9.4 # https://github.com/Frozenball/pytest-sugar 16 | django-test-plus==1.4.0 # https://github.com/revsys/django-test-plus 17 | 18 | # Code quality 19 | # ------------------------------------------------------------------------------ 20 | flake8==3.8.3 # https://github.com/PyCQA/flake8 21 | coverage==5.3 # https://github.com/nedbat/coveragepy 22 | black==20.8b1 # https://github.com/ambv/black 23 | pylint-django==2.3.0 # https://github.com/PyCQA/pylint-django 24 | pre-commit==2.7.1 # https://github.com/pre-commit/pre-commit 25 | 26 | # Django 27 | # ------------------------------------------------------------------------------ 28 | factory-boy==3.0.1 # https://github.com/FactoryBoy/factory_boy 29 | 30 | django-debug-toolbar==3.1 # https://github.com/jazzband/django-debug-toolbar 31 | django-extensions==3.0.9 # https://github.com/django-extensions/django-extensions 32 | django-coverage-plugin==1.8.0 # https://github.com/nedbat/django_coverage_plugin 33 | pytest-django==3.10.0 # https://github.com/pytest-dev/pytest-django 34 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements/production.txt: -------------------------------------------------------------------------------- 1 | # PRECAUTION: avoid production dependencies that aren't in development 2 | 3 | -r ./base.txt 4 | 5 | gunicorn==20.0.4 # https://github.com/benoitc/gunicorn 6 | psycopg2==2.8.6 --no-binary psycopg2 # https://github.com/psycopg/psycopg2 7 | Collectfast==2.2.0 # https://github.com/antonagestam/collectfast 8 | django-anymail[mailgun]==8.0 9 | django-redis==4.12.1 # https://github.com/jazzband/django-redis 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 4 | 5 | [pycodestyle] 6 | max-line-length = 120 7 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules 8 | 9 | [mypy] 10 | python_version = 3.8 11 | check_untyped_defs = True 12 | ignore_missing_imports = True 13 | warn_unused_ignores = True 14 | warn_redundant_casts = True 15 | warn_unused_configs = True 16 | plugins = mypy_django_plugin.main 17 | 18 | [mypy.plugins.django-stubs] 19 | django_settings_module = config.settings.test 20 | 21 | [mypy-*.migrations.*] 22 | # Django migrations should not produce any errors: 23 | ignore_errors = True 24 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | __version_info__ = tuple( 3 | [ 4 | int(num) if num.isdigit() else num 5 | for num in __version__.replace("-", ".", 1).split(".") 6 | ] 7 | ) 8 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.test import RequestFactory 3 | 4 | from {{ cookiecutter.project_slug }}.users.models import User 5 | from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory 6 | 7 | 8 | @pytest.fixture(autouse=True) 9 | def media_storage(settings, tmpdir): 10 | settings.MEDIA_ROOT = tmpdir.strpath 11 | 12 | 13 | @pytest.fixture 14 | def user() -> User: 15 | return UserFactory() 16 | 17 | 18 | @pytest.fixture 19 | def request_factory() -> RequestFactory: 20 | return RequestFactory() 21 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | import django.contrib.sites.models 2 | from django.contrib.sites.models import _simple_domain_name_validator 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [] 9 | 10 | operations = [ 11 | migrations.CreateModel( 12 | name="Site", 13 | fields=[ 14 | ( 15 | "id", 16 | models.AutoField( 17 | verbose_name="ID", 18 | serialize=False, 19 | auto_created=True, 20 | primary_key=True, 21 | ), 22 | ), 23 | ( 24 | "domain", 25 | models.CharField( 26 | max_length=100, 27 | verbose_name="domain name", 28 | validators=[_simple_domain_name_validator], 29 | ), 30 | ), 31 | ("name", models.CharField(max_length=50, verbose_name="display name")), 32 | ], 33 | options={ 34 | "ordering": ["domain"], 35 | "db_table": "django_site", 36 | "verbose_name": "site", 37 | "verbose_name_plural": "sites", 38 | }, 39 | bases=(models.Model,), 40 | managers=[("objects", django.contrib.sites.models.SiteManager()), ], 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py: -------------------------------------------------------------------------------- 1 | import django.contrib.sites.models 2 | from django.db import migrations, models 3 | 4 | 5 | class Migration(migrations.Migration): 6 | 7 | dependencies = [("sites", "0001_initial")] 8 | 9 | operations = [ 10 | migrations.AlterField( 11 | model_name="site", 12 | name="domain", 13 | field=models.CharField( 14 | max_length=100, 15 | unique=True, 16 | validators=[django.contrib.sites.models._simple_domain_name_validator], 17 | verbose_name="domain name", 18 | ), 19 | ) 20 | ] 21 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | from django.conf import settings 7 | from django.db import migrations 8 | 9 | 10 | def update_site_forward(apps, schema_editor): 11 | """Set site domain and name.""" 12 | Site = apps.get_model("sites", "Site") 13 | Site.objects.update_or_create( 14 | id=settings.SITE_ID, 15 | defaults={ 16 | "domain": "{{cookiecutter.domain_name}}", 17 | "name": "{{cookiecutter.project_name}}", 18 | }, 19 | ) 20 | 21 | 22 | def update_site_backward(apps, schema_editor): 23 | """Revert site domain and name to default.""" 24 | Site = apps.get_model("sites", "Site") 25 | Site.objects.update_or_create( 26 | id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"} 27 | ) 28 | 29 | 30 | class Migration(migrations.Migration): 31 | 32 | dependencies = [("sites", "0002_alter_domain_unique")] 33 | 34 | operations = [migrations.RunPython(update_site_forward, update_site_backward)] 35 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | To understand why this file is here, please read: 3 | 4 | http://cookiecutter-django.readthedocs.io/en/latest/faq.html#why-is-there-a-django-contrib-sites-directory-in-cookiecutter-django 5 | """ 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/css/project.css: -------------------------------------------------------------------------------- 1 | /* These styles are generated from project.scss. */ 2 | 3 | .alert-debug { 4 | color: black; 5 | background-color: white; 6 | border-color: #d6e9c6; 7 | } 8 | 9 | .alert-error { 10 | color: #b94a48; 11 | background-color: #f2dede; 12 | border-color: #eed3d7; 13 | } 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feldroy/django-crash-starter/b1a5b3f6d0a4c453c249de66f1c7027d769b0ea6/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/fonts/.gitkeep -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feldroy/django-crash-starter/b1a5b3f6d0a4c453c249de66f1c7027d769b0ea6/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/images/favicons/favicon.ico -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/js/project.js: -------------------------------------------------------------------------------- 1 | /* Project specific Javascript goes here. */ 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feldroy/django-crash-starter/b1a5b3f6d0a4c453c249de66f1c7027d769b0ea6/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/custom_bootstrap_vars.scss -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/static/sass/project.scss: -------------------------------------------------------------------------------- 1 | // project specific CSS goes here 2 | 3 | //////////////////////////////// 4 | //Variables// 5 | //////////////////////////////// 6 | 7 | // Alert colors 8 | 9 | $white: #fff; 10 | $mint-green: #d6e9c6; 11 | $black: #000; 12 | $pink: #f2dede; 13 | $dark-pink: #eed3d7; 14 | $red: #b94a48; 15 | 16 | //////////////////////////////// 17 | //Alerts// 18 | //////////////////////////////// 19 | 20 | // bootstrap alert CSS, translated to the django-standard levels of 21 | // debug, info, success, warning, error 22 | 23 | .alert-debug { 24 | background-color: $white; 25 | border-color: $mint-green; 26 | color: $black; 27 | } 28 | 29 | .alert-error { 30 | background-color: $pink; 31 | border-color: $dark-pink; 32 | color: $red; 33 | } 34 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/403.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "base.html" %} 2 | 3 | {% block title %}Forbidden (403){% endblock %} 4 | 5 | {% block content %} 6 |

Forbidden (403)

7 | 8 |

CSRF verification failed. Request aborted.

9 | {% endblock content %}{% endraw %} 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/404.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "base.html" %} 2 | 3 | {% block title %}Page not found{% endblock %} 4 | 5 | {% block content %} 6 |

Page not found

7 | 8 |

This is not the page you were looking for.

9 | {% endblock content %}{% endraw %} 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/500.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "base.html" %} 2 | 3 | {% block title %}Server Error{% endblock %} 4 | 5 | {% block content %} 6 |

Ooops!!! 500

7 | 8 |

Looks like something went wrong!

9 | 10 |

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

11 | {% endblock content %} 12 | 13 | {% endraw %} 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/account_inactive.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Account Inactive" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Account Inactive" %}

9 | 10 |

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

11 | {% endblock %} 12 | {% endraw %} 13 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/base.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "base.html" %} 2 | {% block title %}{% block head_title %}{% endblock head_title %}{% endblock title %} 3 | 4 | {% block content %} 5 |
6 |
7 | {% block inner %}{% endblock %} 8 |
9 |
10 | {% endblock %} 11 | {% endraw %} -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email.html: -------------------------------------------------------------------------------- 1 | {% raw %} 2 | {% extends "account/base.html" %} 3 | 4 | {% load i18n %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Account" %}{% endblock %} 8 | 9 | {% block inner %} 10 |

{% trans "E-mail Addresses" %}

11 | 12 | {% if user.emailaddress_set.all %} 13 |

{% trans 'The following e-mail addresses are associated with your account:' %}

14 | 15 | 44 | 45 | {% else %} 46 |

{% trans 'Warning:'%} {% trans "You currently do not have any e-mail address set up. You should really add an e-mail address so you can receive notifications, reset your password, etc." %}

47 | 48 | {% endif %} 49 | 50 | 51 |

{% trans "Add E-mail Address" %}

52 | 53 |
54 | {% csrf_token %} 55 | {{ form|crispy }} 56 | 57 |
58 | 59 | {% endblock %} 60 | 61 | 62 | {% block javascript %} 63 | {{ block.super }} 64 | 79 | {% endblock %} 80 | {% endraw %} 81 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Confirm E-mail Address" %}{% endblock %} 7 | 8 | 9 | {% block inner %} 10 |

{% trans "Confirm E-mail Address" %}

11 | 12 | {% if confirmation %} 13 | 14 | {% user_display confirmation.email_address.user as user_display %} 15 | 16 |

{% blocktrans with confirmation.email_address.email as email %}Please confirm that {{ email }} is an e-mail address for user {{ user_display }}.{% endblocktrans %}

17 | 18 |
19 | {% csrf_token %} 20 | 21 |
22 | 23 | {% else %} 24 | 25 | {% url 'account_email' as email_url %} 26 | 27 |

{% blocktrans %}This e-mail confirmation link expired or is invalid. Please issue a new e-mail confirmation request.{% endblocktrans %}

28 | 29 | {% endif %} 30 | 31 | {% endblock %} 32 | {% endraw %} 33 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account socialaccount %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Sign In" %}{% endblock %} 8 | 9 | {% block inner %} 10 | 11 |

{% trans "Sign In" %}

12 | 13 | {% get_providers as socialaccount_providers %} 14 | 15 | {% if socialaccount_providers %} 16 |

{% blocktrans with site.name as site_name %}Please sign in with one 17 | of your existing third party accounts. Or, sign up 18 | for a {{ site_name }} account and sign in below:{% endblocktrans %}

19 | 20 |
21 | 22 |
    23 | {% include "socialaccount/snippets/provider_list.html" with process="login" %} 24 |
25 | 26 | 27 | 28 |
29 | 30 | {% include "socialaccount/snippets/login_extra.html" %} 31 | 32 | {% else %} 33 |

{% blocktrans %}If you have not created an account yet, then please 34 | sign up first.{% endblocktrans %}

35 | {% endif %} 36 | 37 | 46 | 47 | {% endblock %} 48 | {% endraw %} 49 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Out" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Sign Out" %}

9 | 10 |

{% trans 'Are you sure you want to sign out?' %}

11 | 12 |
13 | {% csrf_token %} 14 | {% if redirect_field_value %} 15 | 16 | {% endif %} 17 | 18 |
19 | 20 | 21 | {% endblock %} 22 | {% endraw %} 23 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Change Password" %}

10 | 11 |
12 | {% csrf_token %} 13 | {{ form|crispy }} 14 | 15 |
16 | {% endblock %} 17 | {% endraw %} 18 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | {% load crispy_forms_tags %} 6 | 7 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 8 | 9 | {% block inner %} 10 | 11 |

{% trans "Password Reset" %}

12 | {% if user.is_authenticated %} 13 | {% include "account/snippets/already_logged_in.html" %} 14 | {% endif %} 15 | 16 |

{% trans "Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset it." %}

17 | 18 |
19 | {% csrf_token %} 20 | {{ form|crispy }} 21 | 22 |
23 | 24 |

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

25 | {% endblock %} 26 | {% endraw %} 27 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load account %} 5 | 6 | {% block head_title %}{% trans "Password Reset" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Password Reset" %}

10 | 11 | {% if user.is_authenticated %} 12 | {% include "account/snippets/already_logged_in.html" %} 13 | {% endif %} 14 | 15 |

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

16 | {% endblock %} 17 | {% endraw %} 18 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}

9 | 10 | {% if token_fail %} 11 | {% url 'account_reset_password' as passwd_reset_url %} 12 |

{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a new password reset.{% endblocktrans %}

13 | {% else %} 14 | {% if form %} 15 |
16 | {% csrf_token %} 17 | {{ form|crispy }} 18 | 19 |
20 | {% else %} 21 |

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

22 | {% endif %} 23 | {% endif %} 24 | {% endblock %} 25 | {% endraw %} 26 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% block head_title %}{% trans "Change Password" %}{% endblock %} 5 | 6 | {% block inner %} 7 |

{% trans "Change Password" %}

8 |

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

9 | {% endblock %} 10 | {% endraw %} 11 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/password_set.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Set Password" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Set Password" %}

10 | 11 |
12 | {% csrf_token %} 13 | {{ form|crispy }} 14 | 15 |
16 | {% endblock %} 17 | {% endraw %} 18 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | {% load crispy_forms_tags %} 5 | 6 | {% block head_title %}{% trans "Signup" %}{% endblock %} 7 | 8 | {% block inner %} 9 |

{% trans "Sign Up" %}

10 | 11 |

{% blocktrans %}Already have an account? Then please sign in.{% endblocktrans %}

12 | 13 | 21 | 22 | {% endblock %} 23 | {% endraw %} 24 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/signup_closed.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Sign Up Closed" %}

9 | 10 |

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

11 | {% endblock %} 12 | {% endraw %} 13 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verification_sent.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Verify Your E-mail Address" %}

9 | 10 |

{% 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 %}

11 | 12 | {% endblock %} 13 | {% endraw %} 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/account/verified_email_required.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "account/base.html" %} 2 | 3 | {% load i18n %} 4 | 5 | {% block head_title %}{% trans "Verify Your E-mail Address" %}{% endblock %} 6 | 7 | {% block inner %} 8 |

{% trans "Verify Your E-mail Address" %}

9 | 10 | {% url 'account_email' as email_url %} 11 | 12 |

{% blocktrans %}This part of the site requires us to verify that 13 | you are who you claim to be. For this purpose, we require that you 14 | verify ownership of your e-mail address. {% endblocktrans %}

15 | 16 |

{% blocktrans %}We have sent an e-mail to you for 17 | verification. Please click on the link inside this e-mail. Please 18 | contact us if you do not receive it within a few minutes.{% endblocktrans %}

19 | 20 |

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

21 | 22 | 23 | {% endblock %} 24 | {% endraw %} 25 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/base.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% load static i18n %} 2 | 3 | 4 | 5 | 6 | {% block title %}{% endraw %}{{ cookiecutter.project_name }}{% raw %}{% endblock title %} 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | {% block css %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endblock %} 27 | 28 | 29 | 30 | 31 | 32 |
33 | 69 | 70 |
71 | 72 |
73 | 74 | {% if messages %} 75 | {% for message in messages %} 76 |
{{ message }}
77 | {% endfor %} 78 | {% endif %} 79 | 80 | {% block content %} 81 |

{% endraw %}{{ cookiecutter.description }}{% raw %} This text is inherited on other pages!

82 | {% endblock content %} 83 | 84 |
85 | 86 | {% block modal %}{% endblock modal %} 87 | 88 | 90 | 91 | {% block javascript %} 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | {% endblock javascript %} 103 | 104 | 105 | {% endraw %} 106 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/about.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "base.html" %}{% endraw %} -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/pages/home.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "base.html" %}{% endraw %} -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_detail.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block title %}User: {{ object.username }}{% endblock %} 5 | 6 | {% block content %} 7 |
8 | 9 |
10 |
11 | 12 |

{{ object.username }}

13 | {% if object.name %} 14 |

{{ object.name }}

15 | {% endif %} 16 |
17 |
18 | 19 | {% if object == request.user %} 20 | 21 |
22 | 23 |
24 | My Info 25 | E-Mail 26 | 27 |
28 | 29 |
30 | 31 | {% endif %} 32 | 33 | 34 |
35 | {% endblock content %} 36 | {% endraw %} 37 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/templates/users/user_form.html: -------------------------------------------------------------------------------- 1 | {% raw %}{% extends "base.html" %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block title %}{{ user.username }}{% endblock %} 5 | 6 | {% block content %} 7 |

{{ user.username }}

8 |
9 | {% csrf_token %} 10 | {{ form|crispy }} 11 |
12 |
13 | 14 |
15 |
16 |
17 | {% endblock %}{% endraw %} 18 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feldroy/django-crash-starter/b1a5b3f6d0a4c453c249de66f1c7027d769b0ea6/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from allauth.account.adapter import DefaultAccountAdapter 4 | from allauth.socialaccount.adapter import ( 5 | DefaultSocialAccountAdapter, 6 | ) 7 | from django.conf import settings 8 | from django.http import HttpRequest 9 | 10 | 11 | class AccountAdapter(DefaultAccountAdapter): 12 | def is_open_for_signup(self, request: HttpRequest): 13 | return getattr( 14 | settings, "ACCOUNT_ALLOW_REGISTRATION", True 15 | ) 16 | 17 | 18 | class SocialAccountAdapter(DefaultSocialAccountAdapter): 19 | def is_open_for_signup( 20 | self, request: HttpRequest, sociallogin: Any 21 | ): 22 | return getattr( 23 | settings, "ACCOUNT_ALLOW_REGISTRATION", True 24 | ) 25 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth import admin as auth_admin 3 | from django.contrib.auth import get_user_model 4 | 5 | from {{ cookiecutter.project_slug }}.users.forms import ( 6 | UserChangeForm, 7 | UserCreationForm, 8 | ) 9 | 10 | User = get_user_model() 11 | 12 | 13 | @admin.register(User) 14 | class UserAdmin(auth_admin.UserAdmin): 15 | 16 | form = UserChangeForm 17 | add_form = UserCreationForm 18 | fieldsets = ( 19 | ("User", {"fields": ("name",)}), 20 | ) + auth_admin.UserAdmin.fieldsets 21 | list_display = ["username", "name", "is_superuser"] 22 | search_fields = ["name"] 23 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | from django.utils.translation import gettext_lazy as _ 3 | 4 | 5 | class UsersConfig(AppConfig): 6 | name = "{{ cookiecutter.project_slug }}.users" 7 | verbose_name = _("Users") 8 | 9 | def ready(self): 10 | try: 11 | import {{ cookiecutter.project_slug }}.users.signals # noqa F401 12 | except ImportError: 13 | pass 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/forms.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model, forms 2 | from django.core.exceptions import ValidationError 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | User = get_user_model() 6 | 7 | 8 | class UserChangeForm(forms.UserChangeForm): 9 | class Meta(forms.UserChangeForm.Meta): 10 | model = User 11 | 12 | 13 | class UserCreationForm(forms.UserCreationForm): 14 | 15 | error_message = forms.UserCreationForm.error_messages.update( 16 | { 17 | "duplicate_username": _( 18 | "This username has already been taken." 19 | ) 20 | } 21 | ) 22 | 23 | class Meta(forms.UserCreationForm.Meta): 24 | model = User 25 | 26 | def clean_username(self): 27 | username = self.cleaned_data["username"] 28 | 29 | try: 30 | User.objects.get(username=username) 31 | except User.DoesNotExist: 32 | return username 33 | 34 | raise ValidationError( 35 | self.error_messages["duplicate_username"] 36 | ) 37 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | import django.contrib.auth.models 2 | import django.contrib.auth.validators 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ("auth", "0012_alter_user_first_name_max_length"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="User", 18 | fields=[ 19 | ( 20 | "id", 21 | models.AutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("password", models.CharField(max_length=128, verbose_name="password")), 29 | ( 30 | "last_login", 31 | models.DateTimeField( 32 | blank=True, null=True, verbose_name="last login" 33 | ), 34 | ), 35 | ( 36 | "is_superuser", 37 | models.BooleanField( 38 | default=False, 39 | help_text="Designates that this user has all permissions without explicitly assigning them.", 40 | verbose_name="superuser status", 41 | ), 42 | ), 43 | ( 44 | "username", 45 | models.CharField( 46 | error_messages={ 47 | "unique": "A user with that username already exists." 48 | }, 49 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", 50 | max_length=150, 51 | unique=True, 52 | validators=[ 53 | django.contrib.auth.validators.UnicodeUsernameValidator() 54 | ], 55 | verbose_name="username", 56 | ), 57 | ), 58 | ( 59 | "first_name", 60 | models.CharField( 61 | blank=True, max_length=150, verbose_name="first name" 62 | ), 63 | ), 64 | ( 65 | "last_name", 66 | models.CharField( 67 | blank=True, max_length=150, verbose_name="last name" 68 | ), 69 | ), 70 | ( 71 | "email", 72 | models.EmailField( 73 | blank=True, max_length=254, verbose_name="email address" 74 | ), 75 | ), 76 | ( 77 | "is_staff", 78 | models.BooleanField( 79 | default=False, 80 | help_text="Designates whether the user can log into this admin site.", 81 | verbose_name="staff status", 82 | ), 83 | ), 84 | ( 85 | "is_active", 86 | models.BooleanField( 87 | default=True, 88 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", 89 | verbose_name="active", 90 | ), 91 | ), 92 | ( 93 | "date_joined", 94 | models.DateTimeField( 95 | default=django.utils.timezone.now, verbose_name="date joined" 96 | ), 97 | ), 98 | ( 99 | "name", 100 | models.CharField( 101 | blank=True, max_length=255, verbose_name="Name of User" 102 | ), 103 | ), 104 | ( 105 | "groups", 106 | models.ManyToManyField( 107 | blank=True, 108 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 109 | related_name="user_set", 110 | related_query_name="user", 111 | to="auth.Group", 112 | verbose_name="groups", 113 | ), 114 | ), 115 | ( 116 | "user_permissions", 117 | models.ManyToManyField( 118 | blank=True, 119 | help_text="Specific permissions for this user.", 120 | related_name="user_set", 121 | related_query_name="user", 122 | to="auth.Permission", 123 | verbose_name="user permissions", 124 | ), 125 | ), 126 | ], 127 | options={ 128 | "verbose_name_plural": "users", 129 | "verbose_name": "user", 130 | "abstract": False, 131 | }, 132 | managers=[("objects", django.contrib.auth.models.UserManager()), ], 133 | ), 134 | ] 135 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feldroy/django-crash-starter/b1a5b3f6d0a4c453c249de66f1c7027d769b0ea6/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | from django.urls import reverse 4 | from django.utils.translation import gettext_lazy as _ 5 | 6 | 7 | class User(AbstractUser): 8 | 9 | # First Name and Last Name Do Not Cover Name Patterns 10 | # Around the Globe. 11 | name = models.CharField( 12 | _("Name of User"), blank=True, max_length=255 13 | ) 14 | 15 | def get_absolute_url(self): 16 | return reverse( 17 | "users:detail", kwargs={"username": self.username} 18 | ) 19 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feldroy/django-crash-starter/b1a5b3f6d0a4c453c249de66f1c7027d769b0ea6/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Sequence 2 | 3 | from django.contrib.auth import get_user_model 4 | from factory import Faker, post_generation 5 | from factory.django import DjangoModelFactory 6 | 7 | 8 | class UserFactory(DjangoModelFactory): 9 | 10 | username = Faker("user_name") 11 | email = Faker("email") 12 | name = Faker("name") 13 | 14 | @post_generation 15 | def password( 16 | self, create: bool, extracted: Sequence[Any], **kwargs 17 | ): 18 | password = ( 19 | extracted 20 | if extracted 21 | else Faker( 22 | "password", 23 | length=42, 24 | special_chars=True, 25 | digits=True, 26 | upper_case=True, 27 | lower_case=True, 28 | ).generate(extra_kwargs={}) 29 | ) 30 | self.set_password(password) 31 | 32 | class Meta: 33 | model = get_user_model() 34 | django_get_or_create = ["username"] 35 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_forms.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from {{ cookiecutter.project_slug }}.users.forms import UserCreationForm 4 | from {{ cookiecutter.project_slug }}.users.tests.factories import UserFactory 5 | 6 | pytestmark = pytest.mark.django_db 7 | 8 | 9 | class TestUserCreationForm: 10 | def test_clean_username(self): 11 | # A user with proto_user params does not exist yet. 12 | proto_user = UserFactory.build() 13 | 14 | form = UserCreationForm( 15 | { 16 | "username": proto_user.username, 17 | "password1": proto_user._password, 18 | "password2": proto_user._password, 19 | } 20 | ) 21 | 22 | assert form.is_valid() 23 | assert form.clean_username() == proto_user.username 24 | 25 | # Creating a user. 26 | form.save() 27 | 28 | # The user with proto_user params already exists, 29 | # hence cannot be created. 30 | form = UserCreationForm( 31 | { 32 | "username": proto_user.username, 33 | "password1": proto_user._password, 34 | "password2": proto_user._password, 35 | } 36 | ) 37 | 38 | assert not form.is_valid() 39 | assert len(form.errors) == 1 40 | assert "username" in form.errors 41 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from {{ cookiecutter.project_slug }}.users.models import User 4 | 5 | pytestmark = pytest.mark.django_db 6 | 7 | 8 | def test_user_get_absolute_url(user: User): 9 | assert user.get_absolute_url() == f"/users/{user.username}/" 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.urls import reverse, resolve 3 | 4 | from {{ cookiecutter.project_slug }}.users.models import User 5 | 6 | pytestmark = pytest.mark.django_db 7 | 8 | 9 | def test_detail(user: User): 10 | assert ( 11 | reverse("users:detail", kwargs={"username": user.username}) 12 | == f"/users/{user.username}/" 13 | ) 14 | assert ( 15 | resolve(f"/users/{user.username}/").view_name 16 | == "users:detail" 17 | ) 18 | 19 | 20 | def test_update(): 21 | assert reverse("users:update") == "/users/~update/" 22 | assert resolve("/users/~update/").view_name == "users:update" 23 | 24 | 25 | def test_redirect(): 26 | assert reverse("users:redirect") == "/users/~redirect/" 27 | assert ( 28 | resolve("/users/~redirect/").view_name == "users:redirect" 29 | ) 30 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from django.test import RequestFactory 3 | from django.contrib.messages.middleware import MessageMiddleware 4 | from django.contrib.sessions.middleware import SessionMiddleware 5 | from django.urls import reverse 6 | 7 | from {{ cookiecutter.project_slug }}.users.models import User 8 | from {{ cookiecutter.project_slug }}.users.views import ( 9 | UserRedirectView, 10 | UserUpdateView, 11 | ) 12 | 13 | pytestmark = pytest.mark.django_db 14 | 15 | 16 | class TestUserUpdateView: 17 | """ 18 | TODO: 19 | extracting view initialization code as class-scoped fixture 20 | would be great if only pytest-django supported non-function-scoped 21 | fixture db access -- this is a work-in-progress for now: 22 | https://github.com/pytest-dev/pytest-django/pull/258 23 | """ 24 | 25 | def test_get_success_url( 26 | self, user: User, request_factory: RequestFactory 27 | ): 28 | view = UserUpdateView() 29 | request = request_factory.get("/fake-url/") 30 | request.user = user 31 | 32 | view.request = request 33 | 34 | assert view.get_success_url() == f"/users/{user.username}/" 35 | 36 | def test_get_object( 37 | self, user: User, request_factory: RequestFactory 38 | ): 39 | view = UserUpdateView() 40 | request = request_factory.get("/fake-url/") 41 | request.user = user 42 | 43 | view.request = request 44 | 45 | assert view.get_object() == user 46 | 47 | def test_form_valid( 48 | self, user: User, request_factory: RequestFactory 49 | ): 50 | form_data = {"name": "John Doe"} 51 | request = request_factory.post( 52 | reverse("users:update"), form_data 53 | ) 54 | request.user = user 55 | session_middleware = SessionMiddleware() 56 | session_middleware.process_request(request) 57 | msg_middleware = MessageMiddleware() 58 | msg_middleware.process_request(request) 59 | 60 | response = UserUpdateView.as_view()(request) 61 | user.refresh_from_db() 62 | 63 | assert response.status_code == 302 64 | assert user.name == form_data["name"] 65 | 66 | 67 | class TestUserRedirectView: 68 | def test_get_redirect_url( 69 | self, user: User, request_factory: RequestFactory 70 | ): 71 | view = UserRedirectView() 72 | request = request_factory.get("/fake-url") 73 | request.user = user 74 | 75 | view.request = request 76 | 77 | assert ( 78 | view.get_redirect_url() == f"/users/{user.username}/" 79 | ) 80 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from {{ cookiecutter.project_slug }}.users.views import ( 4 | user_redirect_view, 5 | user_update_view, 6 | user_detail_view, 7 | ) 8 | 9 | app_name = "users" 10 | urlpatterns = [ 11 | path("~redirect/", view=user_redirect_view, name="redirect"), 12 | path("~update/", view=user_update_view, name="update"), 13 | path("/", view=user_detail_view, name="detail"), 14 | ] 15 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import get_user_model 2 | from django.contrib.auth.mixins import LoginRequiredMixin 3 | from django.urls import reverse 4 | from django.views.generic import ( 5 | DetailView, 6 | RedirectView, 7 | UpdateView, 8 | ) 9 | 10 | User = get_user_model() 11 | 12 | 13 | class UserDetailView(LoginRequiredMixin, DetailView): 14 | model = User 15 | # These Next Two Lines Tell the View to Index 16 | # Lookups by Username 17 | slug_field = "username" 18 | slug_url_kwarg = "username" 19 | 20 | 21 | user_detail_view = UserDetailView.as_view() 22 | 23 | 24 | class UserUpdateView(LoginRequiredMixin, UpdateView): 25 | fields = [ 26 | "name", 27 | ] 28 | 29 | # We already imported user in the View code above, 30 | # remember? 31 | model = User 32 | 33 | # Send the User Back to Their Own Page after a 34 | # successful Update 35 | def get_success_url(self): 36 | return reverse( 37 | "users:detail", 38 | kwargs={'username': self.request.user.username}, 39 | ) 40 | 41 | def get_object(self): 42 | # Only Get the User Record for the 43 | # User Making the Request 44 | return User.objects.get( 45 | username=self.request.user.username 46 | ) 47 | 48 | 49 | user_update_view = UserUpdateView.as_view() 50 | 51 | 52 | class UserRedirectView(LoginRequiredMixin, RedirectView): 53 | permanent = False 54 | 55 | def get_redirect_url(self): 56 | return reverse( 57 | "users:detail", 58 | kwargs={"username": self.request.user.username}, 59 | ) 60 | 61 | 62 | user_redirect_view = UserRedirectView.as_view() 63 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feldroy/django-crash-starter/b1a5b3f6d0a4c453c249de66f1c7027d769b0ea6/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/utils/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | def settings_context(_request): 5 | # Put global template variables here. 6 | return {"DEBUG": settings.DEBUG} # explicit 7 | --------------------------------------------------------------------------------