mirror of
				https://github.com/cookiecutter/cookiecutter-django.git
				synced 2025-11-01 00:17:54 +03:00 
			
		
		
		
	Run Black on Travis (#1957)
* Create a test matrix on Travis CI to help testing multiple options * Change test_docker.sh to fail if any command in it fails * Run black on the CI with --check option * Fix formatting of project files using black * Install black in the docker container * Exclude migrations in black checks * Fix Black formatting violations * Run black on the whole generated project & fix issues
This commit is contained in:
		
							parent
							
								
									43a6f5d854
								
							
						
					
					
						commit
						68f7268770
					
				
							
								
								
									
										16
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								.travis.yml
									
									
									
									
									
								
							|  | @ -7,16 +7,20 @@ language: python | |||
| 
 | ||||
| python: 3.6 | ||||
| 
 | ||||
| env: | ||||
|   - TOX_ENV=py36 | ||||
| 
 | ||||
| before_install: | ||||
|   - docker-compose -v | ||||
|   - docker -v | ||||
| 
 | ||||
| script: | ||||
|   - tox -e $TOX_ENV | ||||
|   - sh tests/test_docker.sh | ||||
| matrix: | ||||
|   include: | ||||
|     - name: Test | ||||
|       script: tox -e py36 | ||||
|     - name: Black | ||||
|       script: tox -e black | ||||
|     - name: Basic Docker | ||||
|       script: sh tests/test_docker.sh | ||||
|     - name: Docker with Celery | ||||
|       script: sh tests/test_docker.sh use_celery=y | ||||
| 
 | ||||
| install: | ||||
|   - pip install tox | ||||
|  |  | |||
|  | @ -32,10 +32,7 @@ DEBUG_VALUE = "debug" | |||
| 
 | ||||
| 
 | ||||
| def remove_open_source_files(): | ||||
|     file_names = [ | ||||
|         "CONTRIBUTORS.txt", | ||||
|         "LICENSE", | ||||
|     ] | ||||
|     file_names = ["CONTRIBUTORS.txt", "LICENSE"] | ||||
|     for file_name in file_names: | ||||
|         os.remove(file_name) | ||||
| 
 | ||||
|  | @ -71,7 +68,10 @@ def remove_utility_files(): | |||
| def remove_heroku_files(): | ||||
|     file_names = ["Procfile", "runtime.txt", "requirements.txt"] | ||||
|     for file_name in file_names: | ||||
|         if file_name == "requirements.txt" and "{{ cookiecutter.use_travisci }}".lower() == "y": | ||||
|         if ( | ||||
|             file_name == "requirements.txt" | ||||
|             and "{{ cookiecutter.use_travisci }}".lower() == "y" | ||||
|         ): | ||||
|             # don't remove the file if we are using travisci but not using heroku | ||||
|             continue | ||||
|         os.remove(file_name) | ||||
|  | @ -183,11 +183,7 @@ def generate_postgres_user(debug=False): | |||
| 
 | ||||
| 
 | ||||
| def set_postgres_user(file_path, value): | ||||
|     postgres_user = set_flag( | ||||
|         file_path, | ||||
|         "!!!SET POSTGRES_USER!!!", | ||||
|         value=value, | ||||
|     ) | ||||
|     postgres_user = set_flag(file_path, "!!!SET POSTGRES_USER!!!", value=value) | ||||
|     return postgres_user | ||||
| 
 | ||||
| 
 | ||||
|  | @ -205,9 +201,7 @@ def set_postgres_password(file_path, value=None): | |||
| 
 | ||||
| def set_celery_flower_user(file_path, value): | ||||
|     celery_flower_user = set_flag( | ||||
|         file_path, | ||||
|         "!!!SET CELERY_FLOWER_USER!!!", | ||||
|         value=value, | ||||
|         file_path, "!!!SET CELERY_FLOWER_USER!!!", value=value | ||||
|     ) | ||||
|     return celery_flower_user | ||||
| 
 | ||||
|  | @ -230,11 +224,7 @@ def append_to_gitignore_file(s): | |||
|         gitignore_file.write(os.linesep) | ||||
| 
 | ||||
| 
 | ||||
| def set_flags_in_envs( | ||||
|     postgres_user, | ||||
|     celery_flower_user, | ||||
|     debug=False, | ||||
| ): | ||||
| def set_flags_in_envs(postgres_user, celery_flower_user, debug=False): | ||||
|     local_django_envs_path = os.path.join(".envs", ".local", ".django") | ||||
|     production_django_envs_path = os.path.join(".envs", ".production", ".django") | ||||
|     local_postgres_envs_path = os.path.join(".envs", ".local", ".postgres") | ||||
|  | @ -244,14 +234,22 @@ def set_flags_in_envs( | |||
|     set_django_admin_url(production_django_envs_path) | ||||
| 
 | ||||
|     set_postgres_user(local_postgres_envs_path, value=postgres_user) | ||||
|     set_postgres_password(local_postgres_envs_path, value=DEBUG_VALUE if debug else None) | ||||
|     set_postgres_password( | ||||
|         local_postgres_envs_path, value=DEBUG_VALUE if debug else None | ||||
|     ) | ||||
|     set_postgres_user(production_postgres_envs_path, value=postgres_user) | ||||
|     set_postgres_password(production_postgres_envs_path, value=DEBUG_VALUE if debug else None) | ||||
|     set_postgres_password( | ||||
|         production_postgres_envs_path, value=DEBUG_VALUE if debug else None | ||||
|     ) | ||||
| 
 | ||||
|     set_celery_flower_user(local_django_envs_path, value=celery_flower_user) | ||||
|     set_celery_flower_password(local_django_envs_path, value=DEBUG_VALUE if debug else None) | ||||
|     set_celery_flower_password( | ||||
|         local_django_envs_path, value=DEBUG_VALUE if debug else None | ||||
|     ) | ||||
|     set_celery_flower_user(production_django_envs_path, value=celery_flower_user) | ||||
|     set_celery_flower_password(production_django_envs_path, value=DEBUG_VALUE if debug else None) | ||||
|     set_celery_flower_password( | ||||
|         production_django_envs_path, value=DEBUG_VALUE if debug else None | ||||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| def set_flags_in_settings_files(): | ||||
|  | @ -302,8 +300,8 @@ def main(): | |||
|         if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": | ||||
|             print( | ||||
|                 INFO + ".env(s) are only utilized when Docker Compose and/or " | ||||
|                        "Heroku support is enabled so keeping them does not " | ||||
|                        "make sense given your current setup." + TERMINATOR | ||||
|                 "Heroku support is enabled so keeping them does not " | ||||
|                 "make sense given your current setup." + TERMINATOR | ||||
|             ) | ||||
|         remove_envs_and_associated_files() | ||||
|     else: | ||||
|  | @ -325,10 +323,10 @@ def main(): | |||
|                 "{{ cookiecutter.js_task_runner }}".lower().capitalize() | ||||
|             ) | ||||
|             + "working together not supported yet. " | ||||
|               "You can continue using the generated project like you " | ||||
|               "normally would, however you would need to add a JS " | ||||
|               "task runner service to your Docker Compose configuration " | ||||
|               "manually." + TERMINATOR | ||||
|             "You can continue using the generated project like you " | ||||
|             "normally would, however you would need to add a JS " | ||||
|             "task runner service to your Docker Compose configuration " | ||||
|             "manually." + TERMINATOR | ||||
|         ) | ||||
| 
 | ||||
|     if "{{ cookiecutter.use_celery }}".lower() == "n": | ||||
|  |  | |||
|  | @ -18,11 +18,13 @@ SUCCESS = "\x1b[1;32m [SUCCESS]: " | |||
| 
 | ||||
| project_slug = "{{ cookiecutter.project_slug }}" | ||||
| if hasattr(project_slug, "isidentifier"): | ||||
|     assert project_slug.isidentifier(), "'{}' project slug is not a valid Python identifier.".format( | ||||
|         project_slug | ||||
|     ) | ||||
|     assert ( | ||||
|         project_slug.isidentifier() | ||||
|     ), "'{}' project slug is not a valid Python identifier.".format(project_slug) | ||||
| 
 | ||||
| assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." | ||||
| assert ( | ||||
|     "\\" not in "{{ cookiecutter.author_name }}" | ||||
| ), "Don't include backslashes in author name." | ||||
| 
 | ||||
| if "{{ cookiecutter.use_docker }}".lower() == "n": | ||||
|     python_major_version = sys.version_info[0] | ||||
|  |  | |||
|  | @ -97,8 +97,8 @@ def test_travis_invokes_pytest(cookies, context): | |||
|     assert result.project.basename == context["project_slug"] | ||||
|     assert result.project.isdir() | ||||
| 
 | ||||
|     with open(f'{result.project}/.travis.yml', 'r') as travis_yml: | ||||
|     with open(f"{result.project}/.travis.yml", "r") as travis_yml: | ||||
|         try: | ||||
|             assert yaml.load(travis_yml)['script'] == ['pytest'] | ||||
|             assert yaml.load(travis_yml)["script"] == ["pytest"] | ||||
|         except yaml.YAMLError as e: | ||||
|             pytest.fail(e) | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ | |||
| # it is meant to be run from the root directory of the repository, eg: | ||||
| # sh tests/test_docker.sh | ||||
| 
 | ||||
| set -o errexit | ||||
| 
 | ||||
| # install test requirements | ||||
| pip install -r requirements.txt | ||||
| 
 | ||||
|  | @ -11,12 +13,15 @@ mkdir -p .cache/docker | |||
| cd .cache/docker | ||||
| 
 | ||||
| # create the project using the default settings in cookiecutter.json | ||||
| cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y | ||||
| cookiecutter ../../ --no-input --overwrite-if-exists use_docker=y $@ | ||||
| cd my_awesome_project | ||||
| 
 | ||||
| # run the project's type checks | ||||
| docker-compose -f local.yml run django mypy my_awesome_project | ||||
| 
 | ||||
| # Run black with --check option | ||||
| docker-compose -f local.yml run django black --check --diff  --exclude 'migrations' ./ | ||||
| 
 | ||||
| # run the project's tests | ||||
| docker-compose -f local.yml run django pytest | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										6
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								tox.ini
									
									
									
									
									
								
							|  | @ -1,7 +1,11 @@ | |||
| [tox] | ||||
| skipsdist = true | ||||
| envlist = py36 | ||||
| envlist = py36,black | ||||
| 
 | ||||
| [testenv] | ||||
| deps = -rrequirements.txt | ||||
| commands = pytest {posargs:./tests} | ||||
| 
 | ||||
| [testenv:black] | ||||
| deps = black | ||||
| commands = black --check hooks tests setup.py docs | ||||
|  |  | |||
|  | @ -4,27 +4,29 @@ Base settings to build other settings files upon. | |||
| 
 | ||||
| import environ | ||||
| 
 | ||||
| ROOT_DIR = environ.Path(__file__) - 3  # ({{ cookiecutter.project_slug }}/config/settings/base.py - 3 = {{ cookiecutter.project_slug }}/) | ||||
| APPS_DIR = ROOT_DIR.path('{{ cookiecutter.project_slug }}') | ||||
| ROOT_DIR = ( | ||||
|     environ.Path(__file__) - 3 | ||||
| )  # ({{ cookiecutter.project_slug }}/config/settings/base.py - 3 = {{ cookiecutter.project_slug }}/) | ||||
| APPS_DIR = ROOT_DIR.path("{{ cookiecutter.project_slug }}") | ||||
| 
 | ||||
| env = environ.Env() | ||||
| 
 | ||||
| READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=False) | ||||
| READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False) | ||||
| if READ_DOT_ENV_FILE: | ||||
|     # OS environment variables take precedence over variables from .env | ||||
|     env.read_env(str(ROOT_DIR.path('.env'))) | ||||
|     env.read_env(str(ROOT_DIR.path(".env"))) | ||||
| 
 | ||||
