diff --git a/CHANGELOG.md b/CHANGELOG.md index 27857476..27373cdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. ## [2.x] +- Add black in pre-commit and also formatted exiting code as per black. ([@theskumar]) - Upgrade to run on Python 3.7 ([@theskumar]) - Update celery concurrency to default to number of CPUs ([@theskumar]) - Upgrade to Postgres 11 and Postgis 2.5. ([@CuriousLearner]) diff --git a/README.md b/README.md index 84b6a39e..6c3c74e9 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,11 @@ Project template for django based projects, optimized for making REST API with d - Django 2.2.x - Python 3.7.x +- Support for [black](https://pypi.org/project/black/)! - [12-Factor][12factor] based settings management via [django-environ], reads settings from `.env` if present. - Supports PostreSQL 11.0 (support of postgis-2.5 is available). -- Ready to deploy on Heroku (optional) and Ubuntu 16 LTS via [Ansible](Optional) -- [Django Rest Framework][drf] 3.9.x. ready. +- Ready to deploy on Heroku (optional) and Ubuntu 18 LTS via [Ansible](Optional) +- [Django Rest Framework][drf] 3.9.x. - Uses `django_sites` instead of `django.contrib.sites` - Uses [mkdocs] for project documentation. Optionally, password protect the docs when deployed. - Uses [pytest] as test runner. diff --git a/{{cookiecutter.github_repository}}/.editorconfig b/{{cookiecutter.github_repository}}/.editorconfig index 3e90ae14..95d5bd31 100644 --- a/{{cookiecutter.github_repository}}/.editorconfig +++ b/{{cookiecutter.github_repository}}/.editorconfig @@ -14,14 +14,15 @@ indent_size = 4 [*.py] # https://github.com/timothycrosley/isort/wiki/isort-Settings -line_length=120 +line_length=88 known_first_party={{ cookiecutter.main_module }} multi_line_output=3 +include_trailing_comma=True default_section=THIRDPARTY import_heading_stdlib=Standard Library import_heading_firstparty={{ cookiecutter.project_name }} Stuff import_heading_thirdparty=Third Party Stuff -skip_glob=*/migrations/* +skip_glob=*/migrations/**,*/venv/**,*/docs/** [*.{html,css,scss,json,yml}] indent_style = space diff --git a/{{cookiecutter.github_repository}}/.pre-commit-config.yaml b/{{cookiecutter.github_repository}}/.pre-commit-config.yaml index d682027f..0065fd00 100644 --- a/{{cookiecutter.github_repository}}/.pre-commit-config.yaml +++ b/{{cookiecutter.github_repository}}/.pre-commit-config.yaml @@ -2,7 +2,6 @@ repos: - repo: git://github.com/pre-commit/pre-commit-hooks rev: v1.4.0 hooks: - - id: flake8 - id: end-of-file-fixer - id: trailing-whitespace - id: check-case-conflict @@ -14,8 +13,7 @@ repos: - id: check-json - id: pretty-format-json - id: check-added-large-files - - id: double-quote-string-fixer - - id: fix-encoding-pragma + - id: flake8 - repo: git://github.com/CuriousLearner/pre-commit-python-sorter sha: 5294cde9d51cff119af537e987c30c320e9fbe2f diff --git a/{{cookiecutter.github_repository}}/CONTRIBUTING.md b/{{cookiecutter.github_repository}}/CONTRIBUTING.md index 535e74b8..7079c1a7 100644 --- a/{{cookiecutter.github_repository}}/CONTRIBUTING.md +++ b/{{cookiecutter.github_repository}}/CONTRIBUTING.md @@ -19,7 +19,7 @@ Before you submit a pull request, check that it meets these guidelines: ## Coding conventions - Read and pay attention to current code in the repository -- For the Python part, we follow pep8 in most cases. We use [flake8][flake8] to check for linting errors. Once you're ready to commit changes, check your code with `flake8`. +- For the Python part, we follow [black](https://pypi.org/project/black/) for formating code. We use modified configuration of [flake8][flake8] to check for linting errors that complies formating standards of `black`. Once you're ready to commit changes, format your code with `black` and check your code with `flake8`. Optionally, setup `pre-commit` with `pre-install --install` to do it automatically before commit. - Install a plugin for [EditorConfig][editorconfig] and let it handle some of the formating issues for you. - For the Django part, we follow standard [Django coding style][django-coding style]. - And always remember the Zen. diff --git a/{{cookiecutter.github_repository}}/fabfile.py b/{{cookiecutter.github_repository}}/fabfile.py index 08e7d1a9..1ce0ed74 100644 --- a/{{cookiecutter.github_repository}}/fabfile.py +++ b/{{cookiecutter.github_repository}}/fabfile.py @@ -22,20 +22,16 @@ # ========================================================================== # Settings # ========================================================================== -env.project_name = '{{ cookiecutter.main_module }}' +env.project_name = "{{ cookiecutter.main_module }}" env.apps_dir = join(HERE, env.project_name) -env.docs_dir = join(HERE, 'docs') -env.static_dir = join(env.apps_dir, 'static') -env.virtualenv_dir = join(HERE, 'venv') -env.requirements_file = join(HERE, 'requirements/development.txt') +env.docs_dir = join(HERE, "docs") +env.static_dir = join(env.apps_dir, "static") +env.virtualenv_dir = join(HERE, "venv") +env.requirements_file = join(HERE, "requirements/development.txt") env.shell = "/bin/bash -l -i -c" -{%- if cookiecutter.webpack.lower() == 'y' %} -env.webpack_config_path = join(env.static_dir, 'webpack.config.js') -env.webpack_server_path = join(env.static_dir, 'server.js') -{%- endif %} {% if cookiecutter.add_ansible.lower() == 'y' -%}env.use_ssh_config = True -env.dotenv_path = join(HERE, '.env') +env.dotenv_path = join(HERE, ".env") env.config_setter = local{% endif %} @@ -44,15 +40,15 @@ def init(vagrant=False): install_requirements() {%- if cookiecutter.webpack.lower() == 'y' %} - local('npm install') - local('npm run build') + local("npm install") + local("npm run build") {%- endif %} {%- if cookiecutter.add_pre_commit.lower() == 'y' %} add_pre_commit() {%- endif %} - if not os.getenv('CI', 'False').lower() == 'true': - local('createdb %(project_name)s' % env) # create postgres database - manage('migrate') + if not os.getenv("CI", "False").lower() == "true": + local("createdb %(project_name)s" % env) # create postgres database + manage("migrate") def install_requirements(file=env.requirements_file): @@ -60,45 +56,45 @@ def install_requirements(file=env.requirements_file): verify_virtualenv() # activate virtualenv and install with virtualenv(): - local('pip install -r %s' % file) -{%- if cookiecutter.add_pre_commit.lower() == 'y' %} + local("pip install -r %s" % file) +{%- if cookiecutter.add_pre_commit.lower() == "y" %} def add_pre_commit(): verify_virtualenv() # activate virtualenv and install pre-commit hooks with virtualenv(): - local('pre-commit install') + local("pre-commit install") {%- endif %} def serve_docs(options=''): """Start a local server to view documentation changes.""" with fab.lcd(HERE) and virtualenv(): - local('mkdocs serve {}'.format(options)) + local("mkdocs serve {}".format(options)) def shell(): - manage('shell_plus') + manage("shell_plus") -def test(options='--pdb --cov'): +def test(options="--pdb --cov"): """Run tests locally. By Default, it runs the test using --ipdb. You can skip running it using --ipdb by running - `fab test:""` """ with virtualenv(): - local('flake8 .') - local('pytest %s' % options) + local("flake8 .") + local("pytest %s" % options) -def serve(host='127.0.0.1:8000'): +def serve(host="127.0.0.1:8000"): """Run local developerment server, making sure that dependencies and database migrations are upto date. """ install_requirements() migrate() - manage('runserver %s' % host) -{%- if cookiecutter.add_celery.lower() == 'y' %} + manage("runserver %s" % host) +{%- if cookiecutter.add_celery.lower() == "y" %} def celery(): @@ -107,31 +103,31 @@ def celery(): """ install_requirements() migrate() - local('celery worker -A {{ cookiecutter.main_module }} -B -l INFO --concurrency=2') + local("celery worker -A {{ cookiecutter.main_module }} -B -l INFO") {%- endif %} -def makemigrations(app=''): +def makemigrations(app=""): """Create new database migration for an app.""" - manage('makemigrations %s' % app) + manage("makemigrations %s" % app) def migrate(): """Apply database migrations.""" - manage('migrate') + manage("migrate") def createapp(appname): """fab createapp """ path = join(env.apps_dir, appname) - local('mkdir %s' % path) - manage('startapp %s %s' % (appname, path)) + local("mkdir %s" % path) + manage("startapp %s %s" % (appname, path)) {%- if cookiecutter.webpack.lower() == 'y' %} def watch(): - local('node %s' % env.webpack_server_path) + local("node %s" % env.webpack_server_path) {%- endif %} @@ -139,29 +135,29 @@ def watch(): # Enviroments & Deployments # --------------------------------------------------------- def dev(): - env.host_group = 'dev' - env.remote = 'origin' - env.branch = 'master' - env.hosts = ['dev.{{ cookiecutter.main_module }}.com'] - env.dotenv_path = '/home/ubuntu/dev/{{ cookiecutter.main_module }}/.env' + env.host_group = "dev" + env.remote = "origin" + env.branch = "master" + env.hosts = ["dev.{{ cookiecutter.main_module }}.com"] + env.dotenv_path = "/home/ubuntu/dev/{{ cookiecutter.main_module }}/.env" env.config_setter = fab.run def qa(): - env.host_group = 'qa' - env.remote = 'origin' - env.branch = 'qa' - env.hosts = ['qa.{{ cookiecutter.main_module }}.com'] - env.dotenv_path = '/home/ubuntu/qa/{{ cookiecutter.main_module }}/.env' + env.host_group = "qa" + env.remote = "origin" + env.branch = "qa" + env.hosts = ["qa.{{ cookiecutter.main_module }}.com"] + env.dotenv_path = "/home/ubuntu/qa/{{ cookiecutter.main_module }}/.env" env.config_setter = fab.run def prod(): - env.host_group = 'production' - env.remote = 'origin' - env.branch = 'prod' - env.hosts = ['prod.{{ cookiecutter.main_module }}.com'] - env.dotenv_path = '/home/ubuntu/prod/{{ cookiecutter.main_module }}/.env' + env.host_group = "production" + env.remote = "origin" + env.branch = "prod" + env.hosts = ["prod.{{ cookiecutter.main_module }}.com"] + env.dotenv_path = "/home/ubuntu/prod/{{ cookiecutter.main_module }}/.env" env.config_setter = fab.run @@ -174,8 +170,9 @@ def config(action=None, key=None, value=None): fab [prod] config:list """ import dotenv + command = dotenv.get_cli_string(env.dotenv_path, action, key, value) - env.config_setter('touch %(dotenv_path)s' % env) + env.config_setter("touch %(dotenv_path)s" % env) if env.config_setter == local: with virtualenv(): @@ -186,19 +183,19 @@ def config(action=None, key=None, value=None): def restart_servers(): - services = ['uwsgi-emperor.service', ] + services = ["uwsgi-emperor.service"] for service in services: - fab.sudo('systemctl restart {0}'.format(service)) + fab.sudo("systemctl restart {0}".format(service)) -def configure(tags='', skip_tags='deploy'): +def configure(tags="", skip_tags="deploy"): """Setup a host using ansible scripts Usages: fab [prod|qa|dev] configure """ - fab.require('host_group') - cmd = 'ansible-playbook -i hosts site.yml --limit=%(host_group)s' % env - with fab.lcd('provisioner'): + fab.require("host_group") + cmd = "ansible-playbook -i hosts site.yml --limit=%(host_group)s" % env + with fab.lcd("provisioner"): if tags: cmd += " --tags '%s'" % tags if skip_tags: @@ -207,28 +204,28 @@ def configure(tags='', skip_tags='deploy'): def deploy(): - configure(tags='deploy', skip_tags='') + configure(tags="deploy", skip_tags="") def deploy_docs(): """Deploy documentation to server via ansible. """ - configure(tags='documentation', skip_tags=''){% else %} + configure(tags="documentation", skip_tags=""){% else %} def deploy_docs(): """Deploy documentation to github pages. """ with fab.lcd(HERE) and virtualenv(): - local('mkdocs gh-deploy') - local('rm -rf _docs_html'){% endif %} + local("mkdocs gh-deploy") + local("rm -rf _docs_html"){% endif %} # Helpers # --------------------------------------------------------- def manage(cmd, venv=True): with virtualenv(): - local('python manage.py %s' % cmd) + local("python manage.py %s" % cmd) @_contextmanager @@ -236,7 +233,7 @@ def virtualenv(): """Activates virtualenv context for other commands to run inside it. """ with fab.cd(HERE): - with fab.prefix('source %(virtualenv_dir)s/bin/activate' % env): + with fab.prefix("source %(virtualenv_dir)s/bin/activate" % env): yield @@ -245,8 +242,9 @@ def verify_virtualenv(): It also creates local virtualenv directory if it's not present """ from distutils import spawn - if not spawn.find_executable('virtualenv'): - local('sudo pip install virtualenv') + + if not spawn.find_executable("virtualenv"): + local("sudo pip install virtualenv") if not isdir(env.virtualenv_dir): - local('virtualenv %(virtualenv_dir)s -p $(which python3)' % env) + local("virtualenv %(virtualenv_dir)s -p $(which python3)" % env) diff --git a/{{cookiecutter.github_repository}}/manage.py b/{{cookiecutter.github_repository}}/manage.py index 77f4bc0c..fbb8ee02 100755 --- a/{{cookiecutter.github_repository}}/manage.py +++ b/{{cookiecutter.github_repository}}/manage.py @@ -12,7 +12,7 @@ # Read .env file and set key/value inside it as environement variables # see: http://github.com/theskumar/python-dotenv - load_dotenv(os.path.join(os.path.dirname(__file__), '.env')) + load_dotenv(os.path.join(os.path.dirname(__file__), ".env")) os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.development") diff --git a/{{cookiecutter.github_repository}}/settings/__init__.py b/{{cookiecutter.github_repository}}/settings/__init__.py index 9d575c2a..dbaaf801 100644 --- a/{{cookiecutter.github_repository}}/settings/__init__.py +++ b/{{cookiecutter.github_repository}}/settings/__init__.py @@ -1,7 +1,7 @@ # Standard Library import sys -if 'test' in sys.argv: - print('\033[1;91mNo django tests.\033[0m') - print('Try: \033[1;33mpytest\033[0m') +if "test" in sys.argv: + print("\033[1;91mNo django tests.\033[0m") + print("Try: \033[1;33mpytest\033[0m") sys.exit(0) diff --git a/{{cookiecutter.github_repository}}/settings/common.py b/{{cookiecutter.github_repository}}/settings/common.py index 67e1db02..2372caa3 100644 --- a/{{cookiecutter.github_repository}}/settings/common.py +++ b/{{cookiecutter.github_repository}}/settings/common.py @@ -11,7 +11,7 @@ ROOT_DIR = environ.Path(__file__) - 2 # (/a/b/myfile.py - 2 = /a/) -APPS_DIR = ROOT_DIR.path('{{ cookiecutter.main_module }}') +APPS_DIR = ROOT_DIR.path("{{ cookiecutter.main_module }}") env = environ.Env() @@ -20,28 +20,26 @@ # List of strings representing installed apps. # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django_sites', # http://niwinz.github.io/django-sites/latest/ - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'django.contrib.admin', - # 'django.contrib.humanize', # Useful template tags - - '{{ cookiecutter.main_module }}.base', - '{{ cookiecutter.main_module }}.users', - - 'rest_framework', # http://www.django-rest-framework.org/ - 'rest_framework_swagger', - 'versatileimagefield', # https://github.com/WGBH/django-versatileimagefield/ -{%- if cookiecutter.add_django_cors_headers.lower() == 'y' %} - 'corsheaders', # https://github.com/ottoyiu/django-cors-headers/ + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django_sites", # http://niwinz.github.io/django-sites/latest/ + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.admin", + # "django.contrib.humanize", # Useful template tags + "{{ cookiecutter.main_module }}.base", + "{{ cookiecutter.main_module }}.users", + "rest_framework", # http://www.django-rest-framework.org/ + "rest_framework_swagger", + "versatileimagefield", # https://github.com/WGBH/django-versatileimagefield/ +{%- if cookiecutter.add_django_cors_headers.lower() == "y" %} + "corsheaders", # https://github.com/ottoyiu/django-cors-headers/ {%- endif %} -{%- if cookiecutter.use_sentry_for_error_reporting == 'y' %} - 'raven.contrib.django.raven_compat', +{%- if cookiecutter.use_sentry_for_error_reporting == "y" %} + "raven.contrib.django.raven_compat", {%- endif %} - 'mail_templated', # https://github.com/artemrizhov/django-mail-templated + "mail_templated", # https://github.com/artemrizhov/django-mail-templated ) # INSTALLED APPS CONFIGURATION @@ -49,118 +47,103 @@ # django.contrib.auth # ------------------------------------------------------------------------------ -AUTH_USER_MODEL = 'users.User' -AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) +AUTH_USER_MODEL = "users.User" +AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",) PASSWORD_HASHERS = [ - 'django.contrib.auth.hashers.Argon2PasswordHasher', - 'django.contrib.auth.hashers.PBKDF2PasswordHasher', - 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', - 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', - 'django.contrib.auth.hashers.BCryptPasswordHasher', + "django.contrib.auth.hashers.Argon2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2PasswordHasher", + "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher", + "django.contrib.auth.hashers.BCryptSHA256PasswordHasher", + "django.contrib.auth.hashers.BCryptPasswordHasher", ] AUTH_PASSWORD_VALIDATORS = [ - {'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, - {'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 6, }}, - {'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, - {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + "OPTIONS": {"min_length": 6}, + }, + {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, + {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] # For Exposing browsable api urls. By default urls won't be exposed. -API_DEBUG = env.bool('API_DEBUG', default=False) +API_DEBUG = env.bool("API_DEBUG", default=False) # rest_framework # ------------------------------------------------------------------------------ REST_FRAMEWORK = { - 'DEFAULT_PAGINATION_CLASS': '{{ cookiecutter.main_module }}.base.api.pagination.PageNumberPagination', - 'PAGE_SIZE': 30, + "DEFAULT_PAGINATION_CLASS": "{{ cookiecutter.main_module }}.base.api.pagination.PageNumberPagination", + "PAGE_SIZE": 30, # Default renderer classes for Rest framework - 'DEFAULT_RENDERER_CLASSES': [ - 'rest_framework.renderers.JSONRenderer', - 'rest_framework.renderers.BrowsableAPIRenderer', + "DEFAULT_RENDERER_CLASSES": [ + "rest_framework.renderers.JSONRenderer", + "rest_framework.renderers.BrowsableAPIRenderer", ], - - # 'Accept' header based versioning + # "Accept" header based versioning # http://www.django-rest-framework.org/api-guide/versioning/ - 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.AcceptHeaderVersioning', - 'DEFAULT_VERSION': '1.0', - 'ALLOWED_VERSIONS': ['1.0', ], - 'VERSION_PARAMETER': 'version', - + "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.AcceptHeaderVersioning", + "DEFAULT_VERSION": "1.0", + "ALLOWED_VERSIONS": ["1.0"], + "VERSION_PARAMETER": "version", # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. - 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.IsAuthenticated', - ], - 'DEFAULT_THROTTLE_CLASSES': ( - 'rest_framework.throttling.AnonRateThrottle', - ), - 'DEFAULT_THROTTLE_RATES': { - 'anon': '10000/day', - }, - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'rest_framework.authentication.BasicAuthentication', - + "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], + "DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.AnonRateThrottle"], + "DEFAULT_THROTTLE_RATES": {"anon": "10000/day"}, + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework.authentication.BasicAuthentication", # Primary api authentication - '{{ cookiecutter.main_module }}.users.auth.backends.UserTokenAuthentication', - + "{{ cookiecutter.main_module }}.users.auth.backends.UserTokenAuthentication", # Mainly used for api debug. - 'rest_framework.authentication.SessionAuthentication', + "rest_framework.authentication.SessionAuthentication", ), - 'EXCEPTION_HANDLER': '{{ cookiecutter.main_module }}.base.exceptions.exception_handler', + "EXCEPTION_HANDLER": "{{ cookiecutter.main_module }}.base.exceptions.exception_handler", } # https://django-rest-swagger.readthedocs.io/en/latest/settings/ SWAGGER_SETTINGS = { - 'LOGIN_URL': 'rest_framework:login', - 'LOGOUT_URL': 'rest_framework:logout', - 'SECURITY_DEFINITIONS': { + "LOGIN_URL": "rest_framework:login", + "LOGOUT_URL": "rest_framework:logout", + "SECURITY_DEFINITIONS": { # For BasicAuthentication - 'basic': { - 'type': 'basic' - }, + "basic": {"type": "basic"}, # For UserTokenAuthentication - "api_key": { - "type": "apiKey", - "name": "Authorization", - "in": "header" - }, + "api_key": {"type": "apiKey", "name": "Authorization", "in": "header"}, }, } # DJANGO_SITES # ------------------------------------------------------------------------------ # see: http://django-sites.readthedocs.org -SITE_SCHEME = env("SITE_SCHEME", default='http') -SITE_DOMAIN = env("SITE_DOMAIN", default='localhost:8000') -SITE_NAME = env("SITE_NAME", default='{{ cookiecutter.project_name }}') +SITE_SCHEME = env("SITE_SCHEME", default="http") +SITE_DOMAIN = env("SITE_DOMAIN", default="localhost:8000") +SITE_NAME = env("SITE_NAME", default="{{ cookiecutter.project_name }}") # This is used in-case of the frontend is deployed at a different url than this django app. -FRONTEND_SITE_SCHEME = env('FRONTEND_SITE_SCHEME', default='https') -FRONTEND_SITE_DOMAIN = env('FRONTEND_SITE_DOMAIN', default='example.com') -FRONTEND_SITE_NAME = env('FRONTEND_SITE_NAME', default='{{ cookiecutter.project_name }}') +FRONTEND_SITE_SCHEME = env("FRONTEND_SITE_SCHEME", default="https") +FRONTEND_SITE_DOMAIN = env("FRONTEND_SITE_DOMAIN", default="example.com") +FRONTEND_SITE_NAME = env("FRONTEND_SITE_NAME", default="{{ cookiecutter.project_name }}") SITES = { - 'current': { - 'domain': SITE_DOMAIN, - 'scheme': SITE_SCHEME, - 'name': SITE_NAME - }, - 'frontend': { - 'domain': FRONTEND_SITE_DOMAIN, - 'scheme': FRONTEND_SITE_SCHEME, - 'name': FRONTEND_SITE_NAME + "current": {"domain": SITE_DOMAIN, "scheme": SITE_SCHEME, "name": SITE_NAME}, + "frontend": { + "domain": FRONTEND_SITE_DOMAIN, + "scheme": FRONTEND_SITE_SCHEME, + "name": FRONTEND_SITE_NAME, }, } -SITE_ID = 'current' +SITE_ID = "current" # see user.services.send_password_reset # password-confirm path should have placeholder for token FRONTEND_URLS = { - 'home': '/', - 'password-confirm': '/reset-password/{token}/', + "home": "/", + "password-confirm": "/reset-password/{token}/", } # MIDDLEWARE CONFIGURATION @@ -170,20 +153,20 @@ # response phase the middleware will be applied in reverse order. MIDDLEWARE = [ {%- if cookiecutter.add_django_cors_headers.lower() == 'y' %} - 'corsheaders.middleware.CorsMiddleware', + "corsheaders.middleware.CorsMiddleware", {%- endif %} - 'log_request_id.middleware.RequestIDMiddleware', # For generating/adding Request id for all the logs - 'django.middleware.security.SecurityMiddleware', -{%- if cookiecutter.enable_whitenoise.lower() == 'y' %} - 'whitenoise.middleware.WhiteNoiseMiddleware', + "log_request_id.middleware.RequestIDMiddleware", # For generating/adding Request id for all the logs + "django.middleware.security.SecurityMiddleware", +{%- if cookiecutter.enable_whitenoise.lower() == "y" %} + "whitenoise.middleware.WhiteNoiseMiddleware", {%- endif %} - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] # DJANGO CORE @@ -191,36 +174,33 @@ # See: https://docs.djangoproject.com/en/dev/ref/settings/#debug # Defaults to false, which is safe, enable them only in development. -DEBUG = env.bool('DJANGO_DEBUG', False) +DEBUG = env.bool("DJANGO_DEBUG", False) # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems. # In a Windows environment this must be set to your system time zone. -TIME_ZONE = '{{ cookiecutter.timezone }}' +TIME_ZONE = "{{ cookiecutter.timezone }}" # If you set this to False, Django will not use timezone-aware datetimes. USE_TZ = True # Language code for this installation. All choices can be found here: # http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" # Languages we provide translations for -LANGUAGES = ( - ('en', _('English')), -) +LANGUAGES = (("en", _("English")),) if USE_TZ: # Add timezone information to datetime displayed. # https://mounirmesselmeni.github.io/2014/11/06/date-format-in-django-admin/ from django.conf.locale.en import formats as en_formats - en_formats.DATETIME_FORMAT = 'N j, Y, P (e)' + + en_formats.DATETIME_FORMAT = "N j, Y, P (e)" # A tuple of directories where Django looks for translation files. -LOCALE_PATHS = ( - str(APPS_DIR.path('locale')), -) +LOCALE_PATHS = (str(APPS_DIR.path("locale")),) # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. @@ -232,120 +212,114 @@ # The list of directories to search for fixtures # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS -FIXTURE_DIRS = ( - str(APPS_DIR.path('fixtures')), -) +FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),) # The Python dotted path to the WSGI application that Django's internal servers # (runserver, runfcgi) will use. If `None`, the return value of -# 'django.core.wsgi.get_wsgi_application' is used, thus preserving the same +# "django.core.wsgi.get_wsgi_application" is used, thus preserving the same # behavior as previous versions of Django. Otherwise this should point to an # actual WSGI application object. # See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application -WSGI_APPLICATION = 'wsgi.application' +WSGI_APPLICATION = "wsgi.application" # URL CONFIGURATION # ------------------------------------------------------------------------------ -ROOT_URLCONF = '{{ cookiecutter.main_module }}.urls' +ROOT_URLCONF = "{{ cookiecutter.main_module }}.urls" # Use this to change base url path django admin -DJANGO_ADMIN_URL = env.str('DJANGO_ADMIN_URL', default='admin') +DJANGO_ADMIN_URL = env.str("DJANGO_ADMIN_URL", default="admin") # EMAIL CONFIGURATION # ------------------------------------------------------------------------------ -EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', - default='django.core.mail.backends.smtp.EmailBackend') -DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', - default='{{ cookiecutter.default_from_email }}') -EMAIL_SUBJECT_PREFIX = env('EMAIL_SUBJECT_PREFIX', default='[{{cookiecutter.project_name}}] ') -EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', default=True) -SERVER_EMAIL = env('SERVER_EMAIL', default=DEFAULT_FROM_EMAIL) +EMAIL_BACKEND = env( + "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend" +) +DEFAULT_FROM_EMAIL = env( + "DEFAULT_FROM_EMAIL", default="{{ cookiecutter.default_from_email }}" +) +EMAIL_SUBJECT_PREFIX = env("EMAIL_SUBJECT_PREFIX", default="[{{cookiecutter.project_name}}] ") +EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", default=True) +SERVER_EMAIL = env("SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) # DATABASE CONFIGURATION # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases DATABASES = { - 'default': env.db('DATABASE_URL', default='postgres://localhost/{{ cookiecutter.main_module }}'), + "default": env.db("DATABASE_URL", default="postgres://localhost/{{ cookiecutter.main_module }}") } -DATABASES['default']['ATOMIC_REQUESTS'] = True -DATABASES['default']['CONN_MAX_AGE'] = 10 -{% if cookiecutter.postgis.lower() == 'y' %}DATABASES['default']['ENGINE'] = 'django.contrib.gis.db.backends.postgis'{% endif %} +DATABASES["default"]["ATOMIC_REQUESTS"] = True +DATABASES["default"]["CONN_MAX_AGE"] = 10 +{% if cookiecutter.postgis.lower() == "y" %}DATABASES["default"]["ENGINE"] = "django.contrib.gis.db.backends.postgis"{% endif %} # TEMPLATE CONFIGURATION # ----------------------------------------------------------------------------- # See: https://docs.djangoproject.com/en/dev/ref/settings/#templates TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ - str(APPS_DIR.path('templates')), - ], - 'OPTIONS': { - 'debug': DEBUG, - 'loaders': [ - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [str(APPS_DIR.path("templates"))], + "OPTIONS": { + "debug": DEBUG, + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", ], - # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors - 'context_processors': [ - '{{cookiecutter.main_module}}.base.context_processors.site_settings', - 'django.contrib.auth.context_processors.auth', - 'django.template.context_processors.debug', - 'django.template.context_processors.i18n', - 'django.template.context_processors.media', - 'django.template.context_processors.static', - 'django.template.context_processors.tz', - 'django.contrib.messages.context_processors.messages', - 'django.template.context_processors.request', + "context_processors": [ + "{{cookiecutter.main_module}}.base.context_processors.site_settings", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "django.template.context_processors.request", # Your stuff: custom template context processors go here ], }, - }, + } ] -CSRF_FAILURE_VIEW = '{{ cookiecutter.main_module }}.base.views.csrf_failure' +CSRF_FAILURE_VIEW = "{{ cookiecutter.main_module }}.base.views.csrf_failure" # STATIC FILE CONFIGURATION # ----------------------------------------------------------------------------- # Absolute path to the directory static files should be collected to. -# Example: '/var/www/example.com/static/' +# Example: "/var/www/example.com/static/" # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root -STATIC_ROOT = str(ROOT_DIR.path('.staticfiles')) +STATIC_ROOT = str(ROOT_DIR.path(".staticfiles")) # URL that handles the static files served from STATIC_ROOT. -# Example: 'http://example.com/static/', 'http://static.example.com/' +# Example: "http://example.com/static/", "http://static.example.com/" # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url -STATIC_URL = '/static/' +STATIC_URL = "/static/" # A list of locations of additional static files -{%- if cookiecutter.webpack.lower() == 'y' %} +{%- if cookiecutter.webpack.lower() == "y" %} # Specify the static directory in fabfile also. {%- endif %} -STATICFILES_DIRS = ( - str(APPS_DIR.path('static')), -) +STATICFILES_DIRS = (str(APPS_DIR.path("static")),) # List of finder classes that know how to find static files in # various locations. STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", ) # MEDIA CONFIGURATION # ------------------------------------------------------------------------------ # Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: '/var/www/example.com/media/' +# Example: "/var/www/example.com/media/" # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root -MEDIA_ROOT = str(ROOT_DIR.path('.media')) +MEDIA_ROOT = str(ROOT_DIR.path(".media")) # URL that handles the media served from MEDIA_ROOT. -# Examples: 'http://example.com/media/', 'http://media.example.com/' +# Examples: "http://example.com/media/", "http://media.example.com/" # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url -MEDIA_URL = env("MEDIA_URL", - default="{}://{}/media/".format(SITE_SCHEME, SITE_DOMAIN)) +MEDIA_URL = env("MEDIA_URL", default="{}://{}/media/".format(SITE_SCHEME, SITE_DOMAIN)) # SECURITY # ----------------------------------------------------------------------------- @@ -354,32 +328,30 @@ SECURE_CONTENT_TYPE_NOSNIFF = True SECURE_BROWSER_XSS_FILTER = True -X_FRAME_OPTIONS = 'DENY' +X_FRAME_OPTIONS = "DENY" # django-log-request-id - Sending request id in response -REQUEST_ID_RESPONSE_HEADER = 'REQUEST_ID' +REQUEST_ID_RESPONSE_HEADER = "REQUEST_ID" -{%- if cookiecutter.add_django_cors_headers.lower() == 'y' %} +{%- if cookiecutter.add_django_cors_headers.lower() == "y" %} # CORS # -------------------------------------------------------------------------- -CORS_ORIGIN_WHITELIST = env.list('CORS_ORIGIN_WHITELIST', default=[]) -CORS_ALLOW_HEADERS = default_headers + ( - 'access-control-allow-origin', -) +CORS_ORIGIN_WHITELIST = env.list("CORS_ORIGIN_WHITELIST", default=[]) +CORS_ALLOW_HEADERS = default_headers + ("access-control-allow-origin",) {%- endif %} -{%- if cookiecutter.add_celery.lower() == 'y' %} +{%- if cookiecutter.add_celery.lower() == "y" %} # DJANGO CELERY CONFIGURATION # ----------------------------------------------------------------------------- # see: http://celery.readthedocs.org/en/latest/userguide/tasks.html#task-states -CELERY_BROKER_URL = env('REDIS_URL', default="redis://localhost:6379/0") -CELERY_ACCEPT_CONTENT = ['json'] -CELERY_TASK_SERIALIZER = 'json' -CELERY_RESULT_SERIALIZER = 'json' -CELERY_TIMEZONE = env('CELERY_TIMEZONE', default=TIME_ZONE) # Use django's timezone by default +CELERY_BROKER_URL = env("REDIS_URL", default="redis://localhost:6379/0") +CELERY_ACCEPT_CONTENT = ["json"] +CELERY_TASK_SERIALIZER = "json" +CELERY_RESULT_SERIALIZER = "json" +CELERY_TIMEZONE = env("CELERY_TIMEZONE", default=TIME_ZONE) # Use Django's timezone by default {%- endif %} # LOGGING CONFIGURATION @@ -391,98 +363,85 @@ # See http://docs.djangoproject.com/en/dev/topics/logging LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': {'()': 'django.utils.log.RequireDebugFalse'}, - 'request_id': {'()': 'log_request_id.filters.RequestIDFilter'}, + "version": 1, + "disable_existing_loggers": False, + "filters": { + "require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}, + "request_id": {"()": "log_request_id.filters.RequestIDFilter"}, }, - 'formatters': { - 'complete': { - # NOTE: make sure to include 'request_id' in filters when using this + "formatters": { + "complete": { + # NOTE: make sure to include "request_id" in filters when using this # formatter in any handlers. - 'format': '%(asctime)s:[%(levelname)s]:logger=%(name)s:request_id=%(request_id)s message="%(message)s"' + "format": "%(asctime)s:[%(levelname)s]:logger=%(name)s:request_id=%(request_id)s message='%(message)s'" }, - 'simple': {'format': '%(levelname)s:%(asctime)s: %(message)s'}, - 'django.server': { - '()': 'django.utils.log.ServerFormatter', - 'format': '[%(server_time)s] %(message)s', + "simple": {"format": "%(levelname)s:%(asctime)s: %(message)s"}, + "django.server": { + "()": "django.utils.log.ServerFormatter", + "format": "[%(server_time)s] %(message)s", }, }, - 'handlers': { - 'null': {'level': 'DEBUG', 'class': 'logging.NullHandler'}, - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'complete', - 'filters': ['request_id'], + "handlers": { + "null": {"level": "DEBUG", "class": "logging.NullHandler"}, + "console": { + "level": "DEBUG", + "class": "logging.StreamHandler", + "formatter": "complete", + "filters": ["request_id"], }, - 'django.server': { - 'level': 'INFO', - 'class': 'logging.StreamHandler', - 'formatter': 'django.server', + "django.server": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "django.server", }, - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' + "mail_admins": { + "level": "ERROR", + "filters": ["require_debug_false"], + "class": "django.utils.log.AdminEmailHandler" }, - {%- if cookiecutter.use_sentry_for_error_reporting == 'y' %} - 'sentry': { - 'level': 'ERROR', - 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', - 'formatter': 'complete', - 'filters': ['request_id'], + {%- if cookiecutter.use_sentry_for_error_reporting == "y" %} + "sentry": { + "level": "ERROR", + "class": "raven.contrib.django.raven_compat.handlers.SentryHandler", + "formatter": "complete", + "filters": ["request_id"], }, {%- endif %} }, - 'loggers': { - 'django': { - 'handlers': ['null'], - 'propagate': False, - 'level': 'INFO', - }, - 'django.request': { - 'handlers': ['mail_admins', 'console'], - 'level': 'ERROR', - 'propagate': False, + "loggers": { + "django": { + "handlers": ["null"], + "propagate": False, + "level": "INFO", }, - 'django.server': { - 'handlers': ['django.server'], - 'level': 'INFO', - 'propagate': False, + "django.request": { + "handlers": ["mail_admins", "console"], + "level": "ERROR", + "propagate": False, }, - '{{cookiecutter.main_module}}': { - 'handlers': ['console'], - 'level': 'INFO', - 'propagate': False, + "django.server": { + "handlers": ["django.server"], + "level": "INFO", + "propagate": False, }, + "{{cookiecutter.main_module}}": {"handlers": ["console"], "level": "INFO", "propagate": False}, # Catch All Logger -- Captures any other logging - '': { - 'handlers': [ - 'console', - {%- if cookiecutter.use_sentry_for_error_reporting == 'y' %} - 'sentry' - {%- endif %} - ], - 'level': 'ERROR', - 'propagate': True, - }, + "": {"handlers": ["console",{%- if cookiecutter.use_sentry_for_error_reporting == "y" %} "sentry"{%- endif %}], "level": "ERROR", "propagate": True}, } } def get_release(): import {{cookiecutter.main_module}} - {%- if cookiecutter.use_sentry_for_error_reporting == 'y' %} + {%- if cookiecutter.use_sentry_for_error_reporting == "y" %} import os import raven - {%- endif %} +{% endif %} release = {{cookiecutter.main_module}}.__version__ - {%- if cookiecutter.use_sentry_for_error_reporting == 'y' %} + {%- if cookiecutter.use_sentry_for_error_reporting == "y" %} try: git_hash = raven.fetch_git_sha(os.path.dirname(os.pardir))[:7] - release = '{}-{}'.format(release, git_hash) + release = "{}-{}".format(release, git_hash) except raven.exceptions.InvalidGitRepository: pass {%- endif %} @@ -492,33 +451,33 @@ def get_release(): RELEASE_VERSION = get_release() -{%- if cookiecutter.use_sentry_for_error_reporting == 'y' %} +{%- if cookiecutter.use_sentry_for_error_reporting == "y" %} RAVEN_CONFIG = { - 'dsn': env('SENTRY_DSN', default=''), - 'environment': env('SENTRY_ENVIRONMENT', default='production'), - 'release': RELEASE_VERSION, + "dsn": env("SENTRY_DSN", default=""), + "environment": env("SENTRY_ENVIRONMENT", default="production"), + "release": RELEASE_VERSION, } {%- endif %} SITE_INFO = { - 'RELEASE_VERSION': RELEASE_VERSION, -{%- if cookiecutter.use_sentry_for_error_reporting == 'y' %} - 'IS_RAVEN_INSTALLED': True if RAVEN_CONFIG.get('dsn') else False + "RELEASE_VERSION": RELEASE_VERSION, +{%- if cookiecutter.use_sentry_for_error_reporting == "y" %} + "IS_RAVEN_INSTALLED": True if RAVEN_CONFIG.get("dsn") else False, {%- endif %} } -{%- if cookiecutter.webpack.lower() == 'y' %} +{%- if cookiecutter.webpack.lower() == "y" %} # Webpack Support (https://github.com/owais/django-webpack-loader) # ============================================================================= -INSTALLED_APPS += ('webpack_loader', ) +INSTALLED_APPS += ("webpack_loader",) WEBPACK_LOADER = { - 'DEFAULT': { - 'CACHE': True, - 'BUNDLE_DIR_NAME': 'dist/', # It will add static path before and it must end with slash - 'STATS_FILE': str(ROOT_DIR.path('webpack-stats.json')), - 'POLL_INTERVAL': 0.1, - 'TIMEOUT': None, - 'IGNORE': [r'.+\.hot-update.js', r'.+\.map'] + "DEFAULT": { + "CACHE": True, + "BUNDLE_DIR_NAME": "dist/", # It will add static path before and it must end with slash + "STATS_FILE": str(ROOT_DIR.path("webpack-stats.json")), + "POLL_INTERVAL": 0.1, + "TIMEOUT": None, + "IGNORE": [r".+\.hot-update.js", r".+\.map"], } } {%- endif %} diff --git a/{{cookiecutter.github_repository}}/settings/development.py b/{{cookiecutter.github_repository}}/settings/development.py index 97738973..95d71d8e 100644 --- a/{{cookiecutter.github_repository}}/settings/development.py +++ b/{{cookiecutter.github_repository}}/settings/development.py @@ -14,19 +14,19 @@ # DEBUG # ------------------------------------------------------------------------------ -DEBUG = env.bool('DJANGO_DEBUG', default=True) -TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # noqa: F405 +DEBUG = env.bool("DJANGO_DEBUG", default=True) +TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG # noqa: F405 -INTERNAL_IPS = ('127.0.0.1', '192.168.33.12', ) +INTERNAL_IPS = ("127.0.0.1", "192.168.33.12") -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] -{%- if cookiecutter.enable_whitenoise.lower() == 'y' %} +{%- if cookiecutter.enable_whitenoise.lower() == "y" %} # Staticfiles # ------------------------------------------------------------------------------ # Disable Django's static file handling and allow WhiteNoise to take over. This # helps in minimizing dev/prod differences when serving static files. -INSTALLED_APPS = ('whitenoise.runserver_nostatic', ) + INSTALLED_APPS +INSTALLED_APPS = ("whitenoise.runserver_nostatic", ) + INSTALLED_APPS {%- endif %} @@ -37,46 +37,49 @@ # loudly. # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # Note: This key only used for development and testing. -SECRET_KEY = env("DJANGO_SECRET_KEY", default='CHANGEME!!!') -{%- if cookiecutter.add_django_cors_headers.lower() == 'y' %} +SECRET_KEY = env("DJANGO_SECRET_KEY", default="CHANGEME!!!") +{%- if cookiecutter.add_django_cors_headers.lower() == "y" %} # cors # -------------------------------------------------------------------------- -CORS_ORIGIN_WHITELIST = env.list('CORS_ORIGIN_WHITELIST', default=['http://localhost', 'http://localhost:8000']) +CORS_ORIGIN_WHITELIST = env.list( + "CORS_ORIGIN_WHITELIST", default=["http://localhost", "http://localhost:8000"] +) {%- endif %} # Mail settings # ------------------------------------------------------------------------------ -EMAIL_HOST = 'localhost' +EMAIL_HOST = "localhost" EMAIL_PORT = 1025 -EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', - default='django.core.mail.backends.console.EmailBackend') +EMAIL_BACKEND = env( + "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" +) # CACHES # ------------------------------------------------------------------------------ CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': '' + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "", } } # django-extensions (http://django-extensions.readthedocs.org/) # ------------------------------------------------------------------------------ -INSTALLED_APPS += ('django_extensions', ) +INSTALLED_APPS += ("django_extensions",) # django-debug-toolbar # ------------------------------------------------------------------------------ -MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware', ] # noqa: F405 -INSTALLED_APPS += ('debug_toolbar', ) +MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405 +INSTALLED_APPS += ("debug_toolbar",) DEBUG_TOOLBAR_CONFIG = { - 'DISABLE_PANELS': ['debug_toolbar.panels.redirects.RedirectsPanel', ], - 'SHOW_TEMPLATE_CONTEXT': True, + "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"], + "SHOW_TEMPLATE_CONTEXT": True, } # This will expose all browsable api urls. For dev the default value is true -API_DEBUG = env.bool('API_DEBUG', default=True) +API_DEBUG = env.bool("API_DEBUG", default=True) # MEDIA CONFIGURATION # ------------------------------------------------------------------------------ @@ -84,6 +87,6 @@ # Media configuration to support deployment of media files while is debug=True or development. MEDIA_URL = env("MEDIA_URL", default="/media/") -{%- if cookiecutter.webpack.lower() == 'y' %} -WEBPACK_LOADER['DEFAULT']['CACHE'] = False # noqa: F405 +{%- if cookiecutter.webpack.lower() == "y" %} +WEBPACK_LOADER["DEFAULT"]["CACHE"] = False # noqa: F405 {%- endif %} diff --git a/{{cookiecutter.github_repository}}/settings/production.py b/{{cookiecutter.github_repository}}/settings/production.py index 16e8fc40..db08d53b 100644 --- a/{{cookiecutter.github_repository}}/settings/production.py +++ b/{{cookiecutter.github_repository}}/settings/production.py @@ -23,40 +23,40 @@ {% if cookiecutter.add_django_auth_wall.lower() == 'y' %}MIDDLEWARE,{%- endif %} REST_FRAMEWORK, TEMPLATES, - env + env, ) # SITE CONFIGURATION # Ensure these are set in the `.env` file manually. -SITE_SCHEME = env('SITE_SCHEME') -SITE_DOMAIN = env('SITE_DOMAIN') +SITE_SCHEME = env("SITE_SCHEME") +SITE_DOMAIN = env("SITE_DOMAIN") # Hosts/domain names that are valid for this site. # "*" matches anything, ".example.com" matches example.com and all subdomains # See https://docs.djangoproject.com/en/1.11/ref/settings/#allowed-hosts -ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[SITE_DOMAIN]) +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=[SITE_DOMAIN]) # MANAGER CONFIGURATION # ------------------------------------------------------------------------------ # People who get code error notifications. -# In the format 'Full Name , Full Name ' -ADMINS = getaddresses([env('DJANGO_ADMINS')]) +# In the format "Full Name , Full Name " +ADMINS = getaddresses([env("DJANGO_ADMINS")]) # Not-necessarily-technical managers of the site. They get broken link # notifications and other various emails. MANAGERS = ADMINS -{%- if cookiecutter.add_django_cors_headers.lower() == 'y' %} +{%- if cookiecutter.add_django_cors_headers.lower() == "y" %} # CORS # -------------------------------------------------------------------------- -CORS_ORIGIN_WHITELIST = env.list('CORS_ORIGIN_WHITELIST') +CORS_ORIGIN_WHITELIST = env.list("CORS_ORIGIN_WHITELIST") {%- endif %} -{% if cookiecutter.add_django_auth_wall.lower() == 'y' %} +{% if cookiecutter.add_django_auth_wall.lower() == "y" %} # Basic Auth Protection # ----------------------------------------------------------------------------- # see: https://github.com/theskumar/django-auth-wall#django-auth-wall -MIDDLEWARE = ['django_auth_wall.middleware.BasicAuthMiddleware', ] + MIDDLEWARE +MIDDLEWARE = ["django_auth_wall.middleware.BasicAuthMiddleware"] + MIDDLEWARE {%- endif %} # If your Django app is behind a proxy that sets a header to specify secure @@ -64,23 +64,23 @@ # same name are ignored (so that people can't spoof it), set this value to # a tuple of (header_name, header_value). For any requests that come in with # that header/value, request.is_secure() will return True. -# WARNING! Only set this if you fully understand what you're doing. Otherwise, +# WARNING! Only set this if you fully understand what you"re doing. Otherwise, # you may be opening yourself up to a security risk. -{%- if cookiecutter.enable_heroku_deployment.lower() == 'y' %} +{%- if cookiecutter.enable_heroku_deployment.lower() == "y" %} # This ensures that Django will be able to detect a secure connection # properly on Heroku. {%- endif %} -SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") # SECURITY # ----------------------------------------------------------------------------- # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # Raises ImproperlyConfigured exception if DJANGO_SECRET_KEY not in os.environ -SECRET_KEY = env('DJANGO_SECRET_KEY') +SECRET_KEY = env("DJANGO_SECRET_KEY") -if SITE_SCHEME == 'https': +if SITE_SCHEME == "https": # set this to 60 seconds and then to 518400 when you can prove it works - SECURE_HSTS_SECONDS = env.int('DJANGO_SECURE_HSTS_SECONDS', default=60) + SECURE_HSTS_SECONDS = env.int("DJANGO_SECURE_HSTS_SECONDS", default=60) SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True @@ -93,15 +93,15 @@ # Uploaded Media Files # ------------------------ # See: http://django-storages.readthedocs.org/en/latest/index.html - INSTALLED_APPS += ('storages',) - DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' + INSTALLED_APPS += ("storages",) + DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" - AWS_ACCESS_KEY_ID = env('DJANGO_AWS_ACCESS_KEY_ID') - AWS_SECRET_ACCESS_KEY = env('DJANGO_AWS_SECRET_ACCESS_KEY') - AWS_STORAGE_BUCKET_NAME = env('DJANGO_AWS_STORAGE_BUCKET_NAME') + AWS_ACCESS_KEY_ID = env("DJANGO_AWS_ACCESS_KEY_ID") + AWS_SECRET_ACCESS_KEY = env("DJANGO_AWS_SECRET_ACCESS_KEY") + AWS_STORAGE_BUCKET_NAME = env("DJANGO_AWS_STORAGE_BUCKET_NAME") AWS_QUERYSTRING_AUTH = False - AWS_S3_HOST = env('DJANGO_AWS_S3_HOST', default='') - AWS_S3_REGION_NAME = env('DJANGO_AWS_S3_REGION_NAME', default=None) + AWS_S3_HOST = env("DJANGO_AWS_S3_HOST", default="") + AWS_S3_REGION_NAME = env("DJANGO_AWS_S3_REGION_NAME", default=None) # AWS cache settings, don't change unless you know what you're doing. AWS_EXPIRY = 60 * 60 * 24 * 7 # 1 week @@ -110,70 +110,77 @@ # Revert the following and use str after the above-mentioned bug is fixed in # either django-storage-redux or boto AWS_HEADERS = { - 'Cache-Control': six.b('max-age=%d, s-maxage=%d, must-revalidate' % ( - AWS_EXPIRY, AWS_EXPIRY)) + "Cache-Control": six.b( + "max-age=%d, s-maxage=%d, must-revalidate" % (AWS_EXPIRY, AWS_EXPIRY) + ) } # URL that handles the media served from MEDIA_ROOT, used for managing stored files. - MEDIA_URL = env('MEDIA_URL', - default='https://s3.amazonaws.com/%s/' % AWS_STORAGE_BUCKET_NAME) + MEDIA_URL = env( + "MEDIA_URL", default="https://s3.amazonaws.com/%s/" % AWS_STORAGE_BUCKET_NAME + ) # Static Assets # ------------------------ -{%- if cookiecutter.enable_whitenoise.lower() == 'y' %} -STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' +{%- if cookiecutter.enable_whitenoise.lower() == "y" %} +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" {%- else %} -STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' +STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" {%- endif %} # EMAIL # ------------------------------------------------------------------------------ # DEFAULT_FROM_EMAIL in settings/common.py -EMAIL_HOST = env('EMAIL_HOST') -EMAIL_HOST_PASSWORD = env('EMAIL_HOST_PASSWORD') -EMAIL_HOST_USER = env('EMAIL_HOST_USER') -EMAIL_PORT = env.int('EMAIL_PORT', default=587) +EMAIL_HOST = env("EMAIL_HOST") +EMAIL_HOST_PASSWORD = env("EMAIL_HOST_PASSWORD") +EMAIL_HOST_USER = env("EMAIL_HOST_USER") +EMAIL_PORT = env.int("EMAIL_PORT", default=587) # DATABASE CONFIGURATION # ------------------------------------------------------------------------------ # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ -DATABASES['default'].update(env.db('DATABASE_URL')) # Should not override all db settings -{% if cookiecutter.postgis == 'y' %}DATABASES['default']['ENGINE'] = 'django.contrib.gis.db.backends.postgis'{% endif %} +DATABASES["default"].update(env.db("DATABASE_URL")) # Don't override all db settings +{% if cookiecutter.postgis == "y" %}DATABASES["default"]["ENGINE"] = "django.contrib.gis.db.backends.postgis"{% endif %} # CACHING # ------------------------------------------------------------------------------ # Note: Specify different redis database name, if same redis instance is used. CACHES = { - 'default': { - 'BACKEND': 'django_redis.cache.RedisCache', - 'LOCATION': env('REDIS_URL', default='redis://localhost:6379/0'), - 'OPTIONS': { - 'CLIENT_CLASS': 'django_redis.client.DefaultClient', - 'PARSER_CLASS': 'redis.connection.HiredisParser', - 'CONNECTION_POOL_CLASS': 'redis.BlockingConnectionPool', - 'CONNECTION_POOL_CLASS_KWARGS': { -{%- if cookiecutter.enable_heroku_deployment.lower() == 'y' %} + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": env("REDIS_URL", default="redis://localhost:6379/0"), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + "PARSER_CLASS": "redis.connection.HiredisParser", + "CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool", + "CONNECTION_POOL_CLASS_KWARGS": { +{%- if cookiecutter.enable_heroku_deployment.lower() == "y" %} # Hobby redistogo on heroku only supports max. 10, increase as required. {%- endif %} - 'max_connections': env.int('REDIS_MAX_CONNECTIONS', default=10), - 'timeout': 20, - } - } + "max_connections": env.int("REDIS_MAX_CONNECTIONS", default=10), + "timeout": 20, + }, + }, } } # https://docs.djangoproject.com/en/1.10/topics/http/sessions/#using-cached-sessions -SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' -SESSION_CACHE_ALIAS = 'default' +SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" +SESSION_CACHE_ALIAS = "default" # TEMPLATE CONFIGURATION # ----------------------------------------------------------------------------- # See: https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.cached.Loader -TEMPLATES[0]['OPTIONS']['loaders'] = [ - ('django.template.loaders.cached.Loader', TEMPLATES[0]['OPTIONS']['loaders']), +TEMPLATES[0]["OPTIONS"]["loaders"] = [ + ("django.template.loaders.cached.Loader", TEMPLATES[0]["OPTIONS"]["loaders"]) ] if not API_DEBUG: # noqa: F405 # blocking browsable api for rest framework and allowing just json renderer - if 'rest_framework.renderers.BrowsableAPIRenderer' in REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES']: - REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'].remove('rest_framework.renderers.BrowsableAPIRenderer') + if ( + "rest_framework.renderers.BrowsableAPIRenderer" + in REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] + ): + REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"].remove( + "rest_framework.renderers.BrowsableAPIRenderer" + ) diff --git a/{{cookiecutter.github_repository}}/settings/testing.py b/{{cookiecutter.github_repository}}/settings/testing.py index 594634b3..dee43219 100644 --- a/{{cookiecutter.github_repository}}/settings/testing.py +++ b/{{cookiecutter.github_repository}}/settings/testing.py @@ -5,9 +5,9 @@ from .development import * # noqa F405 -MEDIA_ROOT = '/tmp' +MEDIA_ROOT = "/tmp" -SECRET_KEY = 'top-scret!' +SECRET_KEY = "top-scret!" -EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' -INSTALLED_APPS += ('tests', ) # noqa: F405 +EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" +INSTALLED_APPS += ("tests",) # noqa: F405 diff --git a/{{cookiecutter.github_repository}}/setup.cfg b/{{cookiecutter.github_repository}}/setup.cfg index b7e94740..ec5f8a3f 100644 --- a/{{cookiecutter.github_repository}}/setup.cfg +++ b/{{cookiecutter.github_repository}}/setup.cfg @@ -12,7 +12,9 @@ tag = True [bumpversion:file:README.md] [flake8] -max-line-length = 120 +ignore = E203, E266, E501, W503 +max-line-length = 100 +select = B,C,E,F,W,T4,B9 exclude = .tox,.git,*/migrations/*,*/static/*,docs,venv,.venv,node_modules [tool:pytest] diff --git a/{{cookiecutter.github_repository}}/tests/conftest.py b/{{cookiecutter.github_repository}}/tests/conftest.py index c405abe8..437e2016 100644 --- a/{{cookiecutter.github_repository}}/tests/conftest.py +++ b/{{cookiecutter.github_repository}}/tests/conftest.py @@ -22,21 +22,22 @@ def __getattr__(self, name): return functools.partial(getattr(self.obj, name), **self.partial_params) -@pytest.fixture(autouse=True, scope='function') +@pytest.fixture(autouse=True, scope="function") def cleared_cache(): """Fixture that exposes django cache, which is empty to start with. This fixture also makes sures that cache is cleared before running each and every test case. """ from django.core.cache import cache + cache.clear() return cache -@pytest.fixture(autouse=True, scope='function') +@pytest.fixture(autouse=True, scope="function") def media_root(settings, tmpdir_factory): """Forces django to save media files into temp folder.""" - settings.MEDIA_ROOT = tmpdir_factory.mktemp('media', numbered=True) + settings.MEDIA_ROOT = tmpdir_factory.mktemp("media", numbered=True) return settings.MEDIA_ROOT @@ -47,15 +48,19 @@ def client(): from django.test import Client class _Client(Client): - - def login(self, user=None, backend="django.contrib.auth.backends.ModelBackend", **credentials): + def login( + self, + user=None, + backend="django.contrib.auth.backends.ModelBackend", + **credentials + ): """Modified login method, which allows setup an authenticated session with just passing in the user object, if provided. """ if user is None: return super().login(**credentials) - with mock.patch('django.contrib.auth.authenticate') as authenticate: + with mock.patch("django.contrib.auth.authenticate") as authenticate: user.backend = backend authenticate.return_value = user return super().login(**credentials) @@ -70,6 +75,8 @@ def json(self): >>> client.json.get(url) >>> client.json.post(url, data=json.dumps(payload)) """ - return PartialMethodCaller(obj=self, content_type='application/json;charset="utf-8"') + return PartialMethodCaller( + obj=self, content_type='application/json;charset="utf-8"' + ) return _Client() diff --git a/{{cookiecutter.github_repository}}/tests/factories.py b/{{cookiecutter.github_repository}}/tests/factories.py index 44313e67..03215e21 100644 --- a/{{cookiecutter.github_repository}}/tests/factories.py +++ b/{{cookiecutter.github_repository}}/tests/factories.py @@ -20,6 +20,6 @@ def create_user(**kwargs): """Create an user along with their dependencies.""" User = apps.get_model(settings.AUTH_USER_MODEL) user = G(User, **kwargs) - user.set_password(kwargs.get('password', 'test')) + user.set_password(kwargs.get("password", "test")) user.save() return user diff --git a/{{cookiecutter.github_repository}}/tests/integration/test_auth.py b/{{cookiecutter.github_repository}}/tests/integration/test_auth.py index 3b7ab753..98ab8c17 100644 --- a/{{cookiecutter.github_repository}}/tests/integration/test_auth.py +++ b/{{cookiecutter.github_repository}}/tests/integration/test_auth.py @@ -14,43 +14,33 @@ def test_user_registration(client): - url = reverse('auth-register') - credentials = { - 'email': 'test@test.com', - 'password': 'localhost' - } + url = reverse("auth-register") + credentials = {"email": "test@test.com", "password": "localhost"} response = client.json.post(url, json.dumps(credentials)) assert response.status_code == 201 - expected_keys = [ - 'id', 'email', 'first_name', 'last_name', 'auth_token' - ] + expected_keys = ["id", "email", "first_name", "last_name", "auth_token"] assert set(expected_keys).issubset(response.data.keys()) def test_user_login(client): - url = reverse('auth-login') - u = f.create_user(email='test@example.com', password='test') + url = reverse("auth-login") + u = f.create_user(email="test@example.com", password="test") - credentials = { - 'email': u.email, - 'password': 'test' - } + credentials = {"email": u.email, "password": "test"} response = client.json.post(url, json.dumps(credentials)) assert response.status_code == 200 - expected_keys = [ - 'id', 'email', 'first_name', 'last_name', 'auth_token' - ] + expected_keys = ["id", "email", "first_name", "last_name", "auth_token"] assert set(expected_keys).issubset(response.data.keys()) def test_user_password_change(client): - url = reverse('auth-password-change') - current_password = 'password1' - new_password = 'paSswOrd2.#$' - user = f.create_user(email='test@example.com', password=current_password) + url = reverse("auth-password-change") + current_password = "password1" + new_password = "paSswOrd2.#$" + user = f.create_user(email="test@example.com", password=current_password) change_password_payload = { - 'current_password': current_password, - 'new_password': new_password + "current_password": current_password, + "new_password": new_password, } client.login(user=user) @@ -58,16 +48,11 @@ def test_user_password_change(client): assert response.status_code == 204 client.logout() - url = reverse('auth-login') - credentials = { - 'email': user.email, - 'password': new_password - } + url = reverse("auth-login") + credentials = {"email": user.email, "password": new_password} response = client.json.post(url, json.dumps(credentials)) assert response.status_code == 200 - expected_keys = [ - 'id', 'email', 'first_name', 'last_name', 'auth_token' - ] + expected_keys = ["id", "email", "first_name", "last_name", "auth_token"] assert set(expected_keys).issubset(response.data.keys()) user.refresh_from_db() @@ -75,41 +60,43 @@ def test_user_password_change(client): def test_user_password_reset(client, mailoutbox, settings): - url = reverse('auth-password-reset') - user = f.create_user(email='test@example.com') + url = reverse("auth-password-reset") + user = f.create_user(email="test@example.com") - response = client.json.post(url, json.dumps({'email': user.email})) + response = client.json.post(url, json.dumps({"email": user.email})) assert response.status_code == 200 assert len(mailoutbox) == 1 mail_body = mailoutbox[0].body token = get_token_for_password_reset(user) - assert "{}://{}".format(settings.FRONTEND_SITE_SCHEME, settings.FRONTEND_SITE_DOMAIN) in mail_body + assert ( + "{}://{}".format(settings.FRONTEND_SITE_SCHEME, settings.FRONTEND_SITE_DOMAIN) + in mail_body + ) assert token in mail_body assert user.email in mailoutbox[0].to def test_user_password_reset_and_confirm(client, settings, mocker): - url = reverse('auth-password-reset') - user = f.create_user(email='test@example.com') - mock_email = mocker.patch('{{cookiecutter.main_module}}.users.auth.services.send_mail') + url = reverse("auth-password-reset") + user = f.create_user(email="test@example.com") + mock_email = mocker.patch( + "{{cookiecutter.main_module}}.users.auth.services.send_mail" + ) - response = client.json.post(url, json.dumps({'email': user.email})) + response = client.json.post(url, json.dumps({"email": user.email})) assert response.status_code == 200 assert mock_email.call_count == 1 args, kwargs = mock_email.call_args - assert user.email in kwargs.get('recipient_list') + assert user.email in kwargs.get("recipient_list") # get the context passed to template - token = kwargs['context']['token'] + token = kwargs["context"]["token"] # confirm we can reset password using context values - new_password = 'CompLicatedpaSswOrd2' - password_reset_confirm_data = { - 'new_password': new_password, - 'token': token - } - url = reverse('auth-password-reset-confirm') + new_password = "CompLicatedpaSswOrd2" + password_reset_confirm_data = {"new_password": new_password, "token": token} + url = reverse("auth-password-reset-confirm") response = client.json.post(url, json.dumps(password_reset_confirm_data)) assert response.status_code == 204 user.refresh_from_db() diff --git a/{{cookiecutter.github_repository}}/tests/integration/test_current_user_api.py b/{{cookiecutter.github_repository}}/tests/integration/test_current_user_api.py index 745829c8..bcdaed95 100644 --- a/{{cookiecutter.github_repository}}/tests/integration/test_current_user_api.py +++ b/{{cookiecutter.github_repository}}/tests/integration/test_current_user_api.py @@ -12,8 +12,8 @@ def test_get_current_user_api(client): - url = reverse('me') - user = f.create_user(email='test@example.com') + url = reverse("me") + user = f.create_user(email="test@example.com") # should require auth response = client.get(url) @@ -24,21 +24,19 @@ def test_get_current_user_api(client): # assert response is None assert response.status_code == 200 - expected_keys = [ - 'id', 'email', 'first_name', 'last_name' - ] + expected_keys = ["id", "email", "first_name", "last_name"] assert set(expected_keys).issubset(response.data.keys()) - assert response.data['id'] == str(user.id) + assert response.data["id"] == str(user.id) def test_patch_current_user_api(client): - url = reverse('me') - user = f.create_user(email='test@example.com', first_name='test', last_name='test') + url = reverse("me") + user = f.create_user(email="test@example.com", first_name="test", last_name="test") data = { - 'first_name': 'modified_test', - 'last_name': 'modified_test', - 'email': 'modified_test@example.com' + "first_name": "modified_test", + "last_name": "modified_test", + "email": "modified_test@example.com", } # should require auth @@ -49,11 +47,9 @@ def test_patch_current_user_api(client): response = client.json.patch(url, json.dumps(data)) # assert response is None assert response.status_code == 200 - expected_keys = [ - 'id', 'email', 'first_name', 'last_name' - ] + expected_keys = ["id", "email", "first_name", "last_name"] assert set(expected_keys).issubset(response.data.keys()) - assert response.data['first_name'] == 'modified_test' - assert response.data['last_name'] == 'modified_test' - assert response.data['email'] == 'modified_test@example.com' + assert response.data["first_name"] == "modified_test" + assert response.data["last_name"] == "modified_test" + assert response.data["email"] == "modified_test@example.com" diff --git a/{{cookiecutter.github_repository}}/tests/integration/test_site_pages.py b/{{cookiecutter.github_repository}}/tests/integration/test_site_pages.py index 22c66555..c103a034 100644 --- a/{{cookiecutter.github_repository}}/tests/integration/test_site_pages.py +++ b/{{cookiecutter.github_repository}}/tests/integration/test_site_pages.py @@ -6,22 +6,19 @@ def test_root_txt_files(client): - files = ['robots.txt', 'humans.txt'] + files = ["robots.txt", "humans.txt"] for filename in files: - url = reverse('root-txt-files', kwargs={'filename': filename}) + url = reverse("root-txt-files", kwargs={"filename": filename}) response = client.get(url) assert response.status_code == 200 - assert response['Content-Type'] == 'text/plain' + assert response["Content-Type"] == "text/plain" def test_landing_pages(client): # Test that these urls are rendered properly and doesn't required authorization - urls = [ - '/about/', - '/', - ] + urls = ["/about/", "/"] for url in urls: response = client.get(url) assert response.status_code == 200 - assert response['Content-Type'] == 'text/html; charset=utf-8' - assert '' in response.content.decode('utf-8') + assert response["Content-Type"] == "text/html; charset=utf-8" + assert "" in response.content.decode("utf-8") diff --git a/{{cookiecutter.github_repository}}/tests/unit/test_api_versioning.py b/{{cookiecutter.github_repository}}/tests/unit/test_api_versioning.py index c6d0c1d4..124f455b 100644 --- a/{{cookiecutter.github_repository}}/tests/unit/test_api_versioning.py +++ b/{{cookiecutter.github_repository}}/tests/unit/test_api_versioning.py @@ -1,4 +1,3 @@ - def test_api_default_and_allowed_versions(settings): - assert settings.REST_FRAMEWORK['DEFAULT_VERSION'] == '1.0' - assert settings.REST_FRAMEWORK['ALLOWED_VERSIONS'] == ['1.0', ] + assert settings.REST_FRAMEWORK["DEFAULT_VERSION"] == "1.0" + assert settings.REST_FRAMEWORK["ALLOWED_VERSIONS"] == ["1.0"] diff --git a/{{cookiecutter.github_repository}}/wsgi.py b/{{cookiecutter.github_repository}}/wsgi.py index 545a431b..fff14614 100644 --- a/{{cookiecutter.github_repository}}/wsgi.py +++ b/{{cookiecutter.github_repository}}/wsgi.py @@ -21,13 +21,13 @@ # Read .env file and set key/value inside it as environement variables # see: http://github.com/theskumar/python-dotenv -load_dotenv(os.path.join(os.path.dirname(__file__), '.env')) +load_dotenv(os.path.join(os.path.dirname(__file__), ".env")) # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks # if running multiple sites in the same mod_wsgi process. To fix this, use # mod_wsgi daemon mode with each site in its own daemon process, or use # os.environ['DJANGO_SETTINGS_MODULE'] = '.settings' -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.production') +os.environ.setdefault('DJANGO_SETTINGS_MODULE', "settings.production") # This application object is used by any WSGI server configured to use this # file. This includes Django's development server, if the WSGI_APPLICATION diff --git a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/api_urls.py b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/api_urls.py index 54c77aaa..c01f488d 100644 --- a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/api_urls.py +++ b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/api_urls.py @@ -10,8 +10,8 @@ singleton_router = SingletonRouter(trailing_slash=False) # Register all the django rest framework viewsets below. -default_router.register('auth', AuthViewSet, basename='auth') -singleton_router.register('me', CurrentUserViewSet, basename='me') +default_router.register("auth", AuthViewSet, basename="auth") +singleton_router.register("me", CurrentUserViewSet, basename="me") # Combine urls from both default and singleton routers and expose as # 'urlpatterns' which django can pick up from this module. diff --git a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/base/api/schemas.py b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/base/api/schemas.py index c3fabf88..b20b4939 100644 --- a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/base/api/schemas.py +++ b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/base/api/schemas.py @@ -7,9 +7,7 @@ title="{{ cookiecutter.project_name }} API", description="{{ cookiecutter.project_description }}", public=True, - permission_classes=[AllowAny] + permission_classes=[AllowAny], ) -swagger_schema_view = get_swagger_view( - title="{{ cookiecutter.project_name }} API Playground" -) +swagger_schema_view = get_swagger_view(title="{{ cookiecutter.project_name }} API Playground") diff --git a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/base/views.py b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/base/views.py index e54a9aa3..612fe699 100644 --- a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/base/views.py +++ b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/base/views.py @@ -13,11 +13,14 @@ def server_error(request, *args, **kwargs): As we don't want to return html response for a json request. """ - if not settings.DEBUG and request.META.get("CONTENT_TYPE", None) == "application/json": + if ( + not settings.DEBUG + and request.META.get("CONTENT_TYPE", None) == "application/json" + ): exc_type, exc_obj, exc_tb = sys.exc_info() response_dict = { "error_type": exc_type.__name__ if exc_type else "ServerError", - "errors": [{"message": "Server application error", }] + "errors": [{"message": "Server application error"}], } return http.JsonResponse(data=response_dict, status=500) diff --git a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/urls.py b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/urls.py index 52111e76..5da44c63 100644 --- a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/urls.py +++ b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/urls.py @@ -16,36 +16,37 @@ from .base import views as base_views from .base.api import schemas as api_schemas -admin.site.site_title = admin.site.site_header = '{{ cookiecutter.project_name }} Administration' +admin.site.site_title = admin.site.site_header = "{{ cookiecutter.project_name }} Administration" handler500 = base_views.server_error # Top Level Pages # ============================================================================== urlpatterns = [ - path('', TemplateView.as_view(template_name='pages/home.html'), name='home'), - path('about/', TemplateView.as_view(template_name='pages/about.html'), name='about'), + path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), + path( + "about/", TemplateView.as_view(template_name="pages/about.html"), name="about" + ), # Your stuff: custom urls go here ] urlpatterns += [ - - re_path(r'^(?P(robots.txt)|(humans.txt))$', - base_views.root_txt_files, name='root-txt-files'), - + re_path( + r"^(?P(robots.txt)|(humans.txt))$", + base_views.root_txt_files, + name="root-txt-files", + ), # Rest API - path('api/', include(api_urls)), - + path("api/", include(api_urls)), # Django Admin - path('{}/'.format(settings.DJANGO_ADMIN_URL), admin.site.urls), - + path("{}/".format(settings.DJANGO_ADMIN_URL), admin.site.urls), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.API_DEBUG: urlpatterns += [ # Browsable API - path('schema/', api_schemas.schema_view, name='schema'), - path('api-playground/', api_schemas.swagger_schema_view, name='api-playground'), - path('api/auth-n/', include('rest_framework.urls', namespace='rest_framework')), + path("schema/", api_schemas.schema_view, name="schema"), + path("api-playground/", api_schemas.swagger_schema_view, name="api-playground"), + path("api/auth-n/", include("rest_framework.urls", namespace="rest_framework")), ] if settings.DEBUG: @@ -53,14 +54,27 @@ from django.urls import get_callable urlpatterns += [ - path('400/', dj_default_views.bad_request, kwargs={'exception': Exception('Bad Request!')}), - path('403/', dj_default_views.permission_denied, kwargs={'exception': Exception('Permission Denied!')}), - path('403_csrf/', get_callable(settings.CSRF_FAILURE_VIEW)), - path('404/', dj_default_views.page_not_found, kwargs={'exception': Exception('Not Found!')}), - path('500/', handler500), + path( + "400/", + dj_default_views.bad_request, + kwargs={"exception": Exception("Bad Request!")}, + ), + path( + "403/", + dj_default_views.permission_denied, + kwargs={"exception": Exception("Permission Denied!")}, + ), + path("403_csrf/", get_callable(settings.CSRF_FAILURE_VIEW)), + path( + "404/", + dj_default_views.page_not_found, + kwargs={"exception": Exception("Not Found!")}, + ), + path("500/", handler500), ] # Django Debug Toolbar - if 'debug_toolbar' in settings.INSTALLED_APPS: + if "debug_toolbar" in settings.INSTALLED_APPS: import debug_toolbar - urlpatterns += [path('__debug__/', include(debug_toolbar.urls))] + + urlpatterns += [path("__debug__/", include(debug_toolbar.urls))] diff --git a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/auth/backends.py b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/auth/backends.py index a5d489b3..e1a46937 100644 --- a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/auth/backends.py +++ b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/auth/backends.py @@ -26,10 +26,8 @@ class UserTokenAuthentication(BaseAuthentication): + """ Self-contained stateles authentication implementation that work similar to OAuth2. - """ - Self-contained stateles authentication implementation - that work similar to OAuth2. It uses json web tokens (https://github.com/jpadilla/pyjwt) for trust data stored in the token. """ diff --git a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/migrations/0001_initial.py b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/migrations/0001_initial.py index 1ebea5b5..f99cd6ed 100644 --- a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/migrations/0001_initial.py +++ b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/migrations/0001_initial.py @@ -14,34 +14,107 @@ class Migration(migrations.Migration): - dependencies = [ - ('auth', '0007_alter_validators_add_error_messages'), - ] + dependencies = [("auth", "0007_alter_validators_add_error_messages")] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('password', models.CharField(max_length=128, verbose_name='password')), - ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), - ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('first_name', models.CharField(blank=True, max_length=120, verbose_name='First Name')), - ('last_name', models.CharField(blank=True, max_length=120, verbose_name='Last Name')), - ('email', models.EmailField(db_index=True, max_length=254, unique=True, verbose_name='email address')), - ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), - ('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')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), - ('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')), - ('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')), + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=120, verbose_name="First Name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=120, verbose_name="Last Name" + ), + ), + ( + "email", + models.EmailField( + db_index=True, + max_length=254, + unique=True, + verbose_name="email address", + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "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", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "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", + ), + ), + ( + "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", + ), + ), ], options={ - 'verbose_name_plural': 'users', - 'verbose_name': 'user', - 'ordering': ('-date_joined',), + "verbose_name_plural": "users", + "verbose_name": "user", + "ordering": ("-date_joined",), }, - managers=[ - ('objects', {{cookiecutter.main_module}}.users.models.UserManager()), - ], - ), + managers=[("objects", {{cookiecutter.main_module}}.users.models.UserManager())], + ) ] diff --git a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/migrations/0002_auto_20171024_1200.py b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/migrations/0002_auto_20171024_1200.py index 799ea9d7..e90ef2de 100644 --- a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/migrations/0002_auto_20171024_1200.py +++ b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/migrations/0002_auto_20171024_1200.py @@ -9,15 +9,15 @@ class Migration(migrations.Migration): - dependencies = [ - ('users', '0001_initial'), - ] + dependencies = [("users", "0001_initial")] operations = [ CITextExtension(), migrations.AlterField( - model_name='user', - name='email', - field=django.contrib.postgres.fields.citext.CIEmailField(db_index=True, max_length=254, unique=True, verbose_name='email address'), + model_name="user", + name="email", + field=django.contrib.postgres.fields.citext.CIEmailField( + db_index=True, max_length=254, unique=True, verbose_name="email address" + ), ), ] diff --git a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/models.py b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/models.py index f8904255..46a363bb 100644 --- a/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/models.py +++ b/{{cookiecutter.github_repository}}/{{cookiecutter.main_module}}/users/models.py @@ -1,5 +1,9 @@ # Third Party Stuff -from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin +from django.contrib.auth.models import ( + AbstractBaseUser, + BaseUserManager, + PermissionsMixin, +) from django.contrib.postgres.fields import CIEmailField from django.db import models from django.utils import timezone @@ -12,17 +16,29 @@ class UserManager(BaseUserManager): use_in_migrations = True - def _create_user(self, email: str, password: str, is_staff: bool, is_superuser: bool, **extra_fields): - """Creates and saves a User with the given email and password. - """ + def _create_user( + self, + email: str, + password: str, + is_staff: bool, + is_superuser: bool, + **extra_fields + ): email = self.normalize_email(email) - user = self.model(email=email, is_staff=is_staff, is_active=True, - is_superuser=is_superuser, **extra_fields) + user = self.model( + email=email, + is_staff=is_staff, + is_active=True, + is_superuser=is_superuser, + **extra_fields + ) user.set_password(password) user.save(using=self._db) return user def create_user(self, email: str, password=None, **extra_fields): + """Creates and saves a User with the given email and password. + """ return self._create_user(email, password, False, False, **extra_fields) def create_superuser(self, email: str, password: str, **extra_fields):