├── LICENSE ├── cookiecutter.json ├── hooks └── post_gen_project.py ├── readme.md └── {{cookiecutter.project_slug}} ├── .dockerignore ├── .ebextensions └── django.config ├── .elasticbeanstalk └── eb_create_environment.yml ├── .gitignore ├── .platform ├── confighooks │ └── predeploy │ │ └── predeploy.sh ├── hooks │ └── predeploy │ │ └── predeploy.sh └── httpd │ └── conf.d │ └── ssl_rewrite.conf ├── .pre-commit-config.yaml ├── Dockerfile ├── common ├── __init__.py ├── admin.py ├── apps.py ├── constants.py ├── context_processors.py ├── forms.py ├── managers.py ├── middleware │ ├── __init__.py │ ├── maintenance_mode_middleware.py │ └── user_action_tracking.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── templates │ ├── base_templates │ │ └── base.html │ ├── common │ │ ├── index.html │ │ ├── maintenance_mode.html │ │ ├── sample_django_react.html │ │ └── sample_form.html │ └── errors │ │ ├── 404.html │ │ └── 500.html ├── tests.py ├── urls.py └── views.py ├── config ├── .env.example ├── __init__.py ├── settings.py ├── setup.cfg ├── urls.py ├── webpack_loader.py └── wsgi.py ├── manage.py ├── nwb.config.js ├── package.json ├── readme.md ├── requirements-dev.in ├── requirements.in ├── src ├── Components │ └── Hello.js └── Pages │ └── Home.js └── static ├── favicon.png └── styles ├── _variables.scss ├── base.scss └── pages └── index.scss /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Zagaran, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_name": "[PROJECT]", 3 | "project_slug": "{{ cookiecutter.project_name.lower()|replace(' ', '_')|replace('-', '_')|replace('.', '_')|trim() }}", 4 | 5 | "feature_annotations": ["off", "on"], 6 | "reference_examples": ["on", "off"], 7 | 8 | "bootstrap_messages": ["enabled", "disabled"], 9 | "crispy_forms": ["enabled", "disabled"], 10 | "debug_toolbar": ["enabled", "disabled"], 11 | "django_react": ["enabled", "disabled"], 12 | "django_ses": ["enabled", "disabled"], 13 | "django_social": ["enabled", "disabled"], 14 | "django_storages": ["enabled", "disabled"], 15 | "docker": ["enabled", "disabled"], 16 | "elastic_beanstalk": ["enabled", "disabled"], 17 | "pre_commit": ["enabled", "disabled"], 18 | "sass_bootstrap": ["enabled", "disabled"], 19 | "security_settings": ["enabled", "disabled"], 20 | "sentry": ["enabled", "disabled"], 21 | "user_action_tracking": ["enabled", "disabled"] 22 | } 23 | -------------------------------------------------------------------------------- /hooks/post_gen_project.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import os 3 | import shutil 4 | from os import path 5 | 6 | 7 | CONDITIONAL_REMOVE_PATHS = [ 8 | "{% if cookiecutter.elastic_beanstalk == 'disabled' %}.ebextensions{% endif %}", 9 | "{% if cookiecutter.elastic_beanstalk == 'disabled' %}.elasticbeanstalk{% endif %}", 10 | "{% if cookiecutter.elastic_beanstalk == 'disabled' %}.platform{% endif %}", 11 | "{% if cookiecutter.docker == 'disabled' %}Dockerfile{% endif %}", 12 | "{% if cookiecutter.docker == 'disabled' %}.dockerignore{% endif %}", 13 | "{% if cookiecutter.pre_commit == 'disabled' %}.pre-commit-config.yaml{% endif %}", 14 | "{% if cookiecutter.django_react == 'disabled' %}config/webpack_loader.py{% endif %}", 15 | "{% if cookiecutter.django_react == 'disabled' %}nwb.config.js{% endif %}", 16 | "{% if cookiecutter.django_react == 'disabled' and cookiecutter.sass_bootstrap == 'disabled' %}package.json{% endif %}", 17 | ] 18 | 19 | 20 | def main(): 21 | delete_conditional_paths() 22 | delete_empty_files() 23 | print_next_steps() 24 | 25 | 26 | def delete_conditional_paths(): 27 | for path in CONDITIONAL_REMOVE_PATHS: 28 | if path and os.path.exists(path): 29 | if os.path.isdir(path): 30 | shutil.rmtree(path) 31 | else: 32 | os.remove(path) 33 | 34 | 35 | def delete_empty_files(): 36 | exempt_files = ["__init__.py"] 37 | file_paths = glob.glob("**", recursive=True) 38 | 39 | for file_path in file_paths: 40 | if not path.isfile(file_path): 41 | continue 42 | if path.split(file_path)[-1] in exempt_files: 43 | continue 44 | with open(file_path, 'r') as file: 45 | try: 46 | contents = file.read() 47 | except UnicodeDecodeError: 48 | pass 49 | if not contents.strip(): 50 | os.remove(file_path) 51 | 52 | 53 | def print_next_steps(): 54 | print("\n\nWelcome to your new project.") 55 | print("\ncd {{cookiecutter.project_slug}}") 56 | print("\nSet up a virtual environment (https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment)") 57 | print("Then run the following commands for first-time setup:\n") 58 | commands = [ 59 | "pip install pip-tools", 60 | "pip-compile requirements.in --upgrade", 61 | "pip-compile requirements-dev.in --upgrade", 62 | "pip install -r requirements-dev.txt", 63 | "cp config/.env.example config/.env", 64 | "python manage.py makemigrations", 65 | ] 66 | if "{{ cookiecutter.elastic_beanstalk }}".lower() == "enabled": 67 | commands.append("git add --chmod=+x -- .platform/*/*/*.sh") 68 | for command in commands: 69 | print(command) 70 | print("\nSee the README for the rest of the local setup instructions") 71 | 72 | 73 | if __name__ == "__main__": 74 | main() 75 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # How To Use This Template 2 | 3 | This template is a [cookiecutter](https://github.com/cookiecutter/cookiecutter) template. Create a project from it using the following: 4 | ``` 5 | pip install "cookiecutter>=1.7.0" 6 | cookiecutter https://github.com/zagaran/django-template 7 | ``` 8 | 9 | The cookiecutter command will give you an interactive prompt to choose which optional features to inlcude (see below) 10 | 11 | Once you have cloned the project, [create a virtual environment](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment) 12 | 13 | Then run the following: 14 | 15 | ``` 16 | pip install pip-tools 17 | pip-compile requirements.in --upgrade 18 | pip-compile requirements-dev.in --upgrade 19 | pip install -r requirements-dev.txt 20 | cp config/.env.example config/.env 21 | python manage.py makemigrations 22 | 23 | # If using the `elastic_beanstalk` feature 24 | git add --chmod=+x -- .platform///*.sh 25 | 26 | # Then see the generated README in your new project for the rest of the local setup instructions 27 | ``` 28 | 29 | If you have an existing project, you can see a project based on this template here: https://github.com/zagaran/sample-django-app 30 | 31 | # Included Optional Features 32 | 33 | There are a number of optional features that are present in this template. You will be prompted for whether to include each one as part of running `cookiecutter`. 34 | 35 | ## `feature_annotations` (off by default) 36 | 37 | If you turn feature annotations on, the code for each optional feature will be bracketed by comments such as 38 | `# START_FEATURE feature_name` and `# END_FEATURE feature_name`. 39 | 40 | 41 | ## `reference_examples` (on by default) 42 | If you turn on reference examples, the codebase will have a number of reference examples. These are all marked with one of the following comments: 43 | 44 | ``` 45 | # TODO: delete me; this is just a reference example 46 | // TODO: delete me; this is just a reference example 47 | {# TODO: delete me; this is just a reference example #} 48 | ``` 49 | 50 | 51 | ## Django messages integration with Bootstrap (`bootstrap_messages`) 52 | 53 | 54 | ## Crispy Forms integration (`crispy_forms`) 55 | 56 | 57 | ## Debug Toolbar integration (`debug_toolbar`) 58 | 59 | 60 | ## Django-React integration, aka Djangre (`django_react`) 61 | 62 | ### Files 63 | 64 | The following files and folders are only needed if `django_react` is a desired feature in your app and can be safely 65 | deleted from projects which do not leverage the feature. 66 | 67 | - `nwb.config.js` 68 | - `package.json` 69 | - `webpack-stats.json` 70 | - `config/webpack_loader.py` 71 | - `src/` 72 | 73 | ### Additional Setup 74 | 75 | When using this feature, make sure to install the Node.js requirements using the manager of your choice 76 | (either `npm install` or `yarn install` will work) before proceeding with development. 77 | 78 | ### Special Consideration for Running 79 | 80 | For development on localhost when using Django-React, you should run the following command in a separate terminal to 81 | your standard `runserver` command. 82 | 83 | ``` 84 | nwb serve --no-vendor # Note: refer to the nwb docs on when to use --no-vendor vs not 85 | ``` 86 | 87 | If you have configured everything correctly, you should see each command complete and notify you 88 | that the project is ready to be viewed. 89 | 90 | - If you include `nwb` as a dependency, you can use the locally-installed `nwb` by running `node_modules/.bin/nwb serve --no-vendor` instead of relying on a globally installed `nwb`. 91 | 92 | ### Adding a new React component 93 | 94 | In this paradigm, React components are compiled and injected into the standard Django template. This means we can take 95 | advantage of the built-in templating functionality of Django and, with a bit of elbow grease, use the power of React to 96 | make those templates responsive. 97 | 98 | `django-react-loader` uses the same basic pattern for any component: 99 | 100 | 1. First, ensure that the library is loaded in your template: `{% load django_react_components %}` 101 | 2. Next, ensure that you have rendered the React runtime bundle: `{% render_bundle 'runtime' %}` 102 | - Note that you only have to do this once per page where React components will be used. 103 | 3. Finally, load your React component on the page. `{% react_component 'Component' %}` 104 | - You can add any number of props as named keywords, e.g. `{% react_component 'Home' id='home' prop1=value_from_context %}` 105 | - You can also choose to pass props as an object instead of individual kwargs, e.g. `{% react_component 'Hello' id='hello' props=sample_props %}`. 106 | 107 | ### Preparing for deployment 108 | 109 | The preferred option for deployment is to add the below compilation step to the deployment configuration rather than 110 | building it locally. However, if you wish to build the app locally: 111 | 112 | - run `nwb build --no-vendor`. This will generate or replace a `webpack_bundles` folder in your `/static` folder 113 | populated with the compiled React components. This then allows `collectstatic` to collect these static assets and 114 | make them available via the usual static assets pipeline set up in the deploy configuration. 115 | - Note that calling `nwb build` does not remove existing compiled data from your static folder. If you are building 116 | static assets locally and committing them to the repo rather than implementing a deploy compilation step, it is import 117 | to delete `/static/webpack_bundles` (in addition to wherever your existing static files are, e.g. 118 | `/staticfiles/webpack_bundles`) before running another build, as even without code changes NWB will generate new 119 | compiled JS files without removing the old ones. If you have implemented a `collecstatic` step in your deployment, 120 | ensure that existing webpack bundles are deleted before the new assets are generated by NWB. 121 | 122 | ### Other notes 123 | 124 | - If you use `nwb serve` in your local development environment, you may see a persistent XHR error in the console -- a 125 | request by the app to something like `http://localhost:8000/sockjs-node/info?t=123456789`. This is normal and will 126 | not appear on production or otherwise effect the function of your app - it is because the React components are being 127 | served by the browser in a different environment than the React components expect to be in. 128 | 129 | 130 | ## AWS SES integration (`django_ses`) 131 | 132 | 133 | ## Third-party authentication integrations (`django_social`) 134 | 135 | 136 | ## AWS S3 (or other cloud blob storage) integration (`django_storages`) 137 | 138 | ## Docker integration (`docker`) 139 | 140 | 141 | ## Elastic Beanstalk deployment (`elastic_beanstalk`) 142 | 143 | As a default for web applications, we strongly recommend using Elastic Beanstalk. 144 | 145 | To create a new deployment, [set up your local AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) e.g. ~/.aws/config, 146 | 147 | Ensure shell files in the `.platform` directory are executable according to git. 148 | You can check if they are executable via `git ls-files -s .platform`; 149 | you should see `100755` before any shell files in the output of this command. 150 | If you see `100644` before any of your shell files, 151 | run `git add --chmod=+x -- .platform/*/*/*.sh` to make them executable. 152 | 153 | Set desired parameters `.elasticbeanstalk/eb-create-environment.yml` 154 | 155 | Use [eb-create-environment](https://github.com/zagaran/eb-create-environment/): 156 | ``` 157 | eb-create-environment --config .elasticbeanstalk/eb-create-environment.yml 158 | ``` 159 | 160 | To update an existing deployment 161 | ``` 162 | eb deploy [ENVIRONMENT_NAME] 163 | ``` 164 | 165 | To SSH into a deployment 166 | 167 | Use [eb-ssm](https://github.com/zagaran/eb-ssm/): 168 | ``` 169 | eb-ssm [ENVIRONMENT_NAME] 170 | ``` 171 | 172 | ## Pre-commit hooks (`pre_commit`) 173 | You can configure pre-commit with `.pre-commit-config.yaml` 174 | 175 | See https://pre-commit.com/hooks.html for more hook options. 176 | 177 | To run style checks and desired formatters: 178 | ``` 179 | pre-commit run --all-files 180 | ``` 181 | If wish to install pre-commit as a pre-commit git hook, you can run (optional): 182 | ``` 183 | pre-commit install 184 | ``` 185 | 186 | 187 | ## Sass compilation (`sass_bootstrap`) 188 | 189 | Use this feature to enable Sass processing and Bootstrap styling. 190 | 191 | While you can just include Bootstrap's styling/js via a CDN, using this feature allows you to customize Bootstrap to the 192 | style guide of your project, as well as define custom styling in a cleaner and more maintainable way (compared to plain 193 | CSS). The Bootstrap part of this integration could be swapped out for any other frontend styling framework that also 194 | uses Sass, but there really is no reason to write vanilla CSS. 195 | 196 | In local development, you can simply write scss files and include them using `sass_tags` and your stylesheets should 197 | automatically recompile in reload. This also works seamlessly with `collectstatic` for deploys. 198 | 199 | Note: If you aren't already using npm to install bootstrap, you can alternatively clone the contents of Bootstrap's sass 200 | files directly into your static directory and change your references to point there. There is currently no good way to 201 | install Bootstrap source code using just python. 202 | 203 | ### Production notes 204 | 205 | In development, `.scss` files are compiled on the fly. However, when deploying, these files must be manually generated 206 | using `python manage.py compilescss`. Also note that if your styles folder is in a directory that's collected with 207 | `collectstatic`, you should add the `--ignore *.scss` flag to avoid exposing the raw `.scss` files as staticfiles. 208 | 209 | 210 | ## Security settings (`security_settings`) 211 | These are the recommended security settings. [Explanations for all Django settings can be found here](https://docs.djangoproject.com/en/3.2/ref/settings/). Please pay particular note to what are appropriate cookie and subdomain settings for your application. 212 | 213 | ## Sentry integration (`sentry`) 214 | 215 | 216 | ## User Action Tracking (`user_action_tracking`) 217 | 218 | This feature tracks all URLs accessed by users (along with the status code and user agent) in a table called `UserAction`. 219 | This can be useful for debugging, for analytics, or for auditing. There is a setting `USER_TRACKING_EXEMPT_ROUTES` where 220 | you can add the names of routes that should be excluded from action tracking because they would not be useful 221 | (for example, if your site has a keep_alive route that the frontend regulalry hits automatically). Note that only 222 | actions by authenticated users are tracked. 223 | 224 | # Optional Settings 225 | 226 | `MAINTENANCE_MODE`: Set this flag on a server environment to stop all user requests to the site, such as when you need to make substantial server updates or run a complex database migration. 227 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore pyc files 2 | **/*.pyc 3 | 4 | # Ignore local databases 5 | **/*.sqlite3 6 | 7 | # Local environment files 8 | */.env 9 | 10 | # Environments 11 | .venv 12 | 13 | # Editors 14 | .idea/ 15 | .vscode/ 16 | 17 | # Operating system metafiles 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.ebextensions/django.config: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.feature_annotations == "on" -%}# START_FEATURE elastic_beanstalk{%- endif %} 2 | packages: 3 | yum: 4 | postgresql15-server-devel: [] 5 | nodejs: [] 6 | htop: [] 7 | 8 | container_commands: 9 | 01_update_pip: 10 | command: | 11 | source $PYTHONPATH/activate 12 | pip install --upgrade pip 13 | 02_install_requirements: 14 | command: | 15 | source $PYTHONPATH/activate 16 | pip install -r requirements.txt 17 | 03_migratedb: 18 | command: | 19 | source $PYTHONPATH/activate 20 | python manage.py migrate --noinput 21 | leader_only: true 22 | 23 | option_settings: 24 | aws:elasticbeanstalk:environment:proxy: 25 | ProxyServer: apache 26 | aws:elasticbeanstalk:application:environment: 27 | DJANGO_SETTINGS_MODULE: "config.settings" 28 | aws:elasticbeanstalk:container:python: 29 | WSGIPath: config.wsgi:application 30 | aws:elasticbeanstalk:environment:proxy:staticfiles: 31 | /static: staticfiles 32 | aws:elasticbeanstalk:environment:process:default: 33 | HealthCheckPath: /health-check/ 34 | 35 | files: 36 | "/home/ec2-user/.bashrc": 37 | mode: "000644" 38 | owner: ec2-user 39 | group: ec2-user 40 | content: | 41 | # .bashrc 42 | # Source global definitions 43 | if [ -f /etc/bashrc ]; then 44 | . /etc/bashrc 45 | fi 46 | # User specific aliases and functions 47 | set -a; source <(sudo cat /opt/elasticbeanstalk/deployment/env); cd /var/app/current 48 | alias db="cd /var/app/current/; python manage.py shell_plus" 49 | 50 | "/home/ec2-user/.inputrc": 51 | mode: "000644" 52 | owner: ec2-user 53 | group: ec2-user 54 | content: | 55 | ## arrow up 56 | "\e[A":history-search-backward 57 | ## arrow down 58 | "\e[B":history-search-forward 59 | 60 | "/opt/elasticbeanstalk/tasks/taillogs.d/cfn-init-cmd.conf": 61 | mode: "000755" 62 | owner: root 63 | group: root 64 | content: | 65 | /var/log/cfn-init-cmd.log 66 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE elastic_beanstalk{%- endif %} 67 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.elasticbeanstalk/eb_create_environment.yml: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.feature_annotations == "on" -%}# START_FEATURE elastic_beanstalk{%- endif %} 2 | # https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options-general.html 3 | ElasticBeanstalk: 4 | # aws elasticbeanstalk list-available-solution-stacks --region us-east-1 5 | SolutionStackName: "64bit Amazon Linux 2023 * Python 3.11" 6 | NumProcesses: "1" 7 | InstanceTypes: "t3.micro" 8 | IamInstanceProfile: "aws-elasticbeanstalk-ec2-role" 9 | AssociatePublicIpAddress: True 10 | ProxyServer: "apache" # apache, nginx 11 | InstancePublicSubnets: True 12 | # Omit the load balancer block to have it be not load balanced 13 | LoadBalancer: 14 | LoadBalancerType: "application" 15 | SSLCertificateId: null 16 | ELBScheme: "public" # public, private 17 | MinSize: "1" 18 | MaxSize: "1" 19 | PublicSubnets: True 20 | # Omit the managed updates block to disable managed updates 21 | ManagedUpdates: 22 | PreferredStartTime: "TUE:05:15" 23 | UpdateLevel: "minor" # patch, minor 24 | ServiceRoleForManagedUpdates: "AWSServiceRoleForElasticBeanstalkManagedUpdates" 25 | RDS: 26 | AllocatedStorage: 100 27 | DBInstanceClass: "db.t3.micro" 28 | MasterUsername: "db_admin" 29 | BackupRetentionPeriod: 30 30 | MultiAZ: False 31 | AutoMinorVersionUpgrade: False 32 | PubliclyAccessible: False 33 | StorageType: "gp2" 34 | StorageEncrypted: True 35 | CopyTagsToSnapshot: True 36 | MonitoringInterval: 0 # 60 but 0 disables this 37 | DeletionProtection: False 38 | MaxAllocatedStorage: 1000 39 | Postgres: 40 | DBName: "ebdb" 41 | Engine: "postgres" 42 | EngineVersion: "15.*" 43 | Port: 5432 44 | DBParameterGroupName: "default.postgres15" 45 | LicenseModel: "postgresql-license" 46 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE elastic_beanstalk{%- endif %} 47 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Installer logs 10 | pip-log.txt 11 | pip-delete-this-directory.txt 12 | 13 | # Unit test / coverage reports 14 | htmlcov/ 15 | .tox/ 16 | .nox/ 17 | .coverage 18 | .coverage.* 19 | .cache 20 | nosetests.xml 21 | coverage.xml 22 | *.cover 23 | *.py,cover 24 | .hypothesis/ 25 | .pytest_cache/ 26 | cover/ 27 | 28 | # Translations 29 | *.mo 30 | *.pot 31 | 32 | # Django stuff: 33 | *.log 34 | *.sqlite3 35 | *.sqlite3-journal 36 | 37 | # Environments 38 | .env 39 | .venv 40 | env/ 41 | venv/ 42 | ENV/ 43 | env.bak/ 44 | venv.bak/ 45 | 46 | # Editors 47 | .idea 48 | .vscode 49 | 50 | # Operating systems 51 | .DS_Store 52 | 53 | # Installed packages 54 | node_modules/ 55 | 56 | # Compiled Files 57 | staticfiles/ 58 | staticfiles/* 59 | # START_FEATURE sass_bootstrap 60 | /static/styles/**/*.css 61 | /static/styles/**/*.map 62 | # END_FEATURE sass_bootstrap 63 | # START_FEATURE django_react 64 | static/webpack_bundles/ 65 | webpack-stats.json 66 | # END_FEATURE django_react 67 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.platform/confighooks/predeploy/predeploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | {% if cookiecutter.feature_annotations == "on" -%}# START_FEATURE elastic_beanstalk{%- endif %} 5 | source $PYTHONPATH/activate 6 | 7 | {% if cookiecutter.django_react == "enabled" or cookiecutter.sass_bootstrap == "enabled" -%} 8 | {%- if cookiecutter.feature_annotations == "on" -%}# START_FEATURE django_react, sass_bootstrap{%- endif %} 9 | NODE_OPTIONS=--max_old_space_size=1000 npm install --production 10 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE django_react, sass_bootstrap{%- endif %} 11 | {%- endif %} 12 | 13 | {% if cookiecutter.django_react == "enabled" -%} 14 | {%- if cookiecutter.feature_annotations == "on" -%}# START_FEATURE django_react{%- endif %} 15 | # delete old webpack static resources 16 | rm -rf static/webpack_bundles/ || echo "no webpack bundles to remove" 17 | rm -rf staticfiles/webpack_bundles/ || echo "no staticfiles webpack bundles to remove" 18 | NODE_OPTIONS=--openssl-legacy-provider node_modules/.bin/nwb build --no-vendor 19 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE django_react{%- endif %} 20 | {%- endif %} 21 | 22 | {% if cookiecutter.sass_bootstrap == "enabled" -%} 23 | {%- if cookiecutter.feature_annotations == "on" -%}# START_FEATURE sass_bootstrap{%- endif %} 24 | python manage.py compilescss 25 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE sass_bootstrap{%- endif %} 26 | {%- endif %} 27 | 28 | python manage.py collectstatic --noinput --ignore *.scss 29 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE elastic_beanstalk{%- endif %} 30 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.platform/hooks/predeploy/predeploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | {% if cookiecutter.feature_annotations == "on" -%}# START_FEATURE elastic_beanstalk{%- endif %} 5 | source $PYTHONPATH/activate 6 | 7 | {% if cookiecutter.django_react == "enabled" or cookiecutter.sass_bootstrap == "enabled" -%} 8 | {%- if cookiecutter.feature_annotations == "on" -%}# START_FEATURE django_react, sass_bootstrap{%- endif %} 9 | NODE_OPTIONS=--max_old_space_size=1000 npm install --production 10 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE django_react, sass_bootstrap{%- endif %} 11 | {%- endif %} 12 | 13 | {% if cookiecutter.django_react == "enabled" -%} 14 | {%- if cookiecutter.feature_annotations == "on" -%}# START_FEATURE django_react{%- endif %} 15 | # delete old webpack static resources 16 | rm -rf static/webpack_bundles/ || echo "no webpack bundles to remove" 17 | rm -rf staticfiles/webpack_bundles/ || echo "no staticfiles webpack bundles to remove" 18 | NODE_OPTIONS=--openssl-legacy-provider node_modules/.bin/nwb build --no-vendor 19 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE django_react{%- endif %} 20 | {%- endif %} 21 | 22 | {% if cookiecutter.sass_bootstrap == "enabled" -%} 23 | {%- if cookiecutter.feature_annotations == "on" -%}# START_FEATURE sass_bootstrap{%- endif %} 24 | python manage.py compilescss 25 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE sass_bootstrap{%- endif %} 26 | {%- endif %} 27 | 28 | python manage.py collectstatic --noinput --ignore *.scss 29 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE elastic_beanstalk{%- endif %} 30 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.platform/httpd/conf.d/ssl_rewrite.conf: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.feature_annotations == "on" -%}# START_FEATURE elastic_beanstalk{%- endif %} 2 | RewriteEngine On 3 | 4 | RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R,L] 5 | 6 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE elastic_beanstalk{%- endif %} 7 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.feature_annotations == "on" -%}# START_FEATURE pre_commit{%- endif %} 2 | # See https://pre-commit.com for more information 3 | # See https://pre-commit.com/hooks.html for more hooks 4 | default_language_version: 5 | python: python3.8 6 | exclude: /migrations/ 7 | repos: 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v3.2.0 10 | hooks: 11 | - id: trailing-whitespace 12 | - id: end-of-file-fixer 13 | - id: check-yaml 14 | - id: check-merge-conflict 15 | - id: check-added-large-files 16 | - repo: https://gitlab.com/PyCQA/flake8 17 | rev: 3.9.0 18 | hooks: 19 | - id: flake8 20 | types: [file, python] 21 | args: ['--config=config/setup.cfg'] 22 | - repo: https://github.com/pycqa/isort 23 | rev: 5.8.0 24 | hooks: 25 | - id: isort 26 | types: [file, python] 27 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE pre_commit{%- endif %} 28 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/Dockerfile: -------------------------------------------------------------------------------- 1 | {% if cookiecutter.feature_annotations == "on" -%}# START_FEATURE docker{%- endif %} 2 | FROM python:3.11.4-slim-buster 3 | 4 | WORKDIR /app 5 | 6 | ADD requirements.txt /app/requirements.txt 7 | 8 | RUN set -ex \ 9 | && buildDeps=" \ 10 | build-essential \ 11 | libpq-dev \ 12 | " \ 13 | && deps=" \ 14 | postgresql-client \ 15 | " \ 16 | && apt-get update && apt-get install -y $buildDeps $deps --no-install-recommends \ 17 | && pip install --no-cache-dir -r /app/requirements.txt \ 18 | && apt-get purge -y --auto-remove $buildDeps \ 19 | $(! command -v gpg > /dev/null || echo 'gnupg dirmngr') \ 20 | && rm -rf /var/lib/apt/lists/* 21 | 22 | ENV VIRTUAL_ENV /env 23 | ENV PATH /env/bin:$PATH 24 | 25 | {% if cookiecutter.django_react == "enabled" -%} 26 | {% if cookiecutter.feature_annotations == "on" -%}# START_FEATURE django_react{%- endif %} 27 | COPY ./nwb.config.js /app/nwb.config.js 28 | COPY ./package.json /app/package.json 29 | COPY ./package-lock.json /app/package-lock.json 30 | RUN npm install 31 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE django_react{%- endif %} 32 | 33 | {% endif -%} 34 | 35 | 36 | COPY . /app/ 37 | COPY ./config/.env.example /app/config/.env 38 | 39 | {% if cookiecutter.django_react == "enabled" -%} 40 | {% if cookiecutter.feature_annotations == "on" -%}# START_FEATURE django_react{%- endif %} 41 | RUN ./node_modules/.bin/nwb build --no-vendor 42 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE django_react{%- endif %} 43 | 44 | {% endif -%} 45 | 46 | 47 | {% if cookiecutter.sass_bootstrap == "enabled" -%} 48 | {%- if cookiecutter.feature_annotations == "on" -%}# START_FEATURE sass_bootstrap{%- endif %} 49 | RUN python manage.py compilescss 50 | {% if cookiecutter.feature_annotations == "on" -%}# END_FEATURE sass_bootstrap{%- endif %} 51 | 52 | {% endif -%} 53 | 54 | RUN python manage.py collectstatic --noinput 55 | 56 | RUN rm /app/config/.env 57 | 58 | EXPOSE 8000 59 | 60 | CMD ["gunicorn", "--bind", ":8000", "--workers", "3", "config.wsgi:application"] 61 | {% if cookiecutter.feature_annotations == "on" %}# END_FEATURE docker{%- endif %} 62 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zagaran/django-template/92fb23aae04d6bbdcbc308152982c083395ca480/{{cookiecutter.project_slug}}/common/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommonConfig(AppConfig): 5 | name = 'common' 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/constants.py: -------------------------------------------------------------------------------- 1 | from django.db.models import TextChoices 2 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/context_processors.py: -------------------------------------------------------------------------------- 1 | def django_settings(request): 2 | from django.conf import settings 3 | return { 4 | "PRODUCTION": settings.PRODUCTION, 5 | "LOCALHOST": settings.LOCALHOST, 6 | } 7 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/forms.py: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.crispy_forms == "enabled" -%} 2 | {%- if cookiecutter.feature_annotations == "on" -%} 3 | # START_FEATURE crispy_forms 4 | {%- endif %} 5 | from django import forms 6 | 7 | from crispy_forms.helper import FormHelper 8 | from crispy_forms.layout import Submit 9 | 10 | 11 | class CrispyFormMixin(object): 12 | submit_label = "Save" 13 | form_action = "" 14 | 15 | def __init__(self, *args, **kwargs): 16 | super().__init__(*args, **kwargs) 17 | self.helper = FormHelper() 18 | self.helper.form_method = "POST" 19 | self.helper.form_action = self.form_action 20 | self.helper.add_input(Submit("submit", self.submit_label)) 21 | {%- if cookiecutter.feature_annotations == "on" %} 22 | # END_FEATURE crispy_forms 23 | {%- endif %} 24 | {%- endif %} 25 | 26 | 27 | {%- if cookiecutter.reference_examples == "on" %} 28 | {%- if cookiecutter.crispy_forms == "enabled" %} 29 | 30 | {% if cookiecutter.feature_annotations == "on" %} 31 | # START_FEATURE crispy_forms 32 | {%- endif %} 33 | class SampleForm(CrispyFormMixin, forms.Form): 34 | # TODO: delete me; this is just a reference example 35 | is_company = forms.CharField(label="company", required=False, widget=forms.CheckboxInput()) 36 | email = forms.EmailField( 37 | label="email", max_length=30, required=True, widget=forms.TextInput(), help_text="Insert your email" 38 | ) 39 | first_name = forms.CharField(label="first name", max_length=5, required=True, widget=forms.TextInput()) 40 | last_name = forms.CharField(label="last name", max_length=5, required=True, widget=forms.TextInput()) 41 | datetime_field = forms.SplitDateTimeField(label="date time", widget=forms.SplitDateTimeWidget()) 42 | 43 | def clean(self): 44 | super().clean() 45 | password1 = self.cleaned_data.get("password1", None) 46 | password2 = self.cleaned_data.get("password2", None) 47 | if not password1 and not password2 or password1 != password2: 48 | raise forms.ValidationError("Passwords dont match") 49 | return self.cleaned_data 50 | {%- if cookiecutter.feature_annotations == "on" %} 51 | # END_FEATURE crispy_forms 52 | {%- endif %} 53 | {%- endif %} 54 | {%- endif %} 55 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/managers.py: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.django_social == "enabled" -%} 2 | {%- if cookiecutter.feature_annotations == "on" -%} 3 | # START_FEATURE django_social 4 | {%- endif %} 5 | from django.contrib.auth.base_user import BaseUserManager 6 | 7 | 8 | class UserManager(BaseUserManager): 9 | 10 | """ 11 | Custom user model manager where email is the unique identifiers 12 | for authentication instead of usernames. 13 | """ 14 | def create_user(self, email, password=None, **extra_fields): 15 | """ 16 | Create and save a User with the given email and password. 17 | """ 18 | if not email: 19 | raise ValueError('The Email must be set') 20 | email = self.normalize_email(email) 21 | user = self.model(email=email, **extra_fields) 22 | user.set_password(password) 23 | user.save() 24 | return user 25 | 26 | def create_superuser(self, email, password, **extra_fields): 27 | """ 28 | Create and save a SuperUser with the given email and password. 29 | """ 30 | extra_fields.setdefault('is_staff', True) 31 | extra_fields.setdefault('is_superuser', True) 32 | extra_fields.setdefault('is_active', True) 33 | 34 | if extra_fields.get('is_staff') is not True: 35 | raise ValueError('Superuser must have is_staff=True.') 36 | if extra_fields.get('is_superuser') is not True: 37 | raise ValueError('Superuser must have is_superuser=True.') 38 | return self.create_user(email, password, **extra_fields) 39 | {%- if cookiecutter.feature_annotations == "on" %} 40 | # END_FEATURE django_social 41 | {%- endif %} 42 | {%- endif %} 43 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/middleware/__init__.py: -------------------------------------------------------------------------------- 1 | from common.middleware.maintenance_mode_middleware import MaintenanceModeMiddleware 2 | {%- if cookiecutter.user_action_tracking == "enabled" %} 3 | {%- if cookiecutter.feature_annotations == "on" %} 4 | # START_FEATURE user_action_tracking 5 | {%- endif %} 6 | from common.middleware.user_action_tracking import UserActionTrackingMiddleware 7 | {%- if cookiecutter.feature_annotations == "on" %} 8 | # END_FEATURE user_action_tracking 9 | {%- endif %} 10 | {%- endif %} 11 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/middleware/maintenance_mode_middleware.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.core.exceptions import MiddlewareNotUsed 3 | from django.http.response import HttpResponse 4 | from django.template import loader 5 | 6 | 7 | class MaintenanceModeMiddleware: 8 | def __init__(self, get_response): 9 | # One-time configuration and initialization. 10 | self.get_response = get_response 11 | 12 | # Disable middleware if MAINTENANCE_MODE is not on 13 | if not settings.MAINTENANCE_MODE: 14 | raise MiddlewareNotUsed() 15 | 16 | def __call__(self, request): 17 | # Render the template directly without the request to skip other steps 18 | content = loader.render_to_string("common/maintenance_mode.html") 19 | return HttpResponse(content) 20 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/middleware/user_action_tracking.py: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.user_action_tracking == "enabled" -%} 2 | {%- if cookiecutter.feature_annotations == "on" -%} 3 | # START_FEATURE user_action_tracking 4 | {%- endif %} 5 | from django.conf import settings 6 | from common.models import UserAction 7 | 8 | 9 | class UserActionTrackingMiddleware: 10 | def __init__(self, get_response): 11 | self.get_response = get_response 12 | 13 | def __call__(self, request): 14 | response = self.get_response(request) 15 | self.create_user_action(request, response) 16 | return response 17 | 18 | def create_user_action(self, request, response): 19 | try: 20 | user = request.user 21 | except AttributeError: 22 | return 23 | 24 | if not user.is_authenticated: 25 | return 26 | 27 | url_name = None 28 | if request.resolver_match: 29 | url_name = request.resolver_match.url_name 30 | 31 | if url_name in settings.USER_TRACKING_EXEMPT_ROUTES: 32 | return 33 | 34 | UserAction.objects.create( 35 | user=user, 36 | url=request.build_absolute_uri(), 37 | method=request.method, 38 | url_name=url_name, 39 | status_code=response.status_code, 40 | user_agent=request.META.get("HTTP_USER_AGENT") 41 | ) 42 | {%- if cookiecutter.feature_annotations == "on" %} 43 | # END_FEATURE user_action_tracking 44 | {%- endif %} 45 | {%- endif %} 46 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.6 on 2021-10-04 19:37 2 | 3 | import common.models 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import django.utils.timezone 8 | import uuid 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | ('auth', '0012_alter_user_first_name_max_length'), 17 | ] 18 | 19 | operations = [ 20 | migrations.CreateModel( 21 | name='User', 22 | fields=[ 23 | ('password', models.CharField(max_length=128, verbose_name='password')), 24 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 25 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 26 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 28 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 29 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 30 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 31 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 32 | ('created_on', models.DateTimeField(auto_now_add=True)), 33 | ('updated_on', models.DateTimeField(auto_now=True)), 34 | ('email', models.EmailField(max_length=254, unique=True)), 35 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), 36 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), 37 | ], 38 | options={ 39 | 'verbose_name': 'user', 40 | 'verbose_name_plural': 'users', 41 | 'abstract': False, 42 | }, 43 | ), 44 | migrations.CreateModel( 45 | name='UserAction', 46 | fields=[ 47 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 48 | ('created_on', models.DateTimeField(auto_now_add=True)), 49 | ('updated_on', models.DateTimeField(auto_now=True)), 50 | ('url', models.URLField(max_length=2083)), 51 | ('method', models.CharField(max_length=64)), 52 | ('url_name', models.CharField(max_length=256, null=True)), 53 | ('status_code', models.IntegerField()), 54 | ('user_agent', models.TextField(null=True)), 55 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='user_actions', to=settings.AUTH_USER_MODEL)), 56 | ], 57 | options={ 58 | 'abstract': False, 59 | }, 60 | ), 61 | ] 62 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zagaran/django-template/92fb23aae04d6bbdcbc308152982c083395ca480/{{cookiecutter.project_slug}}/common/migrations/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.contrib.auth.models import AbstractUser 4 | from django.db import models 5 | 6 | {% if cookiecutter.django_social == "enabled" -%} 7 | from common.managers import UserManager 8 | {%- endif %} 9 | 10 | 11 | class TimestampedModel(models.Model): 12 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 13 | 14 | created_on = models.DateTimeField(auto_now_add=True) 15 | updated_on = models.DateTimeField(auto_now=True) 16 | 17 | def update(self, update_dict=None, **kwargs): 18 | """ Helper method to update objects """ 19 | if not update_dict: 20 | update_dict = kwargs 21 | update_fields = {"updated_on"} 22 | for k, v in update_dict.items(): 23 | setattr(self, k, v) 24 | update_fields.add(k) 25 | self.save(update_fields=update_fields) 26 | 27 | class Meta: 28 | abstract = True 29 | 30 | 31 | # Create your models here. 32 | class User(AbstractUser, TimestampedModel): 33 | email = models.EmailField(unique=True) 34 | {%- if cookiecutter.django_social == "enabled" %} 35 | {%- if cookiecutter.feature_annotations == "on" %} 36 | # START_FEATURE django_social 37 | {%- endif %} 38 | username = None # disable the AbstractUser.username field 39 | USERNAME_FIELD = "email" 40 | REQUIRED_FIELDS = [] 41 | 42 | objects = UserManager() 43 | {%- if cookiecutter.feature_annotations == "on" %} 44 | # END_FEATURE django_social 45 | {%- endif %} 46 | {%- endif %} 47 | 48 | {%- if cookiecutter.reference_examples == "on" %} 49 | {%- if cookiecutter.django_storages == "enabled" %} 50 | {%- if cookiecutter.feature_annotations == "on" %} 51 | 52 | 53 | # START_FEATURE django_storages 54 | {%- endif %} 55 | # TODO: delete me; this is just a reference example 56 | def get_s3_path(instance, filename): 57 | return "%s/%s/%s" % ( 58 | "uploads", 59 | instance.user_id, 60 | filename, 61 | ) 62 | 63 | 64 | class UploadFile(TimestampedModel): 65 | user = models.ForeignKey(User, related_name="files", on_delete=models.PROTECT) 66 | file = models.FileField( 67 | max_length=1024, 68 | upload_to=get_s3_path 69 | ) 70 | 71 | class Meta: 72 | abstract = True 73 | {%- if cookiecutter.feature_annotations == "on" %} 74 | # END_FEATURE django_storages 75 | {%- endif %} 76 | {%- endif %} 77 | {%- endif %} 78 | {%- if cookiecutter.user_action_tracking == "enabled" %} 79 | {%- if cookiecutter.feature_annotations == "on" %} 80 | 81 | 82 | # START_FEATURE user_action_tracking 83 | {%- endif %} 84 | class UserAction(TimestampedModel): 85 | user = models.ForeignKey(User, related_name="user_actions", on_delete=models.PROTECT) 86 | url = models.URLField(max_length=2083) 87 | method = models.CharField(max_length=64) 88 | url_name = models.CharField(max_length=256, null=True) 89 | status_code = models.IntegerField() 90 | user_agent = models.TextField(null=True) 91 | {%- if cookiecutter.feature_annotations == "on" %} 92 | # END_FEATURE user_action_tracking 93 | {%- endif %} 94 | {%- endif %} 95 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/templates/base_templates/base.html: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 2 | {%- if cookiecutter.feature_annotations == "on" -%} 3 | {% raw %}{# START_FEATURE sass_bootstrap #}{% endraw %} 4 | {%- endif %} 5 | {%- raw %} 6 | {% load sass_tags %} 7 | {%- endraw %} 8 | {%- if cookiecutter.feature_annotations == "on" %} 9 | {% raw %}{# END_FEATURE sass_bootstrap #}{% endraw %} 10 | {%- endif %} 11 | {%- endif %} 12 | 13 | {%- raw %} 14 | 15 | 16 | 17 | 18 | 19 | {% endraw %}{{ cookiecutter.project_name }}{% raw %} - {% block title %}{% endblock %} 20 | {%- endraw %} 21 | 22 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 23 | {%- if cookiecutter.feature_annotations == "on" %} 24 | {% raw %}{# START_FEATURE sass_bootstrap #}{% endraw %} 25 | {%- endif %} 26 | {%- raw %} 27 | 28 | {%- endraw %} 29 | {%- if cookiecutter.feature_annotations == "on" %} 30 | {% raw %}{# END_FEATURE sass_bootstrap #}{% endraw %} 31 | {%- endif %} 32 | {%- else %} 33 | {%- raw %} 34 | 35 | {%- endraw %} 36 | {%- endif %} 37 | 38 | {%- raw %} 39 | 40 | {% block head %}{% endblock %} 41 | 42 | 43 | 86 | {% if not PRODUCTION and not LOCALHOST %} 87 |
88 |
89 |
90 |
91 |

Warning

92 |

This is a test site. Do not enter personal or sensitive data on this site.

93 |
94 |
95 |
96 | {% endif %} 97 | 98 | {% endraw %} 99 | {%- if cookiecutter.bootstrap_messages == "enabled" %} 100 | {%- if cookiecutter.feature_annotations == "on" -%} 101 | {% raw %}{# START_FEATURE bootstrap_messages #}{% endraw %} 102 | {%- endif %} 103 | {%- raw %} 104 |
105 | {% for message in messages %} 106 | 112 | {% endfor %} 113 | {% block body %}{% endblock %} 114 |
115 | {% endraw %} 116 | {%- if cookiecutter.feature_annotations == "on" -%} 117 | {% raw %}{# END_FEATURE bootstrap_messages #}{% endraw %} 118 | {%- endif %} 119 | {%- endif %} 120 | {%- raw %} 121 | 122 | 123 | 124 | {%- endraw %} 125 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/templates/common/index.html: -------------------------------------------------------------------------------- 1 | {%- raw -%} 2 | {% extends "base_templates/base.html" %} 3 | 4 | {% endraw %} 5 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 6 | {%- if cookiecutter.feature_annotations == "on" %} 7 | {% raw %}{# START_FEATURE sass_bootstrap #}{% endraw %} 8 | {%- endif %} 9 | {%- raw %} 10 | {% load sass_tags %} 11 | {%- endraw %} 12 | {%- if cookiecutter.feature_annotations == "on" %} 13 | {% raw %}{# END_FEATURE sass_bootstrap #}{% endraw %} 14 | {%- endif %} 15 | {%- endif %} 16 | {%- raw %} 17 | 18 | {% block title %}Home{% endblock %} 19 | 20 | {% block head %} 21 | {% endraw %} 22 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 23 | {%- if cookiecutter.feature_annotations == "on" %} 24 | {% raw %}{# START_FEATURE sass_bootstrap #}{% endraw %} 25 | {%- endif %} 26 | {%- raw %} 27 | 28 | {%- endraw %} 29 | {%- if cookiecutter.feature_annotations == "on" %} 30 | {% raw %}{# END_FEATURE sass_bootstrap #}{% endraw %} 31 | {%- endif %} 32 | {%- endif %} 33 | {%- raw %} 34 | {% endblock %} 35 | 36 | {% block body %} 37 |
38 |

Welcome to {% endraw %}{{ cookiecutter.project_name }}{% raw %}. Please log in to continue.

39 | {% endraw %} 40 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 41 | {%- if cookiecutter.feature_annotations == "on" %} 42 | {% raw %}{# START_FEATURE sass_bootstrap #}{% endraw %} 43 | {%- endif %} 44 | {%- if cookiecutter.reference_examples == "on" %} 45 | {%- raw %} 46 | {# TODO: delete me; this is just a reference example #} 47 | 48 | {%- endraw %} 49 | {%- endif %} 50 | {%- if cookiecutter.feature_annotations == "on" %} 51 | {% raw %}{# END_FEATURE sass_bootstrap #}{% endraw %} 52 | {%- endif %} 53 | {%- endif %} 54 | {%- raw %} 55 |
56 | {% endblock %} 57 | {%- endraw %} 58 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/templates/common/maintenance_mode.html: -------------------------------------------------------------------------------- 1 | {%- raw -%} 2 | {% extends "base_templates/base.html" %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block title %}Down for Maintenance{% endblock %} 6 | 7 | {% block body %} 8 |

This site is currently down for maintenance

9 |

Please check back soon. Thank you for your patience.

10 | {% endblock %} 11 | {%- endraw %} 12 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/templates/common/sample_django_react.html: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.reference_examples == "on" -%} 2 | {%- if cookiecutter.django_react == "enabled" -%} 3 | {%- if cookiecutter.feature_annotations == "on" -%} 4 | {% raw %}{# START_FEATURE django_react #}{% endraw %} 5 | {%- endif %} 6 | {%- raw %} 7 | 8 | {# TODO: delete me; this is just a reference example #} 9 | 10 | {% extends 'base_templates/base.html' %} 11 | {% load django_react_components %} 12 | {% load render_bundle from webpack_loader %} 13 | 14 | {% block body %} 15 | {% render_bundle 'runtime' %} 16 | {% render_bundle 'Home' %} 17 | {% render_bundle 'Hello' %} 18 | {% react_component 'Home' id='home' message=message %} 19 | {% react_component 'Hello' id='hello' props=sample_props %} 20 | {% endblock %} 21 | 22 | {%- endraw %} 23 | {%- if cookiecutter.feature_annotations == "on" %} 24 | 25 | {% raw %}{# END_FEATURE django_react #}{% endraw %} 26 | {%- endif %} 27 | {%- endif %} 28 | {%- endif %} 29 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/templates/common/sample_form.html: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.reference_examples == "on" -%} 2 | {%- if cookiecutter.crispy_forms == "enabled" -%} 3 | {%- if cookiecutter.feature_annotations == "on" -%} 4 | {% raw %}{# START_FEATURE crispy_forms #}{% endraw %} 5 | {%- endif %} 6 | {%- raw %} 7 | {# TODO: delete me; this is just a reference example #} 8 | {% extends "base_templates/base.html" %} 9 | {% load crispy_forms_tags %} 10 | 11 | {% block title %}Sample Form{% endblock %} 12 | 13 | {% block body %} 14 | {% crispy form %} 15 | {% endblock %} 16 | {%- endraw %} 17 | {%- if cookiecutter.feature_annotations == "on" %} 18 | {% raw %}{# END_FEATURE crispy_forms #}{% endraw %} 19 | {%- endif %} 20 | {%- endif %} 21 | {%- endif %} 22 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/templates/errors/404.html: -------------------------------------------------------------------------------- 1 | {%- raw -%} 2 | {% extends "base_templates/base.html" %} 3 | 4 | {% block title %}Not Found (404){% endblock %} 5 | 6 | {% block body %} 7 |
8 |

Page not found (404)

9 |

The page you are looking for doesn't exist.

10 | Back to home 11 |
12 | {% endblock %} 13 | {%- endraw %} 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/templates/errors/500.html: -------------------------------------------------------------------------------- 1 | {%- raw -%} 2 | {% extends "base_templates/base.html" %} 3 | 4 | {% block title %}Internal Server Error (500){% endblock %} 5 | 6 | {% block body %} 7 |
8 |

Internal Server Error (500)

9 |

The system encountered an error. Please try again later.

10 | Back to home 11 |
12 | {% endblock %} 13 | {%- endraw %} 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.urls import include, path 3 | 4 | from common import views 5 | 6 | urlpatterns = [ 7 | path("", views.IndexView.as_view(), name="index"), 8 | {%- if cookiecutter.reference_examples == "on" %} 9 | {%- if cookiecutter.django_react == "enabled" %} 10 | {%- if cookiecutter.feature_annotations == "on" %} 11 | # START_FEATURE django_react 12 | {%- endif %} 13 | # TODO: delete me; this is just a reference example 14 | path("django-react/", views.DjangoReactView.as_view(), name='django_react_demo'), 15 | {%- if cookiecutter.feature_annotations == "on" %} 16 | # END_FEATURE django_react 17 | {%- endif %} 18 | {%- endif %} 19 | {%- endif %} 20 | path("logout", views.LogoutView.as_view(), name="logout"), 21 | path("robots.txt", views.RobotsTxtView.as_view(), name="robots_txt"), 22 | ] 23 | {%- if cookiecutter.debug_toolbar == "enabled" %} 24 | {%- if cookiecutter.feature_annotations == "on" %} 25 | 26 | # START_FEATURE debug_toolbar 27 | {%- endif %} 28 | if settings.DEBUG_TOOLBAR: 29 | urlpatterns += [ 30 | path("__debug__/", include("debug_toolbar.urls")), 31 | ] 32 | {%- if cookiecutter.feature_annotations == "on" %} 33 | # END_FEATURE debug_toolbar 34 | {%- endif %} 35 | {%- endif %} 36 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/common/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import logout 2 | from django.conf import settings 3 | from django.shortcuts import redirect, render 4 | from django.views.generic.base import TemplateView, View 5 | from django.http.response import HttpResponse 6 | {%- if cookiecutter.crispy_forms == "enabled" %} 7 | {%- if cookiecutter.feature_annotations == "on" %} 8 | # START_FEATURE crispy_forms 9 | {%- endif %} 10 | from django.views.generic.edit import FormView 11 | 12 | {%- if cookiecutter.reference_examples == "on" %} 13 | from common.forms import SampleForm 14 | {%- endif %} 15 | {%- if cookiecutter.feature_annotations == "on" %} 16 | # END_FEATURE crispy_forms 17 | {%- endif %} 18 | {%- endif %} 19 | 20 | 21 | class IndexView(TemplateView): 22 | template_name = "common/index.html" 23 | 24 | 25 | class LogoutView(View): 26 | def post(self, request): 27 | logout(request) 28 | return redirect("index") 29 | 30 | 31 | class RobotsTxtView(View): 32 | def get(self, request): 33 | if settings.PRODUCTION: 34 | # Allow all (note that a blank Disallow block means "allow all") 35 | lines = ["User-agent: *", "Disallow:"] 36 | else: 37 | # Block all 38 | lines = ["User-agent: *", "Disallow: /"] 39 | return HttpResponse("\n".join(lines), content_type="text/plain") 40 | {%- if cookiecutter.reference_examples == "on" %} 41 | {%- if cookiecutter.django_react == "enabled" %} 42 | 43 | 44 | {% if cookiecutter.feature_annotations == "on" %} 45 | # START_FEATURE django_react 46 | {%- endif %} 47 | class DjangoReactView(TemplateView): 48 | # TODO: delete me; this is just a reference example 49 | template_name = 'common/sample_django_react.html' 50 | 51 | def get_context_data(self, **kwargs): 52 | context = super().get_context_data(**kwargs) 53 | context['hello_msg'] = 'Component' 54 | context['sample_props'] = {'msg': 'sample props'} 55 | return context 56 | {%- if cookiecutter.feature_annotations == "on" %} 57 | # END_FEATURE django_react 58 | {%- endif %} 59 | {%- endif %} 60 | {%- endif %} 61 | {%- if cookiecutter.reference_examples == "on" %} 62 | {%- if cookiecutter.crispy_forms == "enabled" %} 63 | 64 | {% if cookiecutter.feature_annotations == "on" %} 65 | # START_FEATURE crispy_forms 66 | {%- endif %} 67 | class SampleFormView(FormView): 68 | # TODO: delete me; this is just a reference example 69 | form_class = SampleForm 70 | {%- if cookiecutter.feature_annotations == "on" %} 71 | # END_FEATURE crispy_forms 72 | {%- endif %} 73 | {%- endif %} 74 | {%- endif %} 75 | 76 | def error_404(request, exception): 77 | return render(request, "errors/404.html", status=404) 78 | 79 | def error_500(request): 80 | return render(request, "errors/500.html", status=500) 81 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/.env.example: -------------------------------------------------------------------------------- 1 | DEBUG=True 2 | {%- if cookiecutter.debug_toolbar == "enabled" %} 3 | {%- if cookiecutter.feature_annotations == "on" %} 4 | # START_FEATURE debug_toolbar 5 | {%- endif %} 6 | DEBUG_TOOLBAR=True 7 | {%- if cookiecutter.feature_annotations == "on" %} 8 | # END_FEATURE debug_toolbar 9 | {%- endif %} 10 | {%- endif %} 11 | LOCALHOST=True 12 | PRODUCTION=False 13 | SECRET_KEY=[FILL_ME_IN] 14 | DATABASE_URL=sqlite:///db.sqlite3 15 | MAINTENANCE_MODE=False 16 | {%- if cookiecutter.django_social == "enabled" %} 17 | {%- if cookiecutter.feature_annotations == "on" %} 18 | # START_FEATURE django_social 19 | {%- endif %} 20 | GOOGLE_OAUTH2_KEY=[FILL_ME_IN] 21 | GOOGLE_OAUTH2_SECRET=[FILL_ME_IN] 22 | {%- if cookiecutter.feature_annotations == "on" %} 23 | # END_FEATURE django_social 24 | {%- endif %} 25 | {%- endif %} 26 | {%- if cookiecutter.sentry == "enabled" %} 27 | {%- if cookiecutter.feature_annotations == "on" %} 28 | # START_FEATURE sentry 29 | {%- endif %} 30 | SENTRY_DSN=[FILL_ME_IN] 31 | {%- if cookiecutter.feature_annotations == "on" %} 32 | # END_FEATURE sentry 33 | {%- endif %} 34 | {%- endif %} 35 | {%- if cookiecutter.django_react == "enabled" %} 36 | {%- if cookiecutter.feature_annotations == "on" %} 37 | # START_FEATURE django_react 38 | {%- endif %} 39 | # this setting is only relevant to local development 40 | WEBPACK_LOADER_HOTLOAD=False 41 | {%- if cookiecutter.feature_annotations == "on" %} 42 | # END_FEATURE django_react 43 | {%- endif %} 44 | {%- endif %} 45 | {%- if cookiecutter.django_ses == "enabled" %} 46 | {%- if cookiecutter.feature_annotations == "on" %} 47 | # START_FEATURE django_ses 48 | {%- endif %} 49 | DEFAULT_FROM_EMAIL=[FILL_ME_IN] 50 | {%- if cookiecutter.feature_annotations == "on" %} 51 | # END_FEATURE django_ses 52 | {%- endif %} 53 | {%- endif %} 54 | 55 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zagaran/django-template/92fb23aae04d6bbdcbc308152982c083395ca480/{{cookiecutter.project_slug}}/config/__init__.py -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/2.2/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/2.2/ref/settings/ 9 | """ 10 | 11 | import os 12 | 13 | from django.contrib.messages import constants as messages 14 | 15 | import environ 16 | 17 | 18 | env = environ.Env( 19 | # Sets Django's ALLOWED_HOSTS setting 20 | ALLOWED_HOSTS=(list, []), 21 | # Sets Django's DEBUG setting 22 | DEBUG=(bool, False), 23 | # Set to True when running locally for development purposes 24 | LOCALHOST=(bool, False), 25 | # Set to True in order to put the site in maintenance mode 26 | MAINTENANCE_MODE=(bool, False), 27 | # Set to True on the production server environment; setting to False makes the 28 | # site have a "deny all" robots.txt and a non-production warning on all pages 29 | PRODUCTION=(bool, True), 30 | {%- if cookiecutter.django_react == "enabled" %} 31 | {%- if cookiecutter.feature_annotations == "on" %} 32 | 33 | # START_FEATURE django_react 34 | {%- endif %} 35 | # Set to True to use JavaScript assets served on localhost:3000 via `nwb serve` 36 | WEBPACK_LOADER_HOTLOAD=(bool, False), 37 | {%- if cookiecutter.feature_annotations == "on" %} 38 | # END_FEATURE django_react 39 | {%- endif %} 40 | {%- endif %} 41 | {%- if cookiecutter.django_ses == "enabled" %} 42 | {%- if cookiecutter.feature_annotations == "on" %} 43 | 44 | # START_FEATURE django_ses 45 | {%- endif %} 46 | # Set to configure AWS SES to run in a region other than us-east-1 47 | AWS_SES_REGION_NAME=(str, "us-east-1"), 48 | AWS_SES_REGION_ENDPOINT=(str, "email.us-east-1.amazonaws.com"), 49 | {%- if cookiecutter.feature_annotations == "on" %} 50 | # END_FEATURE django_ses 51 | {%- endif %} 52 | {%- endif %} 53 | {%- if cookiecutter.sentry == "enabled" %} 54 | {%- if cookiecutter.feature_annotations == "on" %} 55 | 56 | # START_FEATURE sentry 57 | {%- endif %} 58 | # Set to the DSN from sentry.io to send errors to Sentry 59 | SENTRY_DSN=(str, None), 60 | {%- if cookiecutter.feature_annotations == "on" %} 61 | # END_FEATURE sentry 62 | {%- endif %} 63 | {%- endif %} 64 | {%- if cookiecutter.debug_toolbar == "enabled" %} 65 | {%- if cookiecutter.feature_annotations == "on" %} 66 | 67 | # START_FEATURE debug_toolbar 68 | {%- endif %} 69 | # Set to True to enable the Django Debug Toolbar 70 | DEBUG_TOOLBAR=(bool, False), 71 | {%- if cookiecutter.feature_annotations == "on" %} 72 | # END_FEATURE debug_toolbar 73 | {%- endif %} 74 | {%- endif %} 75 | ) 76 | # If ALLWED_HOSTS has been configured, then we're running on a server and 77 | # can skip looking for a .env file (this assumes that .env files 78 | # file is only used for local development and servers use environment variables) 79 | if not env("ALLOWED_HOSTS"): 80 | environ.Env.read_env() 81 | 82 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 83 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 84 | 85 | # SECURITY WARNING: keep the secret key used in production secret! 86 | SECRET_KEY = env("SECRET_KEY") 87 | 88 | # SECURITY WARNING: do not run with debug turned on in production! 89 | DEBUG = env("DEBUG") 90 | {%- if cookiecutter.debug_toolbar == "enabled" %} 91 | {% if cookiecutter.feature_annotations == "on" %} 92 | # START_FEATURE debug_toolbar 93 | {%- endif %} 94 | DEBUG_TOOLBAR = DEBUG and env("DEBUG_TOOLBAR") 95 | {%- if cookiecutter.feature_annotations == "on" %} 96 | # END_FEATURE debug_toolbar 97 | {%- endif %} 98 | {%- endif %} 99 | 100 | # run with this set to False on server environments 101 | LOCALHOST = env("LOCALHOST") 102 | 103 | # set PRODUCTION to be False on non-production server environments to prevent 104 | # them from being indexed by search engines and to have a banner warning 105 | # that this is not the production site 106 | PRODUCTION = env("PRODUCTION") 107 | 108 | ALLOWED_HOSTS = env("ALLOWED_HOSTS") 109 | if LOCALHOST is True: 110 | ALLOWED_HOSTS = ["127.0.0.1", "localhost"] 111 | {%- if cookiecutter.elastic_beanstalk == "enabled" %} 112 | else: 113 | {%- if cookiecutter.feature_annotations == "on" %} 114 | # START_FEATURE elastic_beanstalk 115 | {%- endif %} 116 | # if using AWS hosting 117 | from ec2_metadata import ec2_metadata 118 | ALLOWED_HOSTS.append(ec2_metadata.private_ipv4) 119 | {%- if cookiecutter.feature_annotations == "on" %} 120 | # END_FEATURE elastic_beanstalk 121 | {%- endif %} 122 | {%- endif %} 123 | 124 | # Application definition 125 | THIRD_PARTY_APPS = [ 126 | "django.contrib.admin", 127 | "django.contrib.auth", 128 | "django.contrib.contenttypes", 129 | "django.contrib.sessions", 130 | "django.contrib.messages", 131 | "django.contrib.staticfiles", 132 | "django_extensions", 133 | {%- if cookiecutter.django_social == "enabled" %} 134 | {%- if cookiecutter.feature_annotations == "on" %} 135 | 136 | # START_FEATURE django_social 137 | {%- endif %} 138 | "social_django", 139 | {%- if cookiecutter.feature_annotations == "on" %} 140 | # END_FEATURE django_social 141 | {%- endif %} 142 | {%- endif %} 143 | {%- if cookiecutter.crispy_forms == "enabled" %} 144 | {%- if cookiecutter.feature_annotations == "on" %} 145 | 146 | # START_FEATURE crispy_forms 147 | {%- endif %} 148 | "crispy_forms", 149 | "crispy_bootstrap5", 150 | {%- if cookiecutter.feature_annotations == "on" %} 151 | # END_FEATURE crispy_forms 152 | {%- endif %} 153 | {%- endif %} 154 | {%- if cookiecutter.django_react == "enabled" %} 155 | {%- if cookiecutter.feature_annotations == "on" %} 156 | 157 | # START_FEATURE django_react 158 | {%- endif %} 159 | "django_react_components", 160 | "webpack_loader", 161 | {%- if cookiecutter.feature_annotations == "on" %} 162 | # END_FEATURE django_react 163 | {%- endif %} 164 | {%- endif %} 165 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 166 | {%- if cookiecutter.feature_annotations == "on" %} 167 | 168 | # START_FEATURE sass_bootstrap 169 | {%- endif %} 170 | "sass_processor", 171 | {%- if cookiecutter.feature_annotations == "on" %} 172 | # END_FEATURE sass_bootstrap 173 | {%- endif %} 174 | {%- endif %} 175 | ] 176 | {%- if cookiecutter.debug_toolbar == "enabled" %} 177 | 178 | {% if cookiecutter.feature_annotations == "on" %} 179 | # START_FEATURE debug_toolbar 180 | {%- endif %} 181 | if DEBUG_TOOLBAR: 182 | THIRD_PARTY_APPS += ["debug_toolbar"] 183 | {%- if cookiecutter.feature_annotations == "on" %} 184 | # END_FEATURE debug_toolbar 185 | {%- endif %} 186 | {%- endif %} 187 | 188 | LOCAL_APPS = [ 189 | "common", 190 | ] 191 | 192 | INSTALLED_APPS = THIRD_PARTY_APPS + LOCAL_APPS 193 | 194 | MIDDLEWARE = [ 195 | "django.middleware.security.SecurityMiddleware", 196 | "django.contrib.sessions.middleware.SessionMiddleware", 197 | "django.middleware.common.CommonMiddleware", 198 | "common.middleware.MaintenanceModeMiddleware", 199 | "django.middleware.csrf.CsrfViewMiddleware", 200 | "django.contrib.auth.middleware.AuthenticationMiddleware", 201 | "django.contrib.messages.middleware.MessageMiddleware", 202 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 203 | {%- if cookiecutter.user_action_tracking == "enabled" %} 204 | {%- if cookiecutter.feature_annotations == "on" %} 205 | # START_FEATURE user_action_tracking 206 | {%- endif %} 207 | "common.middleware.UserActionTrackingMiddleware", 208 | {%- if cookiecutter.feature_annotations == "on" %} 209 | # END_FEATURE user_action_tracking 210 | {%- endif %} 211 | {%- endif %} 212 | ] 213 | 214 | MAINTENANCE_MODE = env("MAINTENANCE_MODE") 215 | {%- if cookiecutter.user_action_tracking == "enabled" %} 216 | 217 | {% if cookiecutter.feature_annotations == "on" %} 218 | # START_FEATURE user_action_tracking 219 | {%- endif %} 220 | USER_TRACKING_EXEMPT_ROUTES = [] 221 | {%- if cookiecutter.feature_annotations == "on" %} 222 | # END_FEATURE user_action_tracking 223 | {%- endif %} 224 | {%- endif %} 225 | 226 | 227 | ROOT_URLCONF = "config.urls" 228 | 229 | TEMPLATES = [ 230 | { 231 | "BACKEND": "django.template.backends.django.DjangoTemplates", 232 | "DIRS": [], 233 | "APP_DIRS": True, 234 | "OPTIONS": { 235 | "context_processors": [ 236 | "django.template.context_processors.debug", 237 | "django.template.context_processors.request", 238 | "django.contrib.auth.context_processors.auth", 239 | "django.contrib.messages.context_processors.messages", 240 | "common.context_processors.django_settings", 241 | ], 242 | }, 243 | }, 244 | ] 245 | 246 | WSGI_APPLICATION = "config.wsgi.application" 247 | 248 | 249 | # Database 250 | # https://docs.djangoproject.com/en/2.2/ref/settings/#databases 251 | 252 | DATABASES = {"default": env.db()} 253 | 254 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 255 | 256 | # Password validation 257 | # https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators 258 | 259 | AUTH_PASSWORD_VALIDATORS = [ 260 | { 261 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 262 | }, 263 | { 264 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 265 | }, 266 | { 267 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 268 | }, 269 | { 270 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 271 | }, 272 | ] 273 | {%- if cookiecutter.django_ses == "enabled" %} 274 | 275 | {% if cookiecutter.feature_annotations == "on" %} 276 | # START_FEATURE django_ses 277 | {%- endif %} 278 | if LOCALHOST: 279 | EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 280 | DEFAULT_FROM_EMAIL = "webmaster@localhost" 281 | else: 282 | EMAIL_BACKEND = "django_ses.SESBackend" 283 | AWS_SES_REGION_NAME = env("AWS_SES_REGION_NAME") 284 | AWS_SES_REGION_ENDPOINT = env("AWS_SES_REGION_ENDPOINT") 285 | AWS_SES_RETURN_PATH = env("DEFAULT_FROM_EMAIL") 286 | DEFAULT_FROM_EMAIL = env("DEFAULT_FROM_EMAIL") 287 | {%- if cookiecutter.feature_annotations == "on" %} 288 | # END_FEATURE django_ses 289 | {%- endif %} 290 | {%- endif %} 291 | 292 | # Logging 293 | # https://docs.djangoproject.com/en/dev/topics/logging/#django-security 294 | LOGGING = { 295 | "version": 1, 296 | "disable_existing_loggers": False, 297 | "formatters": { 298 | "verbose": { 299 | "format": "%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s" 300 | }, 301 | "simple": { 302 | "format": "%(levelname)s %(message)s" 303 | }, 304 | }, 305 | "handlers": { 306 | "console": { 307 | "level": "WARNING", 308 | "class": "logging.StreamHandler", 309 | "formatter": "simple", 310 | }, 311 | "null": { 312 | "class": "logging.NullHandler", 313 | }, 314 | }, 315 | "loggers": { 316 | "django.request": { 317 | "handlers": ["console"] 318 | }, 319 | "django.security.DisallowedHost": { 320 | "handlers": ["null"], 321 | "propagate": False, 322 | }, 323 | }, 324 | } 325 | 326 | 327 | # Internationalization 328 | # https://docs.djangoproject.com/en/2.2/topics/i18n/ 329 | 330 | LANGUAGE_CODE = "en-us" 331 | 332 | TIME_ZONE = "UTC" 333 | 334 | USE_I18N = True 335 | 336 | USE_L10N = True 337 | 338 | USE_TZ = True 339 | 340 | 341 | # Static files (CSS, JavaScript, Images) 342 | # https://docs.djangoproject.com/en/2.2/howto/static-files/ 343 | 344 | STATIC_URL = "/static/" 345 | STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") 346 | STATICFILES_DIRS = [ 347 | os.path.join(BASE_DIR, "static"), 348 | ] 349 | 350 | AUTH_USER_MODEL = "common.User" 351 | {%- if cookiecutter.django_social == "enabled" %} 352 | 353 | {% if cookiecutter.feature_annotations == "on" %} 354 | # START_FEATURE django_social 355 | {%- endif %} 356 | AUTHENTICATION_BACKENDS = [ 357 | "social_core.backends.google.GoogleOAuth2", 358 | ] 359 | 360 | LOGIN_URL = "index" 361 | LOGIN_REDIRECT_URL = "index" 362 | 363 | SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = env("GOOGLE_OAUTH2_KEY") 364 | SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = env("GOOGLE_OAUTH2_SECRET") 365 | {%- if cookiecutter.feature_annotations == "on" %} 366 | # END_FEATURE django_social 367 | {%- endif %} 368 | {%- endif %} 369 | {%- if cookiecutter.crispy_forms == "enabled" %} 370 | 371 | {% if cookiecutter.feature_annotations == "on" %} 372 | # START_FEATURE crispy_forms 373 | {%- endif %} 374 | CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" 375 | CRISPY_TEMPLATE_PACK = "bootstrap5" 376 | {%- if cookiecutter.feature_annotations == "on" %} 377 | # END_FEATURE crispy_forms 378 | {%- endif %} 379 | {%- endif %} 380 | {%- if cookiecutter.bootstrap_messages == "enabled" %} 381 | 382 | {% if cookiecutter.feature_annotations == "on" %} 383 | # START_FEATURE bootstrap_messages 384 | {%- endif %} 385 | # Bootstrap styling for Django messages 386 | MESSAGE_TAGS = { 387 | messages.DEBUG: "alert-info", 388 | messages.INFO: "alert-info", 389 | messages.SUCCESS: "alert-success", 390 | messages.WARNING: "alert-warning", 391 | messages.ERROR: "alert-danger", 392 | } 393 | {%- if cookiecutter.feature_annotations == "on" %} 394 | # END_FEATURE bootstrap_messages 395 | {%- endif %} 396 | {%- endif %} 397 | 398 | {%- if cookiecutter.django_storages == "enabled" %} 399 | 400 | {% if cookiecutter.feature_annotations == "on" %} 401 | # START_FEATURE django_storages 402 | {%- endif %} 403 | if LOCALHOST is True: 404 | DEFAULT_STORAGE = {"BACKEND": "django.core.files.storage.FileSystemStorage"} 405 | MEDIA_ROOT = "" 406 | else: 407 | DEFAULT_STORAGE = { 408 | "BACKEND": "storages.backends.s3boto3.S3Boto3Storage", 409 | "OPTIONS": { 410 | "bucket_name": env("AWS_STORAGE_BUCKET_NAME"), 411 | "file_overwrite": False, 412 | "default_acl": "private", 413 | } 414 | } 415 | {%- if cookiecutter.feature_annotations == "on" %} 416 | # END_FEATURE django_storages 417 | {%- endif %} 418 | {%- else %} 419 | DEFAULT_STORAGE = {"BACKEND": "django.core.files.storage.FileSystemStorage"} 420 | {%- endif %} 421 | STORAGES = { 422 | "default": DEFAULT_STORAGE, 423 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 424 | {%- if cookiecutter.feature_annotations == "on" %} 425 | # START_FEATURE sass_bootstrap 426 | {%- endif %} 427 | "sass_processor": { 428 | "BACKEND": "django.core.files.storage.FileSystemStorage", 429 | "OPTIONS": { 430 | "base_url": STATIC_URL, 431 | "location": os.path.join(BASE_DIR, 'static'), 432 | }, 433 | "ROOT": os.path.join(BASE_DIR, 'static'), 434 | }, 435 | {%- if cookiecutter.feature_annotations == "on" %} 436 | # END_FEATURE sass_bootstrap 437 | {%- endif %} 438 | {%- endif %} 439 | "staticfiles": { 440 | "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage", 441 | }, 442 | } 443 | 444 | STATICFILES_FINDERS = [ 445 | 'django.contrib.staticfiles.finders.FileSystemFinder', 446 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 447 | 'sass_processor.finders.CssFinder', 448 | ] 449 | 450 | {%- if cookiecutter.debug_toolbar == "enabled" %} 451 | 452 | {% if cookiecutter.feature_annotations == "on" %} 453 | # START_FEATURE debug_toolbar 454 | {%- endif %} 455 | INTERNAL_IPS = ["127.0.0.1"] 456 | if DEBUG_TOOLBAR: 457 | MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware") 458 | {%- if cookiecutter.feature_annotations == "on" %} 459 | # END_FEATURE debug_toolbar 460 | {%- endif %} 461 | {%- endif %} 462 | {%- if cookiecutter.django_react == "enabled" %} 463 | 464 | {% if cookiecutter.feature_annotations == "on" %} 465 | # START_FEATURE django_react 466 | {%- endif %} 467 | if DEBUG: 468 | WEBPACK_LOADER_HOTLOAD = env("WEBPACK_LOADER_HOTLOAD") 469 | if WEBPACK_LOADER_HOTLOAD: 470 | WEBPACK_LOADER = { 471 | "DEFAULT": { 472 | "LOADER_CLASS": "config.webpack_loader.DynamicWebpackLoader" 473 | } 474 | } 475 | {%- if cookiecutter.feature_annotations == "on" %} 476 | # END_FEATURE django_react 477 | {%- endif %} 478 | {%- endif %} 479 | {%- if cookiecutter.sentry == "enabled" %} 480 | 481 | {% if cookiecutter.feature_annotations == "on" %} 482 | # START_FEATURE sentry 483 | {%- endif %} 484 | SENTRY_DSN = env("SENTRY_DSN") 485 | if LOCALHOST is False and SENTRY_DSN: 486 | import sentry_sdk 487 | from sentry_sdk.integrations.django import DjangoIntegration 488 | from sentry_sdk.integrations.logging import ignore_logger 489 | sentry_sdk.init( 490 | dsn=env("SENTRY_DSN"), 491 | integrations=[DjangoIntegration()], 492 | traces_sample_rate=1.0, 493 | ) 494 | # Silence "invalid HTTP_HOST" errors 495 | ignore_logger("django.security.DisallowedHost") 496 | 497 | {%- if cookiecutter.feature_annotations == "on" %} 498 | # END_FEATURE sentry 499 | {%- endif %} 500 | {%- endif %} 501 | {%- if cookiecutter.security_settings == "enabled" %} 502 | 503 | {% if cookiecutter.feature_annotations == "on" %} 504 | # START_FEATURE security_settings 505 | {%- endif %} 506 | if LOCALHOST is False: 507 | SECURE_SSL_REDIRECT = True 508 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") 509 | 510 | USE_X_FORWARDED_HOST = True 511 | X_FRAME_OPTIONS = "DENY" 512 | SECURE_REFERRER_POLICY = "same-origin" 513 | SECURE_BROWSER_XSS_FILTER = True 514 | SECURE_CONTENT_TYPE_NOSNIFF = True 515 | 516 | SECURE_HSTS_SECONDS = 3600 # 1 hour 517 | # TODO: increase SECURE_HSTS_SECONDS and register with hstspreload.org once production deploy is stable 518 | # SECURE_HSTS_SECONDS = 3600 * 24 * 365 * 2 # 2 years 519 | SECURE_HSTS_INCLUDE_SUBDOMAINS = True 520 | SECURE_HSTS_PRELOAD = True 521 | 522 | SESSION_COOKIE_SECURE = True 523 | SESSION_COOKIE_HTTPONLY = True 524 | SESSION_COOKIE_AGE = 60 * 60 * 3 # 3 hours 525 | CSRF_COOKIE_SECURE = True 526 | CSRF_COOKIE_HTTPONLY = True # Only do this if you are not accessing the CSRF cookie with JS 527 | {%- if cookiecutter.feature_annotations == "on" %} 528 | # END_FEATURE security_settings 529 | {%- endif %} 530 | {%- endif %} 531 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 532 | 533 | {% if cookiecutter.feature_annotations == "on" %} 534 | # START_FEATURE sass_bootstrap 535 | {%- endif %} 536 | SASS_PRECISION = 8 # Bootstrap's sass requires a precision of at least 8 to prevent layout errors 537 | SASS_PROCESSOR_CUSTOM_FUNCTIONS = { 538 | 'django-static': 'django.templatetags.static.static', 539 | } 540 | SASS_PROCESSOR_INCLUDE_DIRS = [ 541 | os.path.join(BASE_DIR, 'static/styles'), 542 | os.path.join(BASE_DIR, 'node_modules'), 543 | ] 544 | COMPRESS_ROOT = STORAGES["sass_processor"]["ROOT"] 545 | {%- if cookiecutter.feature_annotations == "on" %} 546 | # END_FEATURE sass_bootstrap 547 | {%- endif %} 548 | {%- endif %} 549 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = migrations,env 4 | 5 | [isort] 6 | default_section = THIRDPARTY 7 | known_first_party = core 8 | known_django = django 9 | sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 10 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/urls.py: -------------------------------------------------------------------------------- 1 | """config URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.2/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.conf.urls import include 17 | from django.contrib import admin 18 | from django.http import HttpResponse 19 | from django.urls import path 20 | 21 | urlpatterns = [ 22 | path('admin/', admin.site.urls), 23 | {%- if cookiecutter.django_social == "enabled" %} 24 | path("oauth/", include("social_django.urls", namespace="social")), 25 | {%- endif %} 26 | path('', include("common.urls")), 27 | path("health-check/", lambda request: HttpResponse("ok")), 28 | ] 29 | 30 | handler404 = "common.views.error_404" 31 | handler500 = "common.views.error_500" 32 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/webpack_loader.py: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.django_react == "enabled" -%} 2 | {%- if cookiecutter.feature_annotations == "on" -%} 3 | # START_FEATURE django_react 4 | {%- endif %} 5 | 6 | from django.conf import settings 7 | 8 | from webpack_loader.loader import WebpackLoader 9 | 10 | 11 | class DynamicWebpackLoader(WebpackLoader): 12 | """ 13 | Custom django-webpack-loader loader to allow for hotloading in development. 14 | """ 15 | 16 | def get_chunk_url(self, chunk): 17 | path = super().get_chunk_url(chunk) 18 | if settings.WEBPACK_LOADER_HOTLOAD: 19 | path = f"http://localhost:3000{path}" 20 | return path 21 | {%- if cookiecutter.feature_annotations == "on" %} 22 | 23 | # END_FEATURE django_react 24 | {%- endif %} 25 | {%- endif %} 26 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 9 | try: 10 | from django.core.management import execute_from_command_line 11 | except ImportError as exc: 12 | raise ImportError( 13 | "Couldn't import Django. Are you sure it's installed and " 14 | "available on your PYTHONPATH environment variable? Did you " 15 | "forget to activate a virtual environment?" 16 | ) from exc 17 | execute_from_command_line(sys.argv) 18 | 19 | 20 | if __name__ == '__main__': 21 | main() 22 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/nwb.config.js: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.django_react == "enabled" -%} 2 | {%- if cookiecutter.feature_annotations == "on" -%} 3 | /* 4 | * START_FEATURE django_react 5 | */ 6 | 7 | 8 | {% endif -%} 9 | const path = require('path') 10 | const BundleTracker = require('webpack-bundle-tracker') 11 | 12 | 13 | /** 14 | * Entry point configuration. 15 | * 16 | * This is where you should configure your webpack entry points, for example, a different entry point per page. 17 | */ 18 | 19 | const ENTRIES = { 20 | {%- if cookiecutter.reference_examples == "on" %} 21 | // TODO delete me; this is just a reference example 22 | Home: './src/Pages/Home.js', 23 | Hello: './src/Components/Hello.js' 24 | {%- endif %} 25 | } 26 | 27 | const SHARED_ENTRIES = [ 28 | // we have not included react-app-polyfill in this sample application because it is not technically required for 29 | // projects which do not require polyfill. However, we do recommend installing and uncommenting this line of code 30 | // for projects which will need to have guaranteed functionality on older browsers. 31 | // './node_modules/react-app-polyfill/ie11.js', 32 | ] 33 | 34 | /** 35 | * nwb config 36 | */ 37 | module.exports = function({command}) { 38 | 39 | /* Set config */ 40 | const config = { 41 | type: 'react-app', 42 | } 43 | config.webpack = { 44 | config(webpackConfig) { 45 | 46 | // Set new entry configuration 47 | webpackConfig.entry = {} 48 | Object.keys(ENTRIES).forEach((entryKey) => { 49 | webpackConfig.entry[entryKey] = [...SHARED_ENTRIES, ENTRIES[entryKey]] 50 | }) 51 | return webpackConfig 52 | }, 53 | extra: { 54 | output: { 55 | filename: '[name].js', 56 | chunkFilename: '[name].js', 57 | path: path.resolve('./static/webpack_bundles/'), 58 | }, 59 | module: { 60 | rules: [ 61 | { 62 | test: /\.js$/, 63 | exclude: /node_modules/, 64 | loader: 'django-react-loader', 65 | }, 66 | ], 67 | }, 68 | plugins: [ 69 | new BundleTracker({filename: './webpack-stats.json'}), 70 | ], 71 | }, 72 | publicPath: '/static/webpack_bundles/', 73 | } 74 | return config 75 | } 76 | {% if cookiecutter.feature_annotations == "on" %} 77 | /* 78 | * END_FEATURE django_react 79 | */ 80 | {%- endif %} 81 | {%- endif %} 82 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-django-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | {% if cookiecutter.sass_bootstrap == "enabled" -%} 7 | "bootstrap": "^5.1.1"{% if cookiecutter.django_react == "enabled" %},{% endif %} 8 | {%- endif %} 9 | {% if cookiecutter.django_react == "enabled" -%} 10 | "django-react-loader": "^0.1.7", 11 | "nwb": "^0.24.7", 12 | "react": "^16.13.1", 13 | "react-dom": "^16.13.1", 14 | "webpack-bundle-tracker": "^0.4.3", 15 | "@babel/plugin-transform-react-jsx": "~7.16.7" 16 | {%- endif %} 17 | }, 18 | {% if cookiecutter.django_react == "enabled" -%} 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | {%- endif -%} 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/readme.md: -------------------------------------------------------------------------------- 1 | # Project Template and Optional Features 2 | 3 | This project was created using https://github.com/zagaran/django-template 4 | 5 | See the readme on [django-template](https://github.com/zagaran/django-template) for: 6 | * Instructions on starting your own project 7 | * An explanation of included features. 8 | 9 | # Local Project Setup 10 | 11 | Set up a Python virtual environment: https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment 12 | 13 | ```bash 14 | # Create environment config file. 15 | cp config/.env.example config/.env 16 | 17 | # Fill in appropriate environment values. 18 | nano config/.env 19 | 20 | # Install pip requirements. 21 | pip install --upgrade pip 22 | pip install -r requirements-dev.txt 23 | 24 | # Apply migrations and sync database schema. 25 | python manage.py migrate 26 | {%- if cookiecutter.django_react == "enabled" or cookiecutter.sass_bootstrap == "enabled" %} 27 | # Install Node dependencies 28 | npm install 29 | {%- endif %} 30 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 31 | 32 | {% if cookiecutter.feature_annotations == "on" %} 33 | # START_FEATURE sass_bootstrap 34 | {%- endif %} 35 | # Complie SCSS files 36 | python manage.py compilescss 37 | {%- if cookiecutter.feature_annotations == "on" %} 38 | # END_FEATURE sass_bootstrap 39 | {%- endif %} 40 | {%- endif %} 41 | ``` 42 | 43 | To run the project: 44 | ```bash 45 | python manage.py runserver_plus 46 | ``` 47 | 48 | {%- if cookiecutter.django_react == "enabled" %} 49 | 50 | To run the frontend with hotloading React assets: 51 | 1. Set `WEBPACK_LOADER_HOTLOAD=True` in `config/.env` 52 | 2. Run the following (in addition to `manage.py runserver_plus`): 53 | ```bash 54 | node_modules/nwb/lib/bin/nwb.js serve 55 | ``` 56 | 57 | To make a static build of the frontend (such as when doing development on 58 | non-React parts of the codebase): 59 | 1. Set `WEBPACK_LOADER_HOTLOAD=False` in `config/.env` 60 | 2. Run the following: 61 | ```bash 62 | node_modules/nwb/lib/bin/nwb.js build --no-vendor 63 | ``` 64 | {%- endif %} 65 | 66 | To access the database: 67 | ```bash 68 | python manage.py shell_plus 69 | ``` 70 | 71 | To run the test suite: 72 | ```bash 73 | python manage.py test 74 | ``` 75 | 76 | To get a test coverage report: 77 | ```bash 78 | coverage run --source='.' manage.py test; coverage report 79 | ``` 80 | 81 | # Requirements 82 | 83 | The project uses [pip-tools](https://github.com/jazzband/pip-tools) to manage requirements. In addition, it has two requirements files: 84 | 85 | * `requirements.txt`: this is for requirements that should be installed on servers. 86 | * `requirements-dev.txt`: this is a superset of requirements.txt that should be used only for local development. This includes tools that are not needed on server deployments of the codebase and thus omitted in `requirements.txt` to reduce extraneous server installs. 87 | 88 | To add a new dependency to or update requirements, add the entry to requirements.in (if it's needed for the codebase to run) or requirements-dev.in (if it's just needed for development) and run `pip-compile` to generate new .txt files: 89 | ```bash 90 | nano requirements.in # Updating Python dependencies as needed 91 | nano requirements-dev.in # Updating Python dev dependencies as needed 92 | pip-compile requirements.in --upgrade # Generate requirements.txt with updated dependencies 93 | pip-compile requirements-dev.in --upgrade # Generate requirements-dev.txt with updated dependencies 94 | ``` 95 | 96 | # Settings 97 | 98 | This project uses [django-environ](https://django-environ.readthedocs.io/en/latest/) 99 | to read configuration from either `config/.env` (for local development) 100 | or from environment varables (for server deployments). For a list of settings, 101 | see the `environ.Env(` object in [config/settings.py](config/settings.py). 102 | 103 | 104 | {%- if cookiecutter.elastic_beanstalk == "enabled" %} 105 | # Elastic Beanstalk 106 | 107 | The following Python packages are useful tools for interacting with AWS and Elastic Beanstalk. 108 | Due to dependency conflicts, these should not be installed in your project's regular virtual environment, 109 | and should instead either be installed globally or in a separate virtual environment that runs them: 110 | 111 | ```bash 112 | pip install awscli # For interacting with general AWS services (note, this package often has conflicts with its botocore dependency) 113 | pip install awsebcli # For interacting with Elastic Beanstalk (note, this package often has conflicts with its botocore dependency) 114 | pip install eb-create-environment # For automating the creation of Elastic Beanstalk applications 115 | pip install eb-ssm # For Elastic Beanstalk SSH functionality without requiring shared private keys 116 | ``` 117 | 118 | ## Creating a new environment 119 | 120 | To do create a new Elastic Beanstalk environment, modify the contents of [.elasticbeanstalk/eb_create_environment.yml]([.elasticbeanstalk/eb_create_environment.yml]) and run `eb-create-environment -c .elasticbeanstalk/eb_create_environment.yml`. 121 | 122 | See the docs for [eb-create-environment](https://github.com/zagaran/eb-create-environment/) for more details. 123 | 124 | Then, add the following environment variables: 125 | ``` 126 | ALLOWED_HOSTS 127 | SECRET_KEY 128 | {%- if cookiecutter.django_storages == "enabled" %} 129 | AWS_STORAGE_BUCKET_NAME 130 | {%- endif %} 131 | {%- if cookiecutter.django_social == "enabled" %} 132 | GOOGLE_OAUTH2_KEY 133 | GOOGLE_OAUTH2_SECRET 134 | {%- endif %} 135 | {%- if cookiecutter.sentry == "enabled" %} 136 | SENTRY_DSN 137 | {%- endif %} 138 | {%- if cookiecutter.django_ses == "enabled" %} 139 | DEFAULT_FROM_EMAIL 140 | {%- endif %} 141 | ``` 142 | 143 | Following that, deploy your code to the environment (see below). 144 | 145 | ## Deploying code 146 | 147 | To deploy new versions of your code to your environment, run `eb deploy ` using the EB CLI to deploy your code to that environment. 148 | 149 | See the [eb-cli](https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/eb-cli3.html) docs on general command line usage of Elastic Beanstalk. 150 | 151 | ## SSH 152 | 153 | To SSH into an Elastic Beanstalk Environment, use [eb-ssm](https://github.com/zagaran/eb-ssm). 154 | {%- endif %} -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements-dev.in: -------------------------------------------------------------------------------- 1 | -r requirements.in 2 | -c requirements.txt 3 | 4 | werkzeug 5 | pip-tools 6 | coverage 7 | 8 | ####### OPTIONAL FEATURES ####### 9 | {%- if cookiecutter.elastic_beanstalk == "enabled" %} 10 | {%- if cookiecutter.feature_annotations == "on" %} 11 | # START_FEATURE elastic_beanstalk 12 | {%- endif %} 13 | {%- if cookiecutter.feature_annotations == "on" %} 14 | # END_FEATURE elastic_beanstalk 15 | {%- endif %} 16 | {%- endif %} 17 | {%- if cookiecutter.pre_commit == "enabled" %} 18 | {%- if cookiecutter.feature_annotations == "on" %} 19 | 20 | # START_FEATURE pre_commit 21 | {%- endif %} 22 | pre-commit 23 | flake8 24 | isort 25 | {%- if cookiecutter.feature_annotations == "on" %} 26 | # END_FEATURE pre_commit 27 | {%- endif %} 28 | {%- endif %} 29 | {%- if cookiecutter.debug_toolbar == "enabled" %} 30 | {%- if cookiecutter.feature_annotations == "on" %} 31 | 32 | # START_FEATURE debug_toolbar 33 | {%- endif %} 34 | django-debug-toolbar 35 | {%- if cookiecutter.feature_annotations == "on" %} 36 | # END_FEATURE debug_toolbar 37 | {%- endif %} 38 | {%- endif %} 39 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/requirements.in: -------------------------------------------------------------------------------- 1 | Django==4.2.* # Latest LTS version 2 | django-environ 3 | django-extensions>=3.2 4 | requests 5 | psycopg2 6 | {%- if cookiecutter.feature_annotations == "on" %} 7 | 8 | ####### OPTIONAL FEATURES ####### 9 | {%- endif %} 10 | {%- if cookiecutter.elastic_beanstalk == "enabled" %} 11 | {%- if cookiecutter.feature_annotations == "on" %} 12 | # START_FEATURE elastic_beanstalk 13 | {%- endif %} 14 | ec2_metadata 15 | {%- if cookiecutter.feature_annotations == "on" %} 16 | # END_FEATURE elastic_beanstalk 17 | {%- endif %} 18 | {%- endif %} 19 | {%- if cookiecutter.django_social == "enabled" %} 20 | {%- if cookiecutter.feature_annotations == "on" %} 21 | 22 | # START_FEATURE django_social 23 | {%- endif %} 24 | social-auth-app-django 25 | {%- if cookiecutter.feature_annotations == "on" %} 26 | # END_FEATURE django_social 27 | {%- endif %} 28 | {%- endif %} 29 | {%- if cookiecutter.crispy_forms == "enabled" %} 30 | {%- if cookiecutter.feature_annotations == "on" %} 31 | 32 | # START_FEATURE crispy_forms 33 | {%- endif %} 34 | django-crispy-forms 35 | crispy-bootstrap5 36 | {%- if cookiecutter.feature_annotations == "on" %} 37 | # END_FEATURE crispy_forms 38 | {%- endif %} 39 | {%- endif %} 40 | {%- if cookiecutter.django_react == "enabled" %} 41 | {%- if cookiecutter.feature_annotations == "on" %} 42 | 43 | # START_FEATURE django_react 44 | {%- endif %} 45 | django-react-components 46 | django-webpack-loader==0.7.0 47 | {%- if cookiecutter.feature_annotations == "on" %} 48 | # END_FEATURE django_react 49 | {%- endif %} 50 | {%- endif %} 51 | {%- if cookiecutter.sentry == "enabled" %} 52 | {%- if cookiecutter.feature_annotations == "on" %} 53 | 54 | # START_FEATURE sentry 55 | {%- endif %} 56 | sentry-sdk 57 | {%- if cookiecutter.feature_annotations == "on" %} 58 | # END_FEATURE sentry 59 | {%- endif %} 60 | {%- endif %} 61 | {%- if cookiecutter.django_storages == "enabled" %} 62 | {%- if cookiecutter.feature_annotations == "on" %} 63 | 64 | # START_FEATURE django_storages 65 | {%- endif %} 66 | django-storages 67 | {%- if cookiecutter.feature_annotations == "on" %} 68 | # END_FEATURE django_storages 69 | {%- endif %} 70 | {%- endif %} 71 | {%- if cookiecutter.docker == "enabled" %} 72 | {%- if cookiecutter.feature_annotations == "on" %} 73 | 74 | # START_FEATURE docker 75 | {%- endif %} 76 | gunicorn 77 | {%- if cookiecutter.feature_annotations == "on" %} 78 | # END_FEATURE docker 79 | {%- endif %} 80 | {%- endif %} 81 | {%- if cookiecutter.sentry == "enabled" %} 82 | {%- if cookiecutter.feature_annotations == "on" %} 83 | 84 | {%- endif %} 85 | {%- endif %} 86 | {%- if cookiecutter.django_ses == "enabled" %} 87 | {%- if cookiecutter.feature_annotations == "on" %} 88 | 89 | # START_FEATURE django_ses 90 | {%- endif %} 91 | django-ses 92 | {%- if cookiecutter.feature_annotations == "on" %} 93 | # END_FEATURE django_ses 94 | {%- endif %} 95 | {%- endif %} 96 | 97 | {%- if cookiecutter.sass_bootstrap == "enabled" %} 98 | {%- if cookiecutter.feature_annotations == "on" %} 99 | 100 | # START_FEATURE sass_bootstrap 101 | {%- endif %} 102 | django-compressor 103 | django-sass-processor 104 | libsass 105 | {%- if cookiecutter.feature_annotations == "on" %} 106 | # END_FEATURE sass_bootstrap 107 | {%- endif %} 108 | {%- endif %} 109 | {%- if cookiecutter.feature_annotations == "on" %} 110 | 111 | ################################ 112 | {%- endif %} 113 | 114 | # Convenience 115 | ipython 116 | {%- if cookiecutter.debug_toolbar == "on" %} 117 | django-debug-toolbar 118 | {%- endif %} 119 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/src/Components/Hello.js: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.reference_examples == "on" -%} 2 | /* 3 | * # TODO: delete me; this is just a reference example 4 | */ 5 | 6 | import React from 'react'; 7 | 8 | function Hello(props) { 9 | return ( 10 |
11 |

Hello {props.msg}

12 |
13 | ); 14 | } 15 | 16 | export default Hello; 17 | {%- endif %} 18 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/src/Pages/Home.js: -------------------------------------------------------------------------------- 1 | {%- if cookiecutter.reference_examples == "on" -%} 2 | /* 3 | * # TODO: delete me; this is just a reference example 4 | */ 5 | 6 | import React from 'react'; 7 | import Hello from "../Components/Hello"; 8 | 9 | function Home(props) { 10 | const {message} = props; 11 | return ( 12 |
13 |

You may want to wrap several components in a page, but you don't have to.

14 | 15 |
16 | ); 17 | } 18 | 19 | export default Home; 20 | {%- endif %} 21 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zagaran/django-template/92fb23aae04d6bbdcbc308152982c083395ca480/{{cookiecutter.project_slug}}/static/favicon.png -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/static/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Sass variables 3 | */ 4 | 5 | /* Override Bootstrap default variables */ 6 | {%- if cookiecutter.reference_examples == "on" %} 7 | 8 | // TODO: delete me; this is just a reference example 9 | $primary: #2123a7; 10 | {%- endif %} 11 | 12 | @import 'bootstrap/scss/_functions.scss'; 13 | @import 'bootstrap/scss/_variables.scss'; 14 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/static/styles/base.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Base app styling 3 | */ 4 | @import '_variables.scss'; 5 | @import 'bootstrap/scss/bootstrap.scss'; 6 | -------------------------------------------------------------------------------- /{{cookiecutter.project_slug}}/static/styles/pages/index.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Base app index 3 | */ 4 | {% if cookiecutter.reference_examples == "on" -%} 5 | // TODO: delete me; this is just a reference example 6 | .does-nothing-button { 7 | &:focus { 8 | -webkit-animation: rainbow 2s ease-in-out infinite; 9 | animation: rainbow 2s ease-in-out infinite; 10 | background: linear-gradient(45deg, red, orange, yellow, green, blue, indigo, violet); 11 | background-size: 1000% 1000%; 12 | 13 | &:after { 14 | content: "... just kidding!"; 15 | } 16 | } 17 | 18 | @keyframes rainbow { 19 | 0% { 20 | background-position: 0% 80%; 21 | } 22 | 25% { 23 | background-position: 50% 50%; 24 | } 25 | 50% { 26 | background-position: 100% 20%; 27 | } 28 | 75% { 29 | background-position: 50% 50%; 30 | } 31 | 100% { 32 | background-position: 0% 80%; 33 | } 34 | } 35 | } 36 | {%- endif %} 37 | --------------------------------------------------------------------------------