| # GENERAL | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#debug | ||||
| DEBUG = env.bool('DJANGO_DEBUG', False) | ||||
| DEBUG = env.bool("DJANGO_DEBUG", False) | ||||
| # Local time zone. Choices are | ||||
| # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name | ||||
| # though not all of them may be available with every OS. | ||||
| # In Windows, this must be set to your system time zone. | ||||
| TIME_ZONE = '{{ cookiecutter.timezone }}' | ||||
| TIME_ZONE = "{{ cookiecutter.timezone }}" | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#language-code | ||||
| LANGUAGE_CODE = 'en-us' | ||||
| LANGUAGE_CODE = "en-us" | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#site-id | ||||
| SITE_ID = 1 | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n | ||||
|  | @ -37,45 +39,45 @@ USE_TZ = True | |||
| # DATABASES | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#databases | ||||
| {% if cookiecutter.use_docker == 'y' -%} | ||||
| DATABASES = { | ||||
|     'default': env.db('DATABASE_URL'), | ||||
| } | ||||
| {% if cookiecutter.use_docker == "y" -%} | ||||
| DATABASES = {"default": env.db("DATABASE_URL")} | ||||
| {%- else %} | ||||
| DATABASES = { | ||||
|     'default': env.db('DATABASE_URL', default='postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}'), | ||||
|     "default": env.db( | ||||
|         "DATABASE_URL", default="postgres://{% if cookiecutter.windows == 'y' %}localhost{% endif %}/{{cookiecutter.project_slug}}" | ||||
|     ), | ||||
| } | ||||
| {%- endif %} | ||||
| DATABASES['default']['ATOMIC_REQUESTS'] = True | ||||
| DATABASES["default"]["ATOMIC_REQUESTS"] = True | ||||
| 
 | ||||
| # URLS | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf | ||||
| ROOT_URLCONF = 'config.urls' | ||||
| ROOT_URLCONF = "config.urls" | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application | ||||
| WSGI_APPLICATION = 'config.wsgi.application' | ||||
| WSGI_APPLICATION = "config.wsgi.application" | ||||
| 
 | ||||
| # APPS | ||||
| # ------------------------------------------------------------------------------ | ||||
| DJANGO_APPS = [ | ||||
|     'django.contrib.auth', | ||||
|     'django.contrib.contenttypes', | ||||
|     'django.contrib.sessions', | ||||
|     'django.contrib.sites', | ||||
|     'django.contrib.messages', | ||||
|     'django.contrib.staticfiles', | ||||
|     # 'django.contrib.humanize', # Handy template tags | ||||
|     'django.contrib.admin', | ||||
|     "django.contrib.auth", | ||||
|     "django.contrib.contenttypes", | ||||
|     "django.contrib.sessions", | ||||
|     "django.contrib.sites", | ||||
|     "django.contrib.messages", | ||||
|     "django.contrib.staticfiles", | ||||
|     # "django.contrib.humanize", # Handy template tags | ||||
|     "django.contrib.admin", | ||||
| ] | ||||
| THIRD_PARTY_APPS = [ | ||||
|     'crispy_forms', | ||||
|     'allauth', | ||||
|     'allauth.account', | ||||
|     'allauth.socialaccount', | ||||
|     'rest_framework', | ||||
|     "crispy_forms", | ||||
|     "allauth", | ||||
|     "allauth.account", | ||||
|     "allauth.socialaccount", | ||||
|     "rest_framework", | ||||
| ] | ||||
| LOCAL_APPS = [ | ||||
|     '{{ cookiecutter.project_slug }}.users.apps.UsersAppConfig', | ||||
|     "{{ cookiecutter.project_slug }}.users.apps.UsersAppConfig", | ||||
|     # Your stuff: custom apps go here | ||||
| ] | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps | ||||
|  | @ -84,86 +86,76 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS | |||
| # MIGRATIONS | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#migration-modules | ||||
| MIGRATION_MODULES = { | ||||
|     'sites': '{{ cookiecutter.project_slug }}.contrib.sites.migrations' | ||||
| } | ||||
| MIGRATION_MODULES = {"sites": "{{ cookiecutter.project_slug }}.contrib.sites.migrations"} | ||||
| 
 | ||||
