From 3f753e04114471a87a9b85e6078692c91b4bf164 Mon Sep 17 00:00:00 2001 From: Daniel Roy Greenfeld Date: Sun, 8 Apr 2018 17:03:29 -0500 Subject: [PATCH] First pass at running black across the project (#1602) --- docs/conf.py | 56 +++--- hooks/post_gen_project.py | 183 +++++++----------- hooks/pre_gen_project.py | 41 ++-- setup.py | 46 ++--- tests/test_cookiecutter_generation.py | 31 +-- .../config/settings/test.py | 29 ++- {{cookiecutter.project_slug}}/config/urls.py | 51 +++-- {{cookiecutter.project_slug}}/docs/conf.py | 56 +++--- {{cookiecutter.project_slug}}/manage.py | 7 +- .../merge_production_dotenvs_in_dotenv.py | 40 ++-- .../{{cookiecutter.project_slug}}/__init__.py | 9 +- .../contrib/sites/migrations/0001_initial.py | 39 ++-- .../migrations/0002_alter_domain_unique.py | 16 +- .../0003_set_site_domain_and_name.py | 24 +-- .../users/adapters.py | 6 +- .../users/admin.py | 18 +- .../users/apps.py | 2 +- .../users/migrations/0001_initial.py | 139 ++++++++++--- .../users/models.py | 4 +- .../users/tests/factories.py | 10 +- .../users/tests/test_admin.py | 30 +-- .../users/tests/test_models.py | 7 +- .../users/tests/test_urls.py | 23 +-- .../users/tests/test_views.py | 24 +-- .../users/urls.py | 24 +-- .../users/views.py | 17 +- 26 files changed, 499 insertions(+), 433 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index aa023b74..e3ddae9a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,19 +29,19 @@ now = datetime.now() extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'Cookiecutter Django' +project = "Cookiecutter Django" copyright = "2013-2018, Daniel Roy Greenfeld".format(now.year) # The version info for the project you're documenting, acts as replacement for @@ -49,9 +49,9 @@ copyright = "2013-2018, Daniel Roy Greenfeld".format(now.year) # built documents. # # The short X.Y version. -version = '{}.{}.{}'.format(*now.isocalendar()) +version = "{}.{}.{}".format(*now.isocalendar()) # The full version, including alpha/beta/rc tags. -release = '{}.{}.{}'.format(*now.isocalendar()) +release = "{}.{}.{}".format(*now.isocalendar()) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -65,7 +65,7 @@ release = '{}.{}.{}'.format(*now.isocalendar()) # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None @@ -82,7 +82,7 @@ exclude_patterns = ['_build'] # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -92,7 +92,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -121,7 +121,7 @@ html_theme = 'default' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -165,7 +165,7 @@ html_static_path = ['_static'] # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'cookiecutter-djangodoc' +htmlhelp_basename = "cookiecutter-djangodoc" # -- Options for LaTeX output -------------------------------------------------- @@ -173,10 +173,8 @@ htmlhelp_basename = 'cookiecutter-djangodoc' latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # 'preamble': '', } @@ -184,10 +182,13 @@ latex_elements = { # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', - 'cookiecutter-django.tex', - 'cookiecutter-django Documentation', - 'cookiecutter-django', 'manual'), + ( + "index", + "cookiecutter-django.tex", + "cookiecutter-django Documentation", + "cookiecutter-django", + "manual", + ) ] # The name of an image file (relative to this directory) to place at the top of @@ -216,8 +217,13 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'Cookiecutter Django', 'Cookiecutter Django documentation', - ['Daniel Roy Greenfeld'], 1) + ( + "index", + "Cookiecutter Django", + "Cookiecutter Django documentation", + ["Daniel Roy Greenfeld"], + 1, + ) ] # If true, show URL addresses after external links. @@ -230,9 +236,15 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Cookiecutter Django', 'Cookiecutter Django documentation', - 'Daniel Roy Greenfeld', 'Cookiecutter Django', - 'A Cookiecutter template for creating production-ready Django projects quickly.', 'Miscellaneous'), + ( + "index", + "Cookiecutter Django", + "Cookiecutter Django documentation", + "Daniel Roy Greenfeld", + "Cookiecutter Django", + "A Cookiecutter template for creating production-ready Django projects quickly.", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 050a0ca4..e353c77f 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -30,96 +30,77 @@ SUCCESS = "\x1b[1;32m [SUCCESS]: " def remove_open_source_files(): - file_names = [ - 'CONTRIBUTORS.txt', - ] + file_names = ["CONTRIBUTORS.txt"] for file_name in file_names: os.remove(file_name) def remove_gplv3_files(): - file_names = [ - 'COPYING', - ] + file_names = ["COPYING"] for file_name in file_names: os.remove(file_name) def remove_pycharm_files(): - idea_dir_path = '.idea' + idea_dir_path = ".idea" if os.path.exists(idea_dir_path): shutil.rmtree(idea_dir_path) - docs_dir_path = os.path.join('docs', 'pycharm') + docs_dir_path = os.path.join("docs", "pycharm") if os.path.exists(docs_dir_path): shutil.rmtree(docs_dir_path) def remove_docker_files(): - shutil.rmtree('compose') + shutil.rmtree("compose") - file_names = [ - 'local.yml', - 'production.yml', - '.dockerignore', - ] + file_names = ["local.yml", "production.yml", ".dockerignore"] for file_name in file_names: os.remove(file_name) def remove_heroku_files(): - file_names = [ - 'Procfile', - 'runtime.txt', - 'requirements.txt', - ] + file_names = ["Procfile", "runtime.txt", "requirements.txt"] for file_name in file_names: os.remove(file_name) def remove_grunt_files(): - file_names = [ - 'Gruntfile.js', - ] + file_names = ["Gruntfile.js"] for file_name in file_names: os.remove(file_name) def remove_gulp_files(): - file_names = [ - 'gulpfile.js', - ] + file_names = ["gulpfile.js"] for file_name in file_names: os.remove(file_name) def remove_packagejson_file(): - file_names = [ - 'package.json', - ] + file_names = ["package.json"] for file_name in file_names: os.remove(file_name) def remove_celery_app(): - shutil.rmtree(os.path.join('{{ cookiecutter.project_slug }}', 'taskapp')) + shutil.rmtree(os.path.join("{{ cookiecutter.project_slug }}", "taskapp")) def remove_dottravisyml_file(): - os.remove('.travis.yml') + os.remove(".travis.yml") def append_to_project_gitignore(path): - gitignore_file_path = '.gitignore' - with open(gitignore_file_path, 'a') as gitignore_file: + gitignore_file_path = ".gitignore" + with open(gitignore_file_path, "a") as gitignore_file: gitignore_file.write(path) gitignore_file.write(os.linesep) -def generate_random_string(length, - using_digits=False, - using_ascii_letters=False, - using_punctuation=False): +def generate_random_string( + length, using_digits=False, using_ascii_letters=False, using_punctuation=False +): """ Example: opting out for 50 symbol-long, [a-z][A-Z][0-9] string @@ -134,19 +115,13 @@ def generate_random_string(length, if using_ascii_letters: symbols += string.ascii_letters if using_punctuation: - symbols += string.punctuation \ - .replace('"', '') \ - .replace("'", '') \ - .replace('\\', '') - return ''.join([random.choice(symbols) for _ in range(length)]) + symbols += string.punctuation.replace('"', "").replace("'", "").replace( + "\\", "" + ) + return "".join([random.choice(symbols) for _ in range(length)]) -def set_flag(file_path, - flag, - value=None, - formatted=None, - *args, - **kwargs): +def set_flag(file_path, flag, value=None, formatted=None, *args, **kwargs): if value is None: random_string = generate_random_string(*args, **kwargs) if random_string is None: @@ -159,7 +134,7 @@ def set_flag(file_path, random_string = formatted.format(random_string) value = random_string - with open(file_path, 'r+') as f: + with open(file_path, "r+") as f: file_contents = f.read().replace(flag, value) f.seek(0) f.write(file_contents) @@ -171,10 +146,10 @@ def set_flag(file_path, def set_django_secret_key(file_path): django_secret_key = set_flag( file_path, - '!!!SET DJANGO_SECRET_KEY!!!', + "!!!SET DJANGO_SECRET_KEY!!!", length=64, using_digits=True, - using_ascii_letters=True + using_ascii_letters=True, ) return django_secret_key @@ -182,28 +157,22 @@ def set_django_secret_key(file_path): def set_django_admin_url(file_path): django_admin_url = set_flag( file_path, - '!!!SET DJANGO_ADMIN_URL!!!', - formatted='^{}/', + "!!!SET DJANGO_ADMIN_URL!!!", + formatted="^{}/", length=32, using_digits=True, - using_ascii_letters=True + using_ascii_letters=True, ) return django_admin_url def generate_postgres_user(): - return generate_random_string( - length=32, - using_ascii_letters=True - ) + return generate_random_string(length=32, using_ascii_letters=True) -def set_postgres_user(file_path, - value=None): +def set_postgres_user(file_path, value=None): postgres_user = set_flag( - file_path, - '!!!SET POSTGRES_USER!!!', - value=value or generate_postgres_user() + file_path, "!!!SET POSTGRES_USER!!!", value=value or generate_postgres_user() ) return postgres_user @@ -211,47 +180,47 @@ def set_postgres_user(file_path, def set_postgres_password(file_path): postgres_password = set_flag( file_path, - '!!!SET POSTGRES_PASSWORD!!!', + "!!!SET POSTGRES_PASSWORD!!!", length=64, using_digits=True, - using_ascii_letters=True + using_ascii_letters=True, ) return postgres_password def append_to_gitignore_file(s): - with open('.gitignore', 'a') as gitignore_file: + with open(".gitignore", "a") as gitignore_file: gitignore_file.write(s) gitignore_file.write(os.linesep) def set_flags_in_envs(postgres_user): - local_postgres_envs_path = os.path.join('.envs', '.local', '.postgres') + local_postgres_envs_path = os.path.join(".envs", ".local", ".postgres") set_postgres_user(local_postgres_envs_path, value=postgres_user) set_postgres_password(local_postgres_envs_path) - production_django_envs_path = os.path.join('.envs', '.production', '.django') + production_django_envs_path = os.path.join(".envs", ".production", ".django") set_django_secret_key(production_django_envs_path) set_django_admin_url(production_django_envs_path) - production_postgres_envs_path = os.path.join('.envs', '.production', '.postgres') + production_postgres_envs_path = os.path.join(".envs", ".production", ".postgres") set_postgres_user(production_postgres_envs_path, value=postgres_user) set_postgres_password(production_postgres_envs_path) def set_flags_in_settings_files(): - set_django_secret_key(os.path.join('config', 'settings', 'local.py')) - set_django_secret_key(os.path.join('config', 'settings', 'test.py')) + set_django_secret_key(os.path.join("config", "settings", "local.py")) + set_django_secret_key(os.path.join("config", "settings", "test.py")) def remove_envs_and_associated_files(): - shutil.rmtree('.envs') - os.remove('merge_production_dotenvs_in_dotenv.py') + shutil.rmtree(".envs") + os.remove("merge_production_dotenvs_in_dotenv.py") def remove_celery_compose_dirs(): - shutil.rmtree(os.path.join('compose', 'local', 'django', 'celery')) - shutil.rmtree(os.path.join('compose', 'production', 'django', 'celery')) + shutil.rmtree(os.path.join("compose", "local", "django", "celery")) + shutil.rmtree(os.path.join("compose", "production", "django", "celery")) def main(): @@ -259,75 +228,67 @@ def main(): set_flags_in_envs(postgres_user) set_flags_in_settings_files() - if '{{ cookiecutter.open_source_license }}' == 'Not open source': + if "{{ cookiecutter.open_source_license }}" == "Not open source": remove_open_source_files() - if '{{ cookiecutter.open_source_license}}' != 'GPLv3': + if "{{ cookiecutter.open_source_license}}" != "GPLv3": remove_gplv3_files() - if '{{ cookiecutter.use_pycharm }}'.lower() == 'n': + if "{{ cookiecutter.use_pycharm }}".lower() == "n": remove_pycharm_files() - if '{{ cookiecutter.use_docker }}'.lower() == 'n': + if "{{ cookiecutter.use_docker }}".lower() == "n": remove_docker_files() - if '{{ cookiecutter.use_heroku }}'.lower() == 'n': + if "{{ cookiecutter.use_heroku }}".lower() == "n": remove_heroku_files() - if '{{ cookiecutter.use_docker }}'.lower() == 'n' and '{{ cookiecutter.use_heroku }}'.lower() == 'n': - if '{{ cookiecutter.keep_local_envs_in_vcs }}'.lower() == 'y': + if "{{ cookiecutter.use_docker }}".lower() == "n" and "{{ cookiecutter.use_heroku }}".lower() == "n": + if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": print( - INFO + - ".env(s) are only utilized when Docker Compose and/or " + 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 + "make sense given your current setup." + TERMINATOR ) remove_envs_and_associated_files() else: - append_to_gitignore_file('.env') - append_to_gitignore_file('.envs/*') - if '{{ cookiecutter.keep_local_envs_in_vcs }}'.lower() == 'y': - append_to_gitignore_file('!.envs/.local/') + append_to_gitignore_file(".env") + append_to_gitignore_file(".envs/*") + if "{{ cookiecutter.keep_local_envs_in_vcs }}".lower() == "y": + append_to_gitignore_file("!.envs/.local/") - if '{{ cookiecutter.js_task_runner}}'.lower() == 'gulp': + if "{{ cookiecutter.js_task_runner}}".lower() == "gulp": remove_grunt_files() - elif '{{ cookiecutter.js_task_runner}}'.lower() == 'grunt': + elif "{{ cookiecutter.js_task_runner}}".lower() == "grunt": remove_gulp_files() else: remove_gulp_files() remove_grunt_files() remove_packagejson_file() - if '{{ cookiecutter.js_task_runner }}'.lower() in ['grunt', 'gulp'] \ - and '{{ cookiecutter.use_docker }}'.lower() == 'y': + if "{{ cookiecutter.js_task_runner }}".lower() in [ + "grunt", "gulp" + ] and "{{ cookiecutter.use_docker }}".lower() == "y": print( - WARNING + - "Docker and {} JS task runner ".format( - '{{ cookiecutter.js_task_runner }}' - .lower() - .capitalize() - ) + - "working together not supported yet. " + WARNING + + "Docker and {} JS task runner ".format( + "{{ 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 + "manually." + TERMINATOR ) - if '{{ cookiecutter.use_celery }}'.lower() == 'n': + if "{{ cookiecutter.use_celery }}".lower() == "n": remove_celery_app() - if '{{ cookiecutter.use_docker }}'.lower() == 'y': + if "{{ cookiecutter.use_docker }}".lower() == "y": remove_celery_compose_dirs() - if '{{ cookiecutter.use_travisci }}'.lower() == 'n': + if "{{ cookiecutter.use_travisci }}".lower() == "n": remove_dottravisyml_file() - print( - SUCCESS + - "Project initialized, keep up the good work!" + - TERMINATOR - ) + print(SUCCESS + "Project initialized, keep up the good work!" + TERMINATOR) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index ed2bef18..b7f4dfbb 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -16,40 +16,41 @@ INFO = "\x1b[1;33m [INFO]: " HINT = "\x1b[3;33m" 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) +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 "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name." -if '{{ cookiecutter.use_docker }}'.lower() == 'n': +if "{{ cookiecutter.use_docker }}".lower() == "n": python_major_version = sys.version_info[0] if python_major_version == 2: print( - WARNING + - "Cookiecutter Django does not support Python 2. " + WARNING + "Cookiecutter Django does not support Python 2. " "Stability is guaranteed with Python 3.6+ only, " - "are you sure you want to proceed (y/n)? " + - TERMINATOR + "are you sure you want to proceed (y/n)? " + TERMINATOR ) - yes_options, no_options = frozenset(['y']), frozenset(['n']) + yes_options, no_options = frozenset(["y"]), frozenset(["n"]) while True: choice = raw_input().lower() if choice in yes_options: break + elif choice in no_options: - print( - INFO + - "Generation process stopped as requested." + - TERMINATOR - ) + print(INFO + "Generation process stopped as requested." + TERMINATOR) sys.exit(1) else: print( - HINT + - "Please respond with {} or {}: ".format( - ', '.join(["'{}'".format(o) for o in yes_options if not o == '']), - ', '.join(["'{}'".format(o) for o in no_options if not o == '']) - ) + - TERMINATOR + HINT + + "Please respond with {} or {}: ".format( + ", ".join( + ["'{}'".format(o) for o in yes_options if not o == ""] + ), + ", ".join( + ["'{}'".format(o) for o in no_options if not o == ""] + ), + ) + + TERMINATOR ) diff --git a/setup.py b/setup.py index a7efb0a6..65bcd8fc 100644 --- a/setup.py +++ b/setup.py @@ -10,42 +10,42 @@ except ImportError: # Our version ALWAYS matches the version of Django we support # If Django has a new release, we branch, tag, then update this setting after the tag. -version = '2.0.2' +version = "2.0.2" -if sys.argv[-1] == 'tag': +if sys.argv[-1] == "tag": os.system('git tag -a %s -m "version %s"' % (version, version)) - os.system('git push --tags') + os.system("git push --tags") sys.exit() -with open('README.rst') as readme_file: +with open("README.rst") as readme_file: long_description = readme_file.read() setup( - name='cookiecutter-django', + name="cookiecutter-django", version=version, - description='A Cookiecutter template for creating production-ready Django projects quickly.', + description="A Cookiecutter template for creating production-ready Django projects quickly.", long_description=long_description, - author='Daniel Roy Greenfeld', - author_email='pydanny@gmail.com', - url='https://github.com/pydanny/cookiecutter-django', + author="Daniel Roy Greenfeld", + author_email="pydanny@gmail.com", + url="https://github.com/pydanny/cookiecutter-django", packages=[], - license='BSD', + license="BSD", zip_safe=False, classifiers=[ - 'Development Status :: 4 - Beta', - 'Environment :: Console', - 'Framework :: Django :: 2.0', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: CPython', - 'Topic :: Software Development', + "Development Status :: 4 - Beta", + "Environment :: Console", + "Framework :: Django :: 2.0", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Software Development", ], keywords=( - 'cookiecutter, Python, projects, project templates, django, ' - 'skeleton, scaffolding, project directory, setup.py' + "cookiecutter, Python, projects, project templates, django, " + "skeleton, scaffolding, project directory, setup.py" ), ) diff --git a/tests/test_cookiecutter_generation.py b/tests/test_cookiecutter_generation.py index acbc2c09..cf173576 100755 --- a/tests/test_cookiecutter_generation.py +++ b/tests/test_cookiecutter_generation.py @@ -5,21 +5,21 @@ import sh import pytest from binaryornot.check import is_binary -PATTERN = '{{(\s?cookiecutter)[.](.*?)}}' +PATTERN = "{{(\s?cookiecutter)[.](.*?)}}" RE_OBJ = re.compile(PATTERN) @pytest.fixture def context(): return { - 'project_name': 'My Test Project', - 'project_slug': 'my_test_project', - 'author_name': 'Test Author', - 'email': 'test@example.com', - 'description': 'A short description of the project.', - 'domain_name': 'example.com', - 'version': '0.1.0', - 'timezone': 'UTC', + "project_name": "My Test Project", + "project_slug": "my_test_project", + "author_name": "Test Author", + "email": "test@example.com", + "description": "A short description of the project.", + "domain_name": "example.com", + "version": "0.1.0", + "timezone": "UTC", } @@ -40,9 +40,10 @@ def check_paths(paths): for path in paths: if is_binary(path): continue - for line in open(path, 'r'): + + for line in open(path, "r"): match = RE_OBJ.search(line) - msg = 'cookiecutter variable not replaced in {}' + msg = "cookiecutter variable not replaced in {}" assert match is None, msg.format(path) @@ -50,7 +51,7 @@ def test_default_configuration(cookies, context): result = cookies.bake(extra_context=context) assert result.exit_code == 0 assert result.exception is None - assert result.project.basename == context['project_slug'] + assert result.project.basename == context["project_slug"] assert result.project.isdir() paths = build_files_list(str(result.project)) @@ -58,9 +59,9 @@ def test_default_configuration(cookies, context): check_paths(paths) -@pytest.fixture(params=['use_mailhog', 'use_celery', 'windows']) +@pytest.fixture(params=["use_mailhog", "use_celery", "windows"]) def feature_context(request, context): - context.update({request.param: 'y'}) + context.update({request.param: "y"}) return context @@ -68,7 +69,7 @@ def test_enabled_features(cookies, feature_context): result = cookies.bake(extra_context=feature_context) assert result.exit_code == 0 assert result.exception is None - assert result.project.basename == feature_context['project_slug'] + assert result.project.basename == feature_context["project_slug"] assert result.project.isdir() paths = build_files_list(str(result.project)) diff --git a/{{cookiecutter.project_slug}}/config/settings/test.py b/{{cookiecutter.project_slug}}/config/settings/test.py index e206c2f5..e947d410 100644 --- a/{{cookiecutter.project_slug}}/config/settings/test.py +++ b/{{cookiecutter.project_slug}}/config/settings/test.py @@ -10,47 +10,44 @@ 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' +TEST_RUNNER = "django.test.runner.DiscoverRunner" # CACHES # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#caches CACHES = { - 'default': { - 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', - 'LOCATION': '' + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", "LOCATION": "" } } # PASSWORDS # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#password-hashers -PASSWORD_HASHERS = [ - 'django.contrib.auth.hashers.MD5PasswordHasher', -] +PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] # TEMPLATES # ------------------------------------------------------------------------------ # https://docs.djangoproject.com/en/dev/ref/settings/#templates -TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # noqa F405 -TEMPLATES[0]['OPTIONS']['loaders'] = [ # noqa F405 +TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG # 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/#email-backend -EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend' +EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" # https://docs.djangoproject.com/en/dev/ref/settings/#email-host -EMAIL_HOST = 'localhost' +EMAIL_HOST = "localhost" # https://docs.djangoproject.com/en/dev/ref/settings/#email-port EMAIL_PORT = 1025 diff --git a/{{cookiecutter.project_slug}}/config/urls.py b/{{cookiecutter.project_slug}}/config/urls.py index 930d210e..2a126f6e 100644 --- a/{{cookiecutter.project_slug}}/config/urls.py +++ b/{{cookiecutter.project_slug}}/config/urls.py @@ -6,32 +6,47 @@ from django.views.generic import TemplateView from django.views import defaults as default_views urlpatterns = [ - url(r'^$', TemplateView.as_view(template_name='pages/home.html'), name='home'), - url(r'^about/$', TemplateView.as_view(template_name='pages/about.html'), name='about'), - + url(r"^$", TemplateView.as_view(template_name="pages/home.html"), name="home"), + url( + r"^about/$", + TemplateView.as_view(template_name="pages/about.html"), + name="about", + ), # Django Admin, use {% raw %}{% url 'admin:index' %}{% endraw %} url(settings.ADMIN_URL, admin.site.urls), - # User management - url(r'^users/', include('{{ cookiecutter.project_slug }}.users.urls', namespace='users')), - url(r'^accounts/', include('allauth.urls')), - + url( + r"^users/", + include("{{ cookiecutter.project_slug }}.users.urls", namespace="users"), + ), + url(r"^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 # these url in browser to see how these error pages look like. urlpatterns += [ - url(r'^400/$', default_views.bad_request, kwargs={'exception': Exception('Bad Request!')}), - url(r'^403/$', default_views.permission_denied, kwargs={'exception': Exception('Permission Denied')}), - url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception('Page not Found')}), - url(r'^500/$', default_views.server_error), + url( + r"^400/$", + default_views.bad_request, + kwargs={"exception": Exception("Bad Request!")}, + ), + url( + r"^403/$", + default_views.permission_denied, + kwargs={"exception": Exception("Permission Denied")}, + ), + url( + r"^404/$", + default_views.page_not_found, + kwargs={"exception": Exception("Page not Found")}, + ), + url(r"^500/$", default_views.server_error), ] - if 'debug_toolbar' in settings.INSTALLED_APPS: + if "debug_toolbar" in settings.INSTALLED_APPS: import debug_toolbar - urlpatterns = [ - url(r'^__debug__/', include(debug_toolbar.urls)), - ] + urlpatterns + + urlpatterns = [url(r"^__debug__/", include(debug_toolbar.urls))] + urlpatterns diff --git a/{{cookiecutter.project_slug}}/docs/conf.py b/{{cookiecutter.project_slug}}/docs/conf.py index 80c45032..154c3a7a 100644 --- a/{{cookiecutter.project_slug}}/docs/conf.py +++ b/{{cookiecutter.project_slug}}/docs/conf.py @@ -27,19 +27,19 @@ import sys extensions = [] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = '{{ cookiecutter.project_name }}' +project = "{{ cookiecutter.project_name }}" copyright = """{% now 'utc', '%Y' %}, {{ cookiecutter.author_name }}""" # The version info for the project you're documenting, acts as replacement for @@ -47,9 +47,9 @@ copyright = """{% now 'utc', '%Y' %}, {{ cookiecutter.author_name }}""" # built documents. # # The short X.Y version. -version = '0.1' +version = "0.1" # The full version, including alpha/beta/rc tags. -release = '0.1' +release = "0.1" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -63,7 +63,7 @@ release = '0.1' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None @@ -80,7 +80,7 @@ exclude_patterns = ['_build'] # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -90,7 +90,7 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = "default" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -119,7 +119,7 @@ html_theme = 'default' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -163,7 +163,7 @@ html_static_path = ['_static'] # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = '{{ cookiecutter.project_slug }}doc' +htmlhelp_basename = "{{ cookiecutter.project_slug }}doc" # -- Options for LaTeX output -------------------------------------------------- @@ -171,10 +171,8 @@ htmlhelp_basename = '{{ cookiecutter.project_slug }}doc' latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # 'preamble': '', } @@ -182,10 +180,13 @@ latex_elements = { # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', - '{{ cookiecutter.project_slug }}.tex', - '{{ cookiecutter.project_name }} Documentation', - """{{ cookiecutter.author_name }}""", 'manual'), + ( + "index", + "{{ cookiecutter.project_slug }}.tex", + "{{ cookiecutter.project_name }} Documentation", + """{{ cookiecutter.author_name }}""", + "manual", + ) ] # The name of an image file (relative to this directory) to place at the top of @@ -214,8 +215,13 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', '{{ cookiecutter.project_slug }}', '{{ cookiecutter.project_name }} Documentation', - ["""{{ cookiecutter.author_name }}"""], 1) + ( + "index", + "{{ cookiecutter.project_slug }}", + "{{ cookiecutter.project_name }} Documentation", + ["""{{ cookiecutter.author_name }}"""], + 1, + ) ] # If true, show URL addresses after external links. @@ -228,9 +234,15 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', '{{ cookiecutter.project_slug }}', '{{ cookiecutter.project_name }} Documentation', - """{{ cookiecutter.author_name }}""", '{{ cookiecutter.project_name }}', - """{{ cookiecutter.description }}""", 'Miscellaneous'), + ( + "index", + "{{ cookiecutter.project_slug }}", + "{{ cookiecutter.project_name }} Documentation", + """{{ cookiecutter.author_name }}""", + "{{ cookiecutter.project_name }}", + """{{ cookiecutter.description }}""", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. diff --git a/{{cookiecutter.project_slug}}/manage.py b/{{cookiecutter.project_slug}}/manage.py index 9694c5f8..7e9c99e0 100755 --- a/{{cookiecutter.project_slug}}/manage.py +++ b/{{cookiecutter.project_slug}}/manage.py @@ -2,8 +2,8 @@ import os import sys -if __name__ == '__main__': - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.local') +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") try: from django.core.management import execute_from_command_line @@ -19,11 +19,12 @@ if __name__ == '__main__': "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) + raise # This allows easy placement of apps within the interior # {{ cookiecutter.project_slug }} directory. current_path = os.path.dirname(os.path.abspath(__file__)) - sys.path.append(os.path.join(current_path, '{{ cookiecutter.project_slug }}')) + sys.path.append(os.path.join(current_path, "{{ cookiecutter.project_slug }}")) execute_from_command_line(sys.argv) diff --git a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py index c1bfe30a..5c3027a4 100644 --- a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py +++ b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py @@ -4,21 +4,21 @@ from typing import Sequence import pytest ROOT_DIR_PATH = os.path.dirname(os.path.realpath(__file__)) -PRODUCTION_DOTENVS_DIR_PATH = os.path.join(ROOT_DIR_PATH, '.envs', '.production') +PRODUCTION_DOTENVS_DIR_PATH = os.path.join(ROOT_DIR_PATH, ".envs", ".production") PRODUCTION_DOTENV_FILE_PATHS = [ - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, '.django'), - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, '.postgres'), - os.path.join(PRODUCTION_DOTENVS_DIR_PATH, '.caddy'), + os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".django"), + os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".postgres"), + os.path.join(PRODUCTION_DOTENVS_DIR_PATH, ".caddy"), ] -DOTENV_FILE_PATH = os.path.join(ROOT_DIR_PATH, '.env') +DOTENV_FILE_PATH = os.path.join(ROOT_DIR_PATH, ".env") -def merge(output_file_path: str, - merged_file_paths: Sequence[str], - append_linesep: bool = True) -> None: - with open(output_file_path, 'w') as output_file: +def merge( + output_file_path: str, merged_file_paths: Sequence[str], append_linesep: bool = True +) -> None: + with open(output_file_path, "w") as output_file: for merged_file_path in merged_file_paths: - with open(merged_file_path, 'r') as merged_file: + with open(merged_file_path, "r") as merged_file: merged_file_content = merged_file.read() output_file.write(merged_file_content) if append_linesep: @@ -29,26 +29,24 @@ def main(): merge(DOTENV_FILE_PATH, PRODUCTION_DOTENV_FILE_PATHS) -@pytest.mark.parametrize('merged_file_count', range(3)) -@pytest.mark.parametrize('append_linesep', [True, False]) -def test_merge(tmpdir_factory, - merged_file_count: int, - append_linesep: bool): +@pytest.mark.parametrize("merged_file_count", range(3)) +@pytest.mark.parametrize("append_linesep", [True, False]) +def test_merge(tmpdir_factory, merged_file_count: int, append_linesep: bool): tmp_dir_path = str(tmpdir_factory.getbasetemp()) - output_file_path = os.path.join(tmp_dir_path, '.env') + output_file_path = os.path.join(tmp_dir_path, ".env") - expected_output_file_content = '' + expected_output_file_content = "" merged_file_paths = [] for i in range(merged_file_count): merged_file_ord = i + 1 - merged_filename = '.service{}'.format(merged_file_ord) + merged_filename = ".service{}".format(merged_file_ord) merged_file_path = os.path.join(tmp_dir_path, merged_filename) merged_file_content = merged_filename * merged_file_ord - with open(merged_file_path, 'w+') as file: + with open(merged_file_path, "w+") as file: file.write(merged_file_content) expected_output_file_content += merged_file_content @@ -59,11 +57,11 @@ def test_merge(tmpdir_factory, merge(output_file_path, merged_file_paths, append_linesep) - with open(output_file_path, 'r') as output_file: + with open(output_file_path, "r") as output_file: actual_output_file_content = output_file.read() assert actual_output_file_content == expected_output_file_content -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py index 14a422a2..b4056707 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py @@ -1,2 +1,7 @@ -__version__ = '{{ cookiecutter.version }}' -__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')]) +__version__ = "{{ cookiecutter.version }}" +__version_info__ = tuple( + [ + int(num) if num.isdigit() else num + for num in __version__.replace("-", ".", 1).split(".") + ] +) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py index a7639869..304cd6d7 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0001_initial.py @@ -9,23 +9,34 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Site', + name="Site", fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('domain', models.CharField( - max_length=100, verbose_name='domain name', validators=[_simple_domain_name_validator] - )), - ('name', models.CharField(max_length=50, verbose_name='display name')), + ( + "id", + models.AutoField( + verbose_name="ID", + serialize=False, + auto_created=True, + primary_key=True, + ), + ), + ( + "domain", + models.CharField( + max_length=100, + verbose_name="domain name", + validators=[_simple_domain_name_validator], + ), + ), + ("name", models.CharField(max_length=50, verbose_name="display name")), ], options={ - 'ordering': ('domain',), - 'db_table': 'django_site', - 'verbose_name': 'site', - 'verbose_name_plural': 'sites', + "ordering": ("domain",), + "db_table": "django_site", + "verbose_name": "site", + "verbose_name_plural": "sites", }, bases=(models.Model,), - managers=[ - ('objects', django.contrib.sites.models.SiteManager()), - ], - ), + managers=[("objects", django.contrib.sites.models.SiteManager())], + ) ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py index 6a26ebcd..2c8d6dac 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0002_alter_domain_unique.py @@ -4,17 +4,17 @@ from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ - ('sites', '0001_initial'), - ] + dependencies = [("sites", "0001_initial")] operations = [ migrations.AlterField( - model_name='site', - name='domain', + model_name="site", + name="domain", field=models.CharField( - max_length=100, unique=True, validators=[django.contrib.sites.models._simple_domain_name_validator], - verbose_name='domain name' + max_length=100, + unique=True, + validators=[django.contrib.sites.models._simple_domain_name_validator], + verbose_name="domain name", ), - ), + ) ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py index 05a47ba4..8f4a8f99 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/contrib/sites/migrations/0003_set_site_domain_and_name.py @@ -9,34 +9,26 @@ from django.db import migrations def update_site_forward(apps, schema_editor): """Set site domain and name.""" - Site = apps.get_model('sites', 'Site') + Site = apps.get_model("sites", "Site") Site.objects.update_or_create( id=settings.SITE_ID, defaults={ - 'domain': '{{cookiecutter.domain_name}}', - 'name': '{{cookiecutter.project_name}}' - } + "domain": "{{cookiecutter.domain_name}}", + "name": "{{cookiecutter.project_name}}", + }, ) def update_site_backward(apps, schema_editor): """Revert site domain and name to default.""" - Site = apps.get_model('sites', 'Site') + Site = apps.get_model("sites", "Site") Site.objects.update_or_create( - id=settings.SITE_ID, - defaults={ - 'domain': 'example.com', - 'name': 'example.com' - } + id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"} ) class Migration(migrations.Migration): - dependencies = [ - ('sites', '0002_alter_domain_unique'), - ] + dependencies = [("sites", "0002_alter_domain_unique")] - operations = [ - migrations.RunPython(update_site_forward, update_site_backward), - ] + operations = [migrations.RunPython(update_site_forward, update_site_backward)] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py index b31450ae..5b63593b 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/adapters.py @@ -4,10 +4,12 @@ from allauth.socialaccount.adapter import DefaultSocialAccountAdapter class AccountAdapter(DefaultAccountAdapter): + def is_open_for_signup(self, request): - return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True) + return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) class SocialAccountAdapter(DefaultSocialAccountAdapter): + def is_open_for_signup(self, request, sociallogin): - return getattr(settings, 'ACCOUNT_ALLOW_REGISTRATION', True) + return getattr(settings, "ACCOUNT_ALLOW_REGISTRATION", True) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py index 9b615121..8da8f86a 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/admin.py @@ -6,15 +6,16 @@ from .models import User class MyUserChangeForm(UserChangeForm): + class Meta(UserChangeForm.Meta): model = User class MyUserCreationForm(UserCreationForm): - error_message = UserCreationForm.error_messages.update({ - 'duplicate_username': 'This username has already been taken.' - }) + error_message = UserCreationForm.error_messages.update( + {"duplicate_username": "This username has already been taken."} + ) class Meta(UserCreationForm.Meta): model = User @@ -25,15 +26,14 @@ class MyUserCreationForm(UserCreationForm): User.objects.get(username=username) except User.DoesNotExist: return username - raise forms.ValidationError(self.error_messages['duplicate_username']) + + raise forms.ValidationError(self.error_messages["duplicate_username"]) @admin.register(User) class MyUserAdmin(AuthUserAdmin): form = MyUserChangeForm add_form = MyUserCreationForm - fieldsets = ( - ('User Profile', {'fields': ('name',)}), - ) + AuthUserAdmin.fieldsets - list_display = ('username', 'name', 'is_superuser') - search_fields = ['name'] + fieldsets = (("User Profile", {"fields": ("name",)}),) + AuthUserAdmin.fieldsets + list_display = ("username", "name", "is_superuser") + search_fields = ["name"] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py index 24c319df..118bfda9 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/apps.py @@ -2,7 +2,7 @@ from django.apps import AppConfig class UsersConfig(AppConfig): - name = '{{cookiecutter.project_slug}}.users' + name = "{{cookiecutter.project_slug}}.users" verbose_name = "Users" def ready(self): diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py index 7cc96f62..c9d89056 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/migrations/0001_initial.py @@ -8,36 +8,125 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ('auth', '0008_alter_user_username_max_length'), - ] + dependencies = [("auth", "0008_alter_user_username_max_length")] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('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')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), - ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), - ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), - ('email', models.EmailField(blank=True, max_length=254, 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')), - ('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')), - ('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')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("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", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=30, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, 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" + ), + ), + ( + "name", + models.CharField( + blank=True, max_length=255, verbose_name="Name of User" + ), + ), + ( + "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', - 'abstract': False, + "verbose_name_plural": "users", + "verbose_name": "user", + "abstract": False, }, - managers=[ - ('objects', django.contrib.auth.models.UserManager()), - ], - ), + managers=[("objects", django.contrib.auth.models.UserManager())], + ) ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py index 4b1a10d1..30475871 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/models.py @@ -8,10 +8,10 @@ class User(AbstractUser): # First Name and Last Name do not cover name patterns # around the globe. - name = models.CharField(_('Name of User'), blank=True, max_length=255) + name = models.CharField(_("Name of User"), blank=True, max_length=255) def __str__(self): return self.username def get_absolute_url(self): - return reverse('users:detail', kwargs={'username': self.username}) + return reverse("users:detail", kwargs={"username": self.username}) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py index a777ae9e..8a871b64 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/factories.py @@ -2,10 +2,10 @@ import factory class UserFactory(factory.django.DjangoModelFactory): - username = factory.Sequence(lambda n: f'user-{n}') - email = factory.Sequence(lambda n: f'user-{n}@example.com') - password = factory.PostGenerationMethodCall('set_password', 'password') + username = factory.Sequence(lambda n: f"user-{n}") + email = factory.Sequence(lambda n: f"user-{n}@example.com") + password = factory.PostGenerationMethodCall("set_password", "password") class Meta: - model = 'users.User' - django_get_or_create = ('username', ) + model = "users.User" + django_get_or_create = ("username",) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py index a1ff0b84..a3307103 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_admin.py @@ -6,30 +6,34 @@ from ..admin import MyUserCreationForm class TestMyUserCreationForm(TestCase): def setUp(self): - self.user = self.make_user('notalamode', 'notalamodespassword') + self.user = self.make_user("notalamode", "notalamodespassword") def test_clean_username_success(self): # Instantiate the form with a new username - form = MyUserCreationForm({ - 'username': 'alamode', - 'password1': '7jefB#f@Cc7YJB]2v', - 'password2': '7jefB#f@Cc7YJB]2v', - }) + form = MyUserCreationForm( + { + "username": "alamode", + "password1": "7jefB#f@Cc7YJB]2v", + "password2": "7jefB#f@Cc7YJB]2v", + } + ) # Run is_valid() to trigger the validation valid = form.is_valid() self.assertTrue(valid) # Run the actual clean_username method username = form.clean_username() - self.assertEqual('alamode', username) + self.assertEqual("alamode", username) def test_clean_username_false(self): # Instantiate the form with the same username as self.user - form = MyUserCreationForm({ - 'username': self.user.username, - 'password1': 'notalamodespassword', - 'password2': 'notalamodespassword', - }) + form = MyUserCreationForm( + { + "username": self.user.username, + "password1": "notalamodespassword", + "password2": "notalamodespassword", + } + ) # Run is_valid() to trigger the validation, which is going to fail # because the username is already taken valid = form.is_valid() @@ -37,4 +41,4 @@ class TestMyUserCreationForm(TestCase): # The form.errors dict should contain a single error called 'username' self.assertTrue(len(form.errors) == 1) - self.assertTrue('username' in form.errors) + self.assertTrue("username" in form.errors) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py index 894ed183..13121a01 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_models.py @@ -9,11 +9,8 @@ class TestUser(TestCase): def test__str__(self): self.assertEqual( self.user.__str__(), - 'testuser' # This is the default username for self.make_user() + "testuser", # This is the default username for self.make_user() ) def test_get_absolute_url(self): - self.assertEqual( - self.user.get_absolute_url(), - '/users/testuser/' - ) + self.assertEqual(self.user.get_absolute_url(), "/users/testuser/") diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py index 4935b0f3..6b072436 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_urls.py @@ -11,41 +11,34 @@ class TestUserURLs(TestCase): def test_list_reverse(self): """users:list should reverse to /users/.""" - self.assertEqual(reverse('users:list'), '/users/') + self.assertEqual(reverse("users:list"), "/users/") def test_list_resolve(self): """/users/ should resolve to users:list.""" - self.assertEqual(resolve('/users/').view_name, 'users:list') + self.assertEqual(resolve("/users/").view_name, "users:list") def test_redirect_reverse(self): """users:redirect should reverse to /users/~redirect/.""" - self.assertEqual(reverse('users:redirect'), '/users/~redirect/') + self.assertEqual(reverse("users:redirect"), "/users/~redirect/") def test_redirect_resolve(self): """/users/~redirect/ should resolve to users:redirect.""" - self.assertEqual( - resolve('/users/~redirect/').view_name, - 'users:redirect' - ) + self.assertEqual(resolve("/users/~redirect/").view_name, "users:redirect") def test_detail_reverse(self): """users:detail should reverse to /users/testuser/.""" self.assertEqual( - reverse('users:detail', kwargs={'username': 'testuser'}), - '/users/testuser/' + reverse("users:detail", kwargs={"username": "testuser"}), "/users/testuser/" ) def test_detail_resolve(self): """/users/testuser/ should resolve to users:detail.""" - self.assertEqual(resolve('/users/testuser/').view_name, 'users:detail') + self.assertEqual(resolve("/users/testuser/").view_name, "users:detail") def test_update_reverse(self): """users:update should reverse to /users/~update/.""" - self.assertEqual(reverse('users:update'), '/users/~update/') + self.assertEqual(reverse("users:update"), "/users/~update/") def test_update_resolve(self): """/users/~update/ should resolve to users:update.""" - self.assertEqual( - resolve('/users/~update/').view_name, - 'users:update' - ) + self.assertEqual(resolve("/users/~update/").view_name, "users:update") diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py index 23f30f03..1fa45697 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/tests/test_views.py @@ -2,10 +2,7 @@ from django.test import RequestFactory from test_plus.test import TestCase -from ..views import ( - UserRedirectView, - UserUpdateView -) +from ..views import (UserRedirectView, UserUpdateView) class BaseUserTestCase(TestCase): @@ -21,17 +18,14 @@ class TestUserRedirectView(BaseUserTestCase): # Instantiate the view directly. Never do this outside a test! view = UserRedirectView() # Generate a fake request - request = self.factory.get('/fake-url') + request = self.factory.get("/fake-url") # Attach the user to the request request.user = self.user # Attach the request to the view view.request = request # Expect: '/users/testuser/', as that is the default username for # self.make_user() - self.assertEqual( - view.get_redirect_url(), - '/users/testuser/' - ) + self.assertEqual(view.get_redirect_url(), "/users/testuser/") class TestUserUpdateView(BaseUserTestCase): @@ -42,7 +36,7 @@ class TestUserUpdateView(BaseUserTestCase): # Instantiate the view directly. Never do this outside a test! self.view = UserUpdateView() # Generate a fake request - request = self.factory.get('/fake-url') + request = self.factory.get("/fake-url") # Attach the user to the request request.user = self.user # Attach the request to the view @@ -51,14 +45,8 @@ class TestUserUpdateView(BaseUserTestCase): def test_get_success_url(self): # Expect: '/users/testuser/', as that is the default username for # self.make_user() - self.assertEqual( - self.view.get_success_url(), - '/users/testuser/' - ) + self.assertEqual(self.view.get_success_url(), "/users/testuser/") def test_get_object(self): # Expect: self.user, as that is the request's user object - self.assertEqual( - self.view.get_object(), - self.user - ) + self.assertEqual(self.view.get_object(), self.user) diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py index 1e161836..69414f47 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/urls.py @@ -2,26 +2,14 @@ from django.conf.urls import url from . import views -app_name = 'users' +app_name = "users" urlpatterns = [ + url(regex=r"^$", view=views.UserListView.as_view(), name="list"), + url(regex=r"^~redirect/$", view=views.UserRedirectView.as_view(), name="redirect"), + url(regex=r"^~update/$", view=views.UserUpdateView.as_view(), name="update"), url( - regex=r'^$', - view=views.UserListView.as_view(), - name='list' - ), - url( - regex=r'^~redirect/$', - view=views.UserRedirectView.as_view(), - name='redirect' - ), - url( - regex=r'^~update/$', - view=views.UserUpdateView.as_view(), - name='update' - ), - url( - regex=r'^(?P[\w.@+-]+)/$', + regex=r"^(?P[\w.@+-]+)/$", view=views.UserDetailView.as_view(), - name='detail' + name="detail", ), ] diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py index acde4a8f..a9038b71 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py +++ b/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/users/views.py @@ -8,29 +8,28 @@ from .models import User class UserDetailView(LoginRequiredMixin, DetailView): model = User # These next two lines tell the view to index lookups by username - slug_field = 'username' - slug_url_kwarg = 'username' + slug_field = "username" + slug_url_kwarg = "username" class UserRedirectView(LoginRequiredMixin, RedirectView): permanent = False def get_redirect_url(self): - return reverse('users:detail', - kwargs={'username': self.request.user.username}) + return reverse("users:detail", kwargs={"username": self.request.user.username}) class UserUpdateView(LoginRequiredMixin, UpdateView): - fields = ['name', ] + fields = ["name"] # we already imported User in the view code above, remember? model = User # send the user back to their own page after a successful update + def get_success_url(self): - return reverse('users:detail', - kwargs={'username': self.request.user.username}) + return reverse("users:detail", kwargs={"username": self.request.user.username}) def get_object(self): # Only get the User record for the user making the request @@ -40,5 +39,5 @@ class UserUpdateView(LoginRequiredMixin, UpdateView): class UserListView(LoginRequiredMixin, ListView): model = User # These next two lines tell the view to index lookups by username - slug_field = 'username' - slug_url_kwarg = 'username' + slug_field = "username" + slug_url_kwarg = "username"