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