| # AUTHENTICATION | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#authentication-backends | ||||
| AUTHENTICATION_BACKENDS = [ | ||||
|     'django.contrib.auth.backends.ModelBackend', | ||||
|     'allauth.account.auth_backends.AuthenticationBackend', | ||||
|     "django.contrib.auth.backends.ModelBackend", | ||||
|     "allauth.account.auth_backends.AuthenticationBackend", | ||||
| ] | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#auth-user-model | ||||
| AUTH_USER_MODEL = 'users.User' | ||||
| AUTH_USER_MODEL = "users.User" | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url | ||||
| LOGIN_REDIRECT_URL = 'users:redirect' | ||||
| LOGIN_REDIRECT_URL = "users:redirect" | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#login-url | ||||
| LOGIN_URL = 'account_login' | ||||
| LOGIN_URL = "account_login" | ||||
| 
 | ||||
| # PASSWORDS | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers | ||||
| PASSWORD_HASHERS = [ | ||||
|     # https://docs.djangoproject.com/en/dev/topics/auth/passwords/#using-argon2-with-django | ||||
|     '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", | ||||
| ] | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators | ||||
| AUTH_PASSWORD_VALIDATORS = [ | ||||
|     { | ||||
|         'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', | ||||
|     }, | ||||
|     { | ||||
|         'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', | ||||
|     }, | ||||
|     { | ||||
|         '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"}, | ||||
|     {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, | ||||
|     {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, | ||||
| ] | ||||
| 
 | ||||
| # MIDDLEWARE | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#middleware | ||||
| MIDDLEWARE = [ | ||||
|     'django.middleware.security.SecurityMiddleware', | ||||
|     'django.contrib.sessions.middleware.SessionMiddleware', | ||||
|     'django.middleware.common.CommonMiddleware', | ||||
|     'django.middleware.csrf.CsrfViewMiddleware', | ||||
|     'django.contrib.auth.middleware.AuthenticationMiddleware', | ||||
|     'django.contrib.messages.middleware.MessageMiddleware', | ||||
|     'django.middleware.clickjacking.XFrameOptionsMiddleware', | ||||
|     "django.middleware.security.SecurityMiddleware", | ||||
|     "django.contrib.sessions.middleware.SessionMiddleware", | ||||
|     "django.middleware.common.CommonMiddleware", | ||||
|     "django.middleware.csrf.CsrfViewMiddleware", | ||||
|     "django.contrib.auth.middleware.AuthenticationMiddleware", | ||||
|     "django.contrib.messages.middleware.MessageMiddleware", | ||||
|     "django.middleware.clickjacking.XFrameOptionsMiddleware", | ||||
| ] | ||||
| 
 | ||||
| # STATIC | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#static-root | ||||
| STATIC_ROOT = str(ROOT_DIR('staticfiles')) | ||||
| STATIC_ROOT = str(ROOT_DIR("staticfiles")) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#static-url | ||||
| STATIC_URL = '/static/' | ||||
| STATIC_URL = "/static/" | ||||
| # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS | ||||
| STATICFILES_DIRS = [ | ||||
|     str(APPS_DIR.path('static')), | ||||
| ] | ||||
| STATICFILES_DIRS = [str(APPS_DIR.path("static"))] | ||||
| # https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders | ||||
| STATICFILES_FINDERS = [ | ||||
|     'django.contrib.staticfiles.finders.FileSystemFinder', | ||||
|     'django.contrib.staticfiles.finders.AppDirectoriesFinder', | ||||
|     "django.contrib.staticfiles.finders.FileSystemFinder", | ||||
|     "django.contrib.staticfiles.finders.AppDirectoriesFinder", | ||||
| ] | ||||
| 
 | ||||
| # MEDIA | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#media-root | ||||
| MEDIA_ROOT = str(APPS_DIR('media')) | ||||
| MEDIA_ROOT = str(APPS_DIR("media")) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#media-url | ||||
| MEDIA_URL = '/media/' | ||||
| MEDIA_URL = "/media/" | ||||
| 
 | ||||
| # TEMPLATES | ||||
| # ------------------------------------------------------------------------------ | ||||
|  | @ -171,43 +163,39 @@ MEDIA_URL = '/media/' | |||
| TEMPLATES = [ | ||||
|     { | ||||
|         # https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND | ||||
|         'BACKEND': 'django.template.backends.django.DjangoTemplates', | ||||
|         "BACKEND": "django.template.backends.django.DjangoTemplates", | ||||
|         # https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs | ||||
|         'DIRS': [ | ||||
|             str(APPS_DIR.path('templates')), | ||||
|         ], | ||||
|         'OPTIONS': { | ||||
|         "DIRS": [str(APPS_DIR.path("templates"))], | ||||
|         "OPTIONS": { | ||||
|             # https://docs.djangoproject.com/en/dev/ref/settings/#template-debug | ||||
|             'debug': DEBUG, | ||||
|             "debug": DEBUG, | ||||
|             # https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders | ||||
|             # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types | ||||
|             'loaders': [ | ||||
|                 'django.template.loaders.filesystem.Loader', | ||||
|                 'django.template.loaders.app_directories.Loader', | ||||
|             "loaders": [ | ||||
|                 "django.template.loaders.filesystem.Loader", | ||||
|                 "django.template.loaders.app_directories.Loader", | ||||
|             ], | ||||
|             # https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors | ||||
|             'context_processors': [ | ||||
|                 'django.template.context_processors.debug', | ||||
|                 'django.template.context_processors.request', | ||||
|                 'django.contrib.auth.context_processors.auth', | ||||
|                 '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', | ||||
|             "context_processors": [ | ||||
|                 "django.template.context_processors.debug", | ||||
|                 "django.template.context_processors.request", | ||||
|                 "django.contrib.auth.context_processors.auth", | ||||
|                 "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", | ||||
|             ], | ||||
|         }, | ||||
|     }, | ||||
|     } | ||||
| ] | ||||
| # http://django-crispy-forms.readthedocs.io/en/latest/install.html#template-packs | ||||
| CRISPY_TEMPLATE_PACK = 'bootstrap4' | ||||
| CRISPY_TEMPLATE_PACK = "bootstrap4" | ||||
| 
 | ||||
| # FIXTURES | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#fixture-dirs | ||||
| FIXTURE_DIRS = ( | ||||
|     str(APPS_DIR.path('fixtures')), | ||||
| ) | ||||
| FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),) | ||||
| 
 | ||||
| # SECURITY | ||||
| # ------------------------------------------------------------------------------ | ||||
|  | @ -218,41 +206,41 @@ CSRF_COOKIE_HTTPONLY = True | |||
| # https://docs.djangoproject.com/en/dev/ref/settings/#secure-browser-xss-filter | ||||
| SECURE_BROWSER_XSS_FILTER = True | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#x-frame-options | ||||
| X_FRAME_OPTIONS = 'DENY' | ||||
| X_FRAME_OPTIONS = "DENY" | ||||
| 
 | ||||
| # EMAIL | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend | ||||
| EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend') | ||||
| EMAIL_BACKEND = env( | ||||
|     "DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.smtp.EmailBackend" | ||||
| ) | ||||
| 
 | ||||
| # ADMIN | ||||
| # ------------------------------------------------------------------------------ | ||||
| # Django Admin URL. | ||||
| ADMIN_URL = 'admin/' | ||||
| ADMIN_URL = "admin/" | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#admins | ||||
| ADMINS = [ | ||||
|     ("""{{cookiecutter.author_name}}""", '{{cookiecutter.email}}'), | ||||
| ] | ||||
| ADMINS = [("""{{cookiecutter.author_name}}""", "{{cookiecutter.email}}")] | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#managers | ||||
| MANAGERS = ADMINS | ||||
| 
 | ||||
| {% if cookiecutter.use_celery == 'y' -%} | ||||
| # Celery | ||||
| # ------------------------------------------------------------------------------ | ||||
| INSTALLED_APPS += ['{{cookiecutter.project_slug}}.taskapp.celery.CeleryAppConfig'] | ||||
| INSTALLED_APPS += ["{{cookiecutter.project_slug}}.taskapp.celery.CeleryAppConfig"] | ||||
| if USE_TZ: | ||||
|     # http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-timezone | ||||
|     CELERY_TIMEZONE = TIME_ZONE | ||||
| # http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-broker_url | ||||
| CELERY_BROKER_URL = env('CELERY_BROKER_URL') | ||||
| CELERY_BROKER_URL = env("CELERY_BROKER_URL") | ||||
| # http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_backend | ||||
| CELERY_RESULT_BACKEND = CELERY_BROKER_URL | ||||
| # http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-accept_content | ||||
| CELERY_ACCEPT_CONTENT = ['json'] | ||||
| CELERY_ACCEPT_CONTENT = ["json"] | ||||
| # http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-task_serializer | ||||
| CELERY_TASK_SERIALIZER = 'json' | ||||
| CELERY_TASK_SERIALIZER = "json" | ||||
| # http://docs.celeryproject.org/en/latest/userguide/configuration.html#std:setting-result_serializer | ||||
| CELERY_RESULT_SERIALIZER = 'json' | ||||
| CELERY_RESULT_SERIALIZER = "json" | ||||
| # http://docs.celeryproject.org/en/latest/userguide/configuration.html#task-time-limit | ||||
| # TODO: set to whatever value is adequate in your circumstances | ||||
| CELERYD_TASK_TIME_LIMIT = 5 * 60 | ||||
|  | @ -263,24 +251,24 @@ CELERYD_TASK_SOFT_TIME_LIMIT = 60 | |||
| {%- endif %} | ||||
| # django-allauth | ||||
| # ------------------------------------------------------------------------------ | ||||
| ACCOUNT_ALLOW_REGISTRATION = env.bool('DJANGO_ACCOUNT_ALLOW_REGISTRATION', True) | ||||
| ACCOUNT_ALLOW_REGISTRATION = env.bool("DJANGO_ACCOUNT_ALLOW_REGISTRATION", True) | ||||
| # https://django-allauth.readthedocs.io/en/latest/configuration.html | ||||
| ACCOUNT_AUTHENTICATION_METHOD = 'username' | ||||
| ACCOUNT_AUTHENTICATION_METHOD = "username" | ||||
| # https://django-allauth.readthedocs.io/en/latest/configuration.html | ||||
| ACCOUNT_EMAIL_REQUIRED = True | ||||
| # https://django-allauth.readthedocs.io/en/latest/configuration.html | ||||
| ACCOUNT_EMAIL_VERIFICATION = 'mandatory' | ||||
| ACCOUNT_EMAIL_VERIFICATION = "mandatory" | ||||
| # https://django-allauth.readthedocs.io/en/latest/configuration.html | ||||
| ACCOUNT_ADAPTER = '{{cookiecutter.project_slug}}.users.adapters.AccountAdapter' | ||||
| ACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.AccountAdapter" | ||||
| # https://django-allauth.readthedocs.io/en/latest/configuration.html | ||||
| SOCIALACCOUNT_ADAPTER = '{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter' | ||||
| SOCIALACCOUNT_ADAPTER = "{{cookiecutter.project_slug}}.users.adapters.SocialAccountAdapter" | ||||
| 
 | ||||
| {% if cookiecutter.use_compressor == 'y' -%} | ||||
| # django-compressor | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://django-compressor.readthedocs.io/en/latest/quickstart/#installation | ||||
| INSTALLED_APPS += ['compressor'] | ||||
| STATICFILES_FINDERS += ['compressor.finders.CompressorFinder'] | ||||
| INSTALLED_APPS += ["compressor"] | ||||
| STATICFILES_FINDERS += ["compressor.finders.CompressorFinder"] | ||||
| 
 | ||||
| {%- endif %} | ||||
| # Your stuff... | ||||
|  |  | |||
|  | @ -6,42 +6,43 @@ from .base import env | |||
| # https://docs.djangoproject.com/en/dev/ref/settings/#debug | ||||
| DEBUG = True | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key | ||||
| SECRET_KEY = env('DJANGO_SECRET_KEY', default='!!!SET DJANGO_SECRET_KEY!!!') | ||||
| SECRET_KEY = env( | ||||
|     "DJANGO_SECRET_KEY", | ||||
|     default="!!!SET DJANGO_SECRET_KEY!!!", | ||||
| ) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts | ||||
| ALLOWED_HOSTS = [ | ||||
|     "localhost", | ||||
|     "0.0.0.0", | ||||
|     "127.0.0.1", | ||||
| ] | ||||
| ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"] | ||||
| 
 | ||||
| # CACHES | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#caches | ||||
| CACHES = { | ||||
|     'default': { | ||||
|         'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', | ||||
|         'LOCATION': '' | ||||
|     "default": { | ||||
|         "BACKEND": "django.core.cache.backends.locmem.LocMemCache", | ||||
|         "LOCATION": "", | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| # TEMPLATES | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#templates | ||||
| TEMPLATES[0]['OPTIONS']['debug'] = DEBUG  # noqa F405 | ||||
| TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG  # noqa F405 | ||||
| 
 | ||||
| # EMAIL | ||||
| # ------------------------------------------------------------------------------ | ||||
| {% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'y' -%} | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#email-host | ||||
| EMAIL_HOST = env('EMAIL_HOST', default='mailhog') | ||||
| EMAIL_HOST = env("EMAIL_HOST", default="mailhog") | ||||
| {%- elif cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' -%} | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#email-host | ||||
| EMAIL_HOST = 'localhost' | ||||
| EMAIL_HOST = "localhost" | ||||
| {%- else -%} | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#email-backend | ||||
| 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" | ||||
| ) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#email-host | ||||
| EMAIL_HOST = 'localhost' | ||||
| EMAIL_HOST = "localhost" | ||||
| {%- endif %} | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#email-port | ||||
| EMAIL_PORT = 1025 | ||||
|  | @ -49,29 +50,28 @@ EMAIL_PORT = 1025 | |||
| # django-debug-toolbar | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#prerequisites | ||||
| INSTALLED_APPS += ['debug_toolbar']  # noqa F405 | ||||
| INSTALLED_APPS += ["debug_toolbar"]  # noqa F405 | ||||
| # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#middleware | ||||
| MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']  # noqa F405 | ||||
| MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"]  # noqa F405 | ||||
| # https://django-debug-toolbar.readthedocs.io/en/latest/configuration.html#debug-toolbar-config | ||||
| 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, | ||||
| } | ||||
| # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips | ||||
| INTERNAL_IPS = ['127.0.0.1', '10.0.2.2'] | ||||
| INTERNAL_IPS = ["127.0.0.1", "10.0.2.2"] | ||||
| {% if cookiecutter.use_docker == 'y' -%} | ||||
| if env('USE_DOCKER') == 'yes': | ||||
| if env("USE_DOCKER") == "yes": | ||||
|     import socket | ||||
| 
 | ||||
|     hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) | ||||
|     INTERNAL_IPS += [ip[:-1] + '1' for ip in ips] | ||||
|     INTERNAL_IPS += [ip[:-1] + "1" for ip in ips] | ||||
| {%- endif %} | ||||
| 
 | ||||
| # django-extensions | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://django-extensions.readthedocs.io/en/latest/installation_instructions.html#configuration | ||||
| INSTALLED_APPS += ['django_extensions']  # noqa F405 | ||||
| INSTALLED_APPS += ["django_extensions"]  # noqa F405 | ||||
| {% if cookiecutter.use_celery == 'y' -%} | ||||
| 
 | ||||
| # Celery | ||||
|  |  | |||
|  | @ -8,37 +8,37 @@ from .base import env | |||
| # GENERAL | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key | ||||
| SECRET_KEY = env('DJANGO_SECRET_KEY') | ||||
| SECRET_KEY = env("DJANGO_SECRET_KEY") | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts | ||||
| ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['{{ cookiecutter.domain_name }}']) | ||||
| ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS", default=["{{ cookiecutter.domain_name }}"]) | ||||
| 
 | ||||
| # DATABASES | ||||
| # ------------------------------------------------------------------------------ | ||||
| DATABASES['default'] = env.db('DATABASE_URL')  # noqa F405 | ||||
| DATABASES['default']['ATOMIC_REQUESTS'] = True  # noqa F405 | ||||
| DATABASES['default']['CONN_MAX_AGE'] = env.int('CONN_MAX_AGE', default=60)  # noqa F405 | ||||
| DATABASES["default"] = env.db("DATABASE_URL")  # noqa F405 | ||||
| DATABASES["default"]["ATOMIC_REQUESTS"] = True  # noqa F405 | ||||
| DATABASES["default"]["CONN_MAX_AGE"] = env.int("CONN_MAX_AGE", default=60)  # noqa F405 | ||||
| 
 | ||||
| # CACHES | ||||
| # ------------------------------------------------------------------------------ | ||||
| CACHES = { | ||||
|     'default': { | ||||
|         'BACKEND': 'django_redis.cache.RedisCache', | ||||
|         'LOCATION': env('REDIS_URL'), | ||||
|         'OPTIONS': { | ||||
|             'CLIENT_CLASS': 'django_redis.client.DefaultClient', | ||||
|     "default": { | ||||
|         "BACKEND": "django_redis.cache.RedisCache", | ||||
|         "LOCATION": env("REDIS_URL"), | ||||
|         "OPTIONS": { | ||||
|             "CLIENT_CLASS": "django_redis.client.DefaultClient", | ||||
|             # Mimicing memcache behavior. | ||||
|             # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior | ||||
|             'IGNORE_EXCEPTIONS': True, | ||||
|         } | ||||
|             "IGNORE_EXCEPTIONS": True, | ||||
|         }, | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| # SECURITY | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header | ||||
| SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') | ||||
| SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#secure-ssl-redirect | ||||
| SECURE_SSL_REDIRECT = env.bool('DJANGO_SECURE_SSL_REDIRECT', default=True) | ||||
| SECURE_SSL_REDIRECT = env.bool("DJANGO_SECURE_SSL_REDIRECT", default=True) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#session-cookie-secure | ||||
| SESSION_COOKIE_SECURE = True | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#csrf-cookie-secure | ||||
|  | @ -48,45 +48,49 @@ CSRF_COOKIE_SECURE = True | |||
| # TODO: set this to 60 seconds first and then to 518400 once you prove the former works | ||||
| SECURE_HSTS_SECONDS = 60 | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-include-subdomains | ||||
| SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool('DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS', default=True) | ||||
| SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool( | ||||
|     "DJANGO_SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True | ||||
| ) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#secure-hsts-preload | ||||
| SECURE_HSTS_PRELOAD = env.bool('DJANGO_SECURE_HSTS_PRELOAD', default=True) | ||||
| SECURE_HSTS_PRELOAD = env.bool("DJANGO_SECURE_HSTS_PRELOAD", default=True) | ||||
| # https://docs.djangoproject.com/en/dev/ref/middleware/#x-content-type-options-nosniff | ||||
| SECURE_CONTENT_TYPE_NOSNIFF = env.bool('DJANGO_SECURE_CONTENT_TYPE_NOSNIFF', default=True) | ||||
| SECURE_CONTENT_TYPE_NOSNIFF = env.bool( | ||||
|     "DJANGO_SECURE_CONTENT_TYPE_NOSNIFF", default=True | ||||
| ) | ||||
| 
 | ||||
| # STORAGES | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://django-storages.readthedocs.io/en/latest/#installation | ||||
| INSTALLED_APPS += ['storages']  # noqa F405 | ||||
| INSTALLED_APPS += ["storages"]  # noqa F405 | ||||
| # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings | ||||
| AWS_ACCESS_KEY_ID = env('DJANGO_AWS_ACCESS_KEY_ID') | ||||
| AWS_ACCESS_KEY_ID = env("DJANGO_AWS_ACCESS_KEY_ID") | ||||
| # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings | ||||
| AWS_SECRET_ACCESS_KEY = env('DJANGO_AWS_SECRET_ACCESS_KEY') | ||||
| AWS_SECRET_ACCESS_KEY = env("DJANGO_AWS_SECRET_ACCESS_KEY") | ||||
| # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings | ||||
| AWS_STORAGE_BUCKET_NAME = env('DJANGO_AWS_STORAGE_BUCKET_NAME') | ||||
| AWS_STORAGE_BUCKET_NAME = env("DJANGO_AWS_STORAGE_BUCKET_NAME") | ||||
| # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings | ||||
| AWS_QUERYSTRING_AUTH = False | ||||
| # DO NOT change these unless you know what you're doing. | ||||
| _AWS_EXPIRY = 60 * 60 * 24 * 7 | ||||
| # https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#settings | ||||
| AWS_S3_OBJECT_PARAMETERS = { | ||||
|     'CacheControl': f'max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate', | ||||
|     "CacheControl": f"max-age={_AWS_EXPIRY}, s-maxage={_AWS_EXPIRY}, must-revalidate" | ||||
| } | ||||
| 
 | ||||
| # STATIC | ||||
| # ------------------------ | ||||
| {% if cookiecutter.use_whitenoise == 'y' -%} | ||||
| STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' | ||||
| STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" | ||||
| {%- else %} | ||||
| STATICFILES_STORAGE = 'config.settings.production.StaticRootS3Boto3Storage' | ||||
| STATIC_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/' | ||||
| STATICFILES_STORAGE = "config.settings.production.StaticRootS3Boto3Storage" | ||||
| STATIC_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/static/" | ||||
| {%- endif %} | ||||
| 
 | ||||
| # MEDIA | ||||
| # ------------------------------------------------------------------------------ | ||||
| {% if cookiecutter.use_whitenoise == 'y' -%} | ||||
| DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' | ||||
| MEDIA_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/' | ||||
| DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" | ||||
| MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/" | ||||
| {%- else %} | ||||
| # region http://stackoverflow.com/questions/10390244/ | ||||
| # Full-fledge class: https://stackoverflow.com/a/18046120/104731 | ||||
|  | @ -94,78 +98,79 @@ from storages.backends.s3boto3 import S3Boto3Storage  # noqa E402 | |||
| 
 | ||||
| 
 | ||||
| class StaticRootS3Boto3Storage(S3Boto3Storage): | ||||
|     location = 'static' | ||||
|     location = "static" | ||||
| 
 | ||||
| 
 | ||||
| class MediaRootS3Boto3Storage(S3Boto3Storage): | ||||
|     location = 'media' | ||||
|     location = "media" | ||||
|     file_overwrite = False | ||||
| 
 | ||||
| 
 | ||||
| # endregion | ||||
| DEFAULT_FILE_STORAGE = 'config.settings.production.MediaRootS3Boto3Storage' | ||||
| MEDIA_URL = f'https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/' | ||||
| DEFAULT_FILE_STORAGE = "config.settings.production.MediaRootS3Boto3Storage" | ||||
| MEDIA_URL = f"https://{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com/media/" | ||||
| {%- endif %} | ||||
| 
 | ||||
| # TEMPLATES | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#templates | ||||
| TEMPLATES[0]['OPTIONS']['loaders'] = [  # noqa F405 | ||||
| TEMPLATES[0]["OPTIONS"]["loaders"] = [  # noqa F405 | ||||
|     ( | ||||
|         'django.template.loaders.cached.Loader', | ||||
|         "django.template.loaders.cached.Loader", | ||||
|         [ | ||||
|             'django.template.loaders.filesystem.Loader', | ||||
|             'django.template.loaders.app_directories.Loader', | ||||
|         ] | ||||
|     ), | ||||
|             "django.template.loaders.filesystem.Loader", | ||||
|             "django.template.loaders.app_directories.Loader", | ||||
|         ], | ||||
|     ) | ||||
| ] | ||||
| 
 | ||||
| # EMAIL | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#default-from-email | ||||
| DEFAULT_FROM_EMAIL = env( | ||||
|     'DJANGO_DEFAULT_FROM_EMAIL', | ||||
|     default='{{cookiecutter.project_name}} <noreply@{{cookiecutter.domain_name}}>' | ||||
|     "DJANGO_DEFAULT_FROM_EMAIL", default="{{cookiecutter.project_name}} <noreply@{{cookiecutter.domain_name}}>" | ||||
| ) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#server-email | ||||
| SERVER_EMAIL = env('DJANGO_SERVER_EMAIL', default=DEFAULT_FROM_EMAIL) | ||||
| SERVER_EMAIL = env("DJANGO_SERVER_EMAIL", default=DEFAULT_FROM_EMAIL) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#email-subject-prefix | ||||
| EMAIL_SUBJECT_PREFIX = env('DJANGO_EMAIL_SUBJECT_PREFIX', default='[{{cookiecutter.project_name}}]') | ||||
| EMAIL_SUBJECT_PREFIX = env( | ||||
|     "DJANGO_EMAIL_SUBJECT_PREFIX", default="[{{cookiecutter.project_name}}]" | ||||
| ) | ||||
| 
 | ||||
| # ADMIN | ||||
| # ------------------------------------------------------------------------------ | ||||
| # Django Admin URL regex. | ||||
| ADMIN_URL = env('DJANGO_ADMIN_URL') | ||||
| ADMIN_URL = env("DJANGO_ADMIN_URL") | ||||
| 
 | ||||
| # Anymail (Mailgun) | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://anymail.readthedocs.io/en/stable/installation/#installing-anymail | ||||
| INSTALLED_APPS += ['anymail']  # noqa F405 | ||||
| EMAIL_BACKEND = 'anymail.backends.mailgun.EmailBackend' | ||||
| INSTALLED_APPS += ["anymail"]  # noqa F405 | ||||
| EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend" | ||||
| # https://anymail.readthedocs.io/en/stable/installation/#anymail-settings-reference | ||||
| ANYMAIL = { | ||||
|     'MAILGUN_API_KEY': env('MAILGUN_API_KEY'), | ||||
|     'MAILGUN_SENDER_DOMAIN': env('MAILGUN_DOMAIN') | ||||
|     "MAILGUN_API_KEY": env("MAILGUN_API_KEY"), | ||||
|     "MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN"), | ||||
| } | ||||
| 
 | ||||
| # Gunicorn | ||||
| # ------------------------------------------------------------------------------ | ||||
| INSTALLED_APPS += ['gunicorn']  # noqa F405 | ||||
| INSTALLED_APPS += ["gunicorn"]  # noqa F405 | ||||
| 
 | ||||
| {% if cookiecutter.use_whitenoise == 'y' -%} | ||||
| # WhiteNoise | ||||
| # ------------------------------------------------------------------------------ | ||||
| # http://whitenoise.evans.io/en/latest/django.html#enable-whitenoise | ||||
| MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')  # noqa F405 | ||||
| MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware")  # noqa F405 | ||||
| 
 | ||||
| {% endif %} | ||||
| {%- if cookiecutter.use_compressor == 'y' -%} | ||||
| # django-compressor | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_ENABLED | ||||
| COMPRESS_ENABLED = env.bool('COMPRESS_ENABLED', default=True) | ||||
| COMPRESS_ENABLED = env.bool("COMPRESS_ENABLED", default=True) | ||||
| # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_STORAGE | ||||
| COMPRESS_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' | ||||
| COMPRESS_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" | ||||
| # https://django-compressor.readthedocs.io/en/latest/settings/#django.conf.settings.COMPRESS_URL | ||||
| COMPRESS_URL = STATIC_URL | ||||
| 
 | ||||
|  | @ -174,7 +179,7 @@ COMPRESS_URL = STATIC_URL | |||
| # Collectfast | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://github.com/antonagestam/collectfast#installation | ||||
| INSTALLED_APPS = ['collectfast'] + INSTALLED_APPS  # noqa F405 | ||||
| INSTALLED_APPS = ["collectfast"] + INSTALLED_APPS  # noqa F405 | ||||
| AWS_PRELOAD_METADATA = True | ||||
| 
 | ||||
| {% endif %} | ||||
|  | @ -182,64 +187,64 @@ AWS_PRELOAD_METADATA = True | |||
| # raven | ||||
| # ------------------------------------------------------------------------------ | ||||
| # https://docs.sentry.io/clients/python/integrations/django/ | ||||
| INSTALLED_APPS += ['raven.contrib.django.raven_compat']  # noqa F405 | ||||
| MIDDLEWARE = ['raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware'] + MIDDLEWARE | ||||
| INSTALLED_APPS += ["raven.contrib.django.raven_compat"]  # noqa F405 | ||||
| MIDDLEWARE = ["raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware"] + MIDDLEWARE | ||||
| 
 | ||||
| # Sentry | ||||
| # ------------------------------------------------------------------------------ | ||||
| SENTRY_DSN = env('SENTRY_DSN') | ||||
| SENTRY_CLIENT = env('DJANGO_SENTRY_CLIENT', default='raven.contrib.django.raven_compat.DjangoClient') | ||||
| SENTRY_DSN = env("SENTRY_DSN") | ||||
| SENTRY_CLIENT = env("DJANGO_SENTRY_CLIENT", default="raven.contrib.django.raven_compat.DjangoClient") | ||||
| LOGGING = { | ||||
|     'version': 1, | ||||
|     'disable_existing_loggers': True, | ||||
|     'root': { | ||||
|         'level': 'WARNING', | ||||
|         'handlers': ['sentry'], | ||||
|     "version": 1, | ||||
|     "disable_existing_loggers": True, | ||||
|     "root": { | ||||
|         "level": "WARNING", | ||||
|         "handlers": ["sentry"], | ||||
|     }, | ||||
|     'formatters': { | ||||
|         'verbose': { | ||||
|             'format': '%(levelname)s %(asctime)s %(module)s ' | ||||
|                       '%(process)d %(thread)d %(message)s' | ||||
|         }, | ||||
|     }, | ||||
|     'handlers': { | ||||
|         'sentry': { | ||||
|             'level': 'ERROR', | ||||
|             'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', | ||||
|         }, | ||||
|         'console': { | ||||
|             'level': 'DEBUG', | ||||
|             'class': 'logging.StreamHandler', | ||||
|             'formatter': 'verbose' | ||||
|     "formatters": { | ||||
|         "verbose": { | ||||
|             "format": "%(levelname)s %(asctime)s %(module)s " | ||||
|             "%(process)d %(thread)d %(message)s" | ||||
|         } | ||||
|     }, | ||||
|     'loggers': { | ||||
|         'django.db.backends': { | ||||
|             'level': 'ERROR', | ||||
|             'handlers': ['console'], | ||||
|             'propagate': False, | ||||
|     "handlers": { | ||||
|         "sentry": { | ||||
|             "level": "ERROR", | ||||
|             "class": "raven.contrib.django.raven_compat.handlers.SentryHandler", | ||||
|         }, | ||||
|         'raven': { | ||||
|             'level': 'DEBUG', | ||||
|             'handlers': ['console'], | ||||
|             'propagate': False, | ||||
|         "console": { | ||||
|             "level": "DEBUG", | ||||
|             "class": "logging.StreamHandler", | ||||
|             "formatter": "verbose", | ||||
|         }, | ||||
|         'sentry.errors': { | ||||
|             'level': 'DEBUG', | ||||
|             'handlers': ['console'], | ||||
|             'propagate': False, | ||||
|     }, | ||||
|     "loggers": { | ||||
|         "django.db.backends": { | ||||
|             "level": "ERROR", | ||||
|             "handlers": ["console"], | ||||
|             "propagate": False, | ||||
|         }, | ||||
|         'django.security.DisallowedHost': { | ||||
|             'level': 'ERROR', | ||||
|             'handlers': ['console', 'sentry'], | ||||
|             'propagate': False, | ||||
|         "raven": { | ||||
|             "level": "DEBUG", | ||||
|             "handlers": ["console"], | ||||
|             "propagate": False, | ||||
|         }, | ||||
|         "sentry.errors": { | ||||
|             "level": "DEBUG", | ||||
|             "handlers": ["console"], | ||||
|             "propagate": False, | ||||
|         }, | ||||
|         "django.security.DisallowedHost": { | ||||
|             "level": "ERROR", | ||||
|             "handlers": ["console", "sentry"], | ||||
|             "propagate": False, | ||||
|         }, | ||||
|     }, | ||||
| } | ||||
| 
 | ||||
| SENTRY_CELERY_LOGLEVEL = env.int('DJANGO_SENTRY_LOG_LEVEL', logging.INFO) | ||||
| SENTRY_CELERY_LOGLEVEL = env.int("DJANGO_SENTRY_LOG_LEVEL", logging.INFO) | ||||
| RAVEN_CONFIG = { | ||||
|     'dsn': SENTRY_DSN | ||||
|     "dsn": SENTRY_DSN | ||||
| } | ||||
| 
 | ||||
| {%- else %} | ||||
|  | @ -252,43 +257,39 @@ RAVEN_CONFIG = { | |||
| # See https://docs.djangoproject.com/en/dev/topics/logging for | ||||
| # more details on how to customize your logging configuration. | ||||
| LOGGING = { | ||||
|     'version': 1, | ||||
|     'disable_existing_loggers': False, | ||||
|     'filters': { | ||||
|         'require_debug_false': { | ||||
|             '()': 'django.utils.log.RequireDebugFalse' | ||||
|     "version": 1, | ||||
|     "disable_existing_loggers": False, | ||||
|     "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, | ||||
|     "formatters": { | ||||
|         "verbose": { | ||||
|             "format": "%(levelname)s %(asctime)s %(module)s " | ||||
|             "%(process)d %(thread)d %(message)s" | ||||
|         } | ||||
|     }, | ||||
|     'formatters': { | ||||
|         'verbose': { | ||||
|             'format': '%(levelname)s %(asctime)s %(module)s ' | ||||
|                       '%(process)d %(thread)d %(message)s' | ||||
|     "handlers": { | ||||
|         "mail_admins": { | ||||
|             "level": "ERROR", | ||||
|             "filters": ["require_debug_false"], | ||||
|             "class": "django.utils.log.AdminEmailHandler", | ||||
|         }, | ||||
|         "console": { | ||||
|             "level": "DEBUG", | ||||
|             "class": "logging.StreamHandler", | ||||
|             "formatter": "verbose", | ||||
|         }, | ||||
|     }, | ||||
|     'handlers': { | ||||
|         'mail_admins': { | ||||
|             'level': 'ERROR', | ||||
|             'filters': ['require_debug_false'], | ||||
|             'class': 'django.utils.log.AdminEmailHandler' | ||||
|     "loggers": { | ||||
|         "django.request": { | ||||
|             "handlers": ["mail_admins"], | ||||
|             "level": "ERROR", | ||||
|             "propagate": True, | ||||
|         }, | ||||
|         'console': { | ||||
|             'level': 'DEBUG', | ||||
|             'class': 'logging.StreamHandler', | ||||
|             'formatter': 'verbose', | ||||
|         "django.security.DisallowedHost": { | ||||
|             "level": "ERROR", | ||||
|             "handlers": ["console", "mail_admins"], | ||||
|             "propagate": True, | ||||
|         }, | ||||
|     }, | ||||
|     'loggers': { | ||||
|         'django.request': { | ||||
|             'handlers': ['mail_admins'], | ||||
|             'level': 'ERROR', | ||||
|             'propagate': True | ||||
|         }, | ||||
|         'django.security.DisallowedHost': { | ||||
|             'level': 'ERROR', | ||||
|             'handlers': ['console', 'mail_admins'], | ||||
|             'propagate': True | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| {% endif %} | ||||
|  |  | |||
|  | @ -10,7 +10,10 @@ from .base import env | |||
| # https://docs.djangoproject.com/en/dev/ref/settings/#debug | ||||
| DEBUG = False | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#secret-key | ||||
| SECRET_KEY = env("DJANGO_SECRET_KEY", default="!!!SET DJANGO_SECRET_KEY!!!") | ||||
| SECRET_KEY = env( | ||||
|     "DJANGO_SECRET_KEY", | ||||
|     default="!!!SET DJANGO_SECRET_KEY!!!", | ||||
| ) | ||||
| # https://docs.djangoproject.com/en/dev/ref/settings/#test-runner | ||||
| TEST_RUNNER = "django.test.runner.DiscoverRunner" | ||||
| 
 | ||||
|  | @ -19,7 +22,8 @@ TEST_RUNNER = "django.test.runner.DiscoverRunner" | |||
| # https://docs.djangoproject.com/en/dev/ref/settings/#caches | ||||
| CACHES = { | ||||
|     "default": { | ||||
|         "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "" | ||||
|         "BACKEND": "django.core.cache.backends.locmem.LocMemCache", | ||||
|         "LOCATION": "", | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,22 +8,15 @@ from django.views import defaults as default_views | |||
| urlpatterns = [ | ||||
|     path("", TemplateView.as_view(template_name="pages/home.html"), name="home"), | ||||
|     path( | ||||
|         "about/", | ||||
|         TemplateView.as_view(template_name="pages/about.html"), | ||||
|         name="about", | ||||
|         "about/", TemplateView.as_view(template_name="pages/about.html"), name="about" | ||||
|     ), | ||||
|     # Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %} | ||||
|     path(settings.ADMIN_URL, admin.site.urls), | ||||
|     # User management | ||||
|     path( | ||||
|         "users/", | ||||
|         include("{{ cookiecutter.project_slug }}.users.urls", namespace="users"), | ||||
|     ), | ||||
|     path("users/", include("{{ cookiecutter.project_slug }}.users.urls", namespace="users")), | ||||
|     path("accounts/", include("allauth.urls")), | ||||
|     # Your stuff: custom urls includes go here | ||||
| ] + static( | ||||
|     settings.MEDIA_URL, document_root=settings.MEDIA_ROOT | ||||
| ) | ||||
| ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) | ||||
| 
 | ||||
| if settings.DEBUG: | ||||
|     # This allows the error pages to be debugged during development, just visit | ||||
|  |  | |||
|  | @ -20,11 +20,12 @@ from django.core.wsgi import get_wsgi_application | |||
| 
 | ||||
| # This allows easy placement of apps within the interior | ||||
| # {{ cookiecutter.project_slug }} directory. | ||||
| app_path = os.path.abspath(os.path.join( | ||||
|     os.path.dirname(os.path.abspath(__file__)), os.pardir)) | ||||
| sys.path.append(os.path.join(app_path, '{{ cookiecutter.project_slug }}')) | ||||
| app_path = os.path.abspath( | ||||
|     os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir) | ||||
| ) | ||||
| sys.path.append(os.path.join(app_path, "{{ cookiecutter.project_slug }}")) | ||||
| {% if cookiecutter.use_sentry == 'y' -%} | ||||
| if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': | ||||
| if os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.production": | ||||
|     from raven.contrib.django.raven_compat.middleware.wsgi import Sentry | ||||
| {%- endif %} | ||||
| # We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks | ||||
|  | @ -38,7 +39,7 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") | |||
| # setting points here. | ||||
| application = get_wsgi_application() | ||||
| {% if cookiecutter.use_sentry == 'y' -%} | ||||
| if os.environ.get('DJANGO_SETTINGS_MODULE') == 'config.settings.production': | ||||
| if os.environ.get("DJANGO_SETTINGS_MODULE") == "config.settings.production": | ||||
|     application = Sentry(application) | ||||
| {%- endif %} | ||||
| # Apply WSGI middleware here. | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ pytest-sugar==0.9.2  # https://github.com/Frozenball/pytest-sugar | |||
| # ------------------------------------------------------------------------------ | ||||
| flake8==3.7.5  # https://github.com/PyCQA/flake8 | ||||
| coverage==4.5.2  # https://github.com/nedbat/coveragepy | ||||
| black==18.9b0  # https://github.com/ambv/black | ||||
| 
 | ||||
| # Django | ||||
| # ------------------------------------------------------------------------------ | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| {% if cookiecutter.use_celery == 'y' %} | ||||
| {% if cookiecutter.use_celery == 'y' -%} | ||||
| import os | ||||
| from celery import Celery | ||||
| from django.apps import apps, AppConfig | ||||
|  | @ -7,26 +7,28 @@ from django.conf import settings | |||
| 
 | ||||
| if not settings.configured: | ||||
|     # set the default Django settings module for the 'celery' program. | ||||
|     os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local')  # pragma: no cover | ||||
|     os.environ.setdefault( | ||||
|         "DJANGO_SETTINGS_MODULE", "config.settings.local" | ||||
|     )  # pragma: no cover | ||||
| 
 | ||||
| 
 | ||||
| app = Celery('{{cookiecutter.project_slug}}') | ||||
| app = Celery("{{cookiecutter.project_slug}}") | ||||
| # Using a string here means the worker will not have to | ||||
| # pickle the object when using Windows. | ||||
| # - namespace='CELERY' means all celery-related configuration keys | ||||
| #   should have a `CELERY_` prefix. | ||||
| app.config_from_object('django.conf:settings', namespace='CELERY') | ||||
| app.config_from_object("django.conf:settings", namespace="CELERY") | ||||
| 
 | ||||
| 
 | ||||
| class CeleryAppConfig(AppConfig): | ||||
|     name = '{{cookiecutter.project_slug}}.taskapp' | ||||
|     verbose_name = 'Celery Config' | ||||
|     name = "{{cookiecutter.project_slug}}.taskapp" | ||||
|     verbose_name = "Celery Config" | ||||
| 
 | ||||
|     def ready(self): | ||||
|         installed_apps = [app_config.name for app_config in apps.get_app_configs()] | ||||
|         app.autodiscover_tasks(lambda: installed_apps, force=True) | ||||
|         {%- if cookiecutter.use_sentry == 'y' %} | ||||
| 
 | ||||
|         {% if cookiecutter.use_sentry == 'y' -%} | ||||
|         if hasattr(settings, 'RAVEN_CONFIG'): | ||||
|             # Celery signal registration | ||||
| {% if cookiecutter.use_pycharm == 'y' -%} | ||||
|  | @ -51,7 +53,7 @@ class CeleryAppConfig(AppConfig): | |||
| 
 | ||||
| @app.task(bind=True) | ||||
| def debug_task(self): | ||||
|     print(f'Request: {self.request!r}')  # pragma: no cover | ||||
|     print(f"Request: {self.request!r}")  # pragma: no cover | ||||
| {% else %} | ||||
| # Use this as a starting point for your project with celery. | ||||
| # If you are not using celery, you can remove this app | ||||
|  |  | |||
|  | @ -7,12 +7,10 @@ from django.http import HttpRequest | |||
| 
 | ||||
| 
 | ||||
| class AccountAdapter(DefaultAccountAdapter): | ||||
| 
 | ||||
|     def is_open_for_signup(self, request: HttpRequest): | ||||
|         return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) | ||||
| 
 | ||||
| 
 | ||||
| class SocialAccountAdapter(DefaultSocialAccountAdapter): | ||||
| 
 | ||||
|     def is_open_for_signup(self, request: HttpRequest, sociallogin: Any): | ||||
|         return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) | ||||
|  |  | |||
|  | @ -6,7 +6,6 @@ User = get_user_model() | |||
| 
 | ||||
| 
 | ||||
| class UserChangeForm(forms.UserChangeForm): | ||||
| 
 | ||||
|     class Meta(forms.UserChangeForm.Meta): | ||||
|         model = User | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,9 +19,7 @@ class UserFactory(DjangoModelFactory): | |||
|             digits=True, | ||||
|             upper_case=True, | ||||
|             lower_case=True, | ||||
|         ).generate( | ||||
|             extra_kwargs={} | ||||
|         ) | ||||
|         ).generate(extra_kwargs={}) | ||||
|         self.set_password(password) | ||||
| 
 | ||||
|     class Meta: | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ pytestmark = pytest.mark.django_db | |||
| 
 | ||||
| 
 | ||||
| class TestUserCreationForm: | ||||
| 
 | ||||
|     def test_clean_username(self): | ||||
|         # A user with proto_user params does not exist yet. | ||||
|         proto_user = UserFactory.build() | ||||
|  |  | |||
|  | @ -40,7 +40,6 @@ class TestUserUpdateView: | |||
| 
 | ||||
| 
 | ||||
| class TestUserRedirectView: | ||||
| 
 | ||||
|     def test_get_redirect_url( | ||||
|         self, user: settings.AUTH_USER_MODEL, request_factory: RequestFactory | ||||
|     ): | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user