Merge branch 'master' of github.com:sfdye/cookiecutter-django into use-aws-roles

This commit is contained in:
Wan Liuyang 2018-02-28 10:16:48 +08:00
commit 39d2000856
40 changed files with 411 additions and 286 deletions

View File

@ -2,6 +2,10 @@
All enhancements and patches to Cookiecutter Django will be documented in this file. All enhancements and patches to Cookiecutter Django will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/). This project adheres to [Semantic Versioning](http://semver.org/).
## [2018-02-16]
### Changed
- Upgraded to Django 2.0 (@epicwhale)
## [2018-01-15] ## [2018-01-15]
### Changed ### Changed
- Removed Elastic Beanstalk support (@pydanny) - Removed Elastic Beanstalk support (@pydanny)

View File

@ -36,13 +36,12 @@ To run all tests using various versions of python in virtualenvs defined in tox.
$ tox $ tox
It is possible to tests with some versions of python, to do this the command It is possible to test with a specific version of python. To do this, the command
is:: is::
$ tox -e py34,py35 $ tox -e py36
Will run py.test with the python3.4, and python3.5 interpreters, for This will run py.test with the python3.6 interpreter, for example.
example.
To run a particular test with tox for against your current Python version:: To run a particular test with tox for against your current Python version::

View File

@ -38,10 +38,10 @@ production-ready Django projects quickly.
Features Features
--------- ---------
* For Django 1.11 * For Django 2.0
* Works with Python 3.6 * Works with Python 3.6
* Renders Django projects with 100% starting test coverage * Renders Django projects with 100% starting test coverage
* Twitter Bootstrap_ v4.0.0 - beta 1 (`maintained Foundation fork`_ also available) * Twitter Bootstrap_ v4.0.0 (`maintained Foundation fork`_ also available)
* 12-Factor_ based settings via django-environ_ * 12-Factor_ based settings via django-environ_
* Secure by default. We believe in SSL. * Secure by default. We believe in SSL.
* Optimized development and production settings * Optimized development and production settings
@ -111,7 +111,7 @@ Two Scoops of Django 1.11
:name: Two Scoops of Django 1.11 Cover :name: Two Scoops of Django 1.11 Cover
:align: center :align: center
:alt: Two Scoops of Django :alt: Two Scoops of Django
:target: http://twoscoopspress.org/products/two-scoops-of-django-1-11 :target: http://twoscoopspress.com/products/two-scoops-of-django-1-11
Two Scoops of Django is the best dessert-themed Django reference in the universe Two Scoops of Django is the best dessert-themed Django reference in the universe

View File

@ -35,7 +35,7 @@ Make sure your project is fully commited and pushed up to Bitbucket or Github or
git clone <my-repo-url> # you can also use hg git clone <my-repo-url> # you can also use hg
cd my-project-name cd my-project-name
mkvirtualenv --python=/usr/bin/python3.5 my-project-name mkvirtualenv --python=/usr/bin/python3.6 my-project-name
pip install -r requirements/production.txt # may take a few minutes pip install -r requirements/production.txt # may take a few minutes

View File

@ -22,7 +22,7 @@ DJANGO_ADMIN_URL n/a r'^admin/'
DJANGO_CACHES CACHES (default) locmem redis DJANGO_CACHES CACHES (default) locmem redis
DJANGO_DATABASES DATABASES (default) See code See code DJANGO_DATABASES DATABASES (default) See code See code
DJANGO_DEBUG DEBUG True False DJANGO_DEBUG DEBUG True False
DJANGO_SECRET_KEY SECRET_KEY CHANGEME!!! raises error DJANGO_SECRET_KEY SECRET_KEY !!!SET DJANGO_SECRET_KEY!!! raises error
DJANGO_SECURE_BROWSER_XSS_FILTER SECURE_BROWSER_XSS_FILTER n/a True DJANGO_SECURE_BROWSER_XSS_FILTER SECURE_BROWSER_XSS_FILTER n/a True
DJANGO_SECURE_SSL_REDIRECT SECURE_SSL_REDIRECT n/a True DJANGO_SECURE_SSL_REDIRECT SECURE_SSL_REDIRECT n/a True
DJANGO_SECURE_CONTENT_TYPE_NOSNIFF SECURE_CONTENT_TYPE_NOSNIFF n/a True DJANGO_SECURE_CONTENT_TYPE_NOSNIFF SECURE_CONTENT_TYPE_NOSNIFF n/a True

View File

@ -1,219 +1,257 @@
""" """
Does the following: NOTE:
the below code is to be maintained Python 2.x-compatible
as the whole Cookiecutter Django project initialization
can potentially be run in Python 2.x environment
(at least so we presume in `pre_gen_project.py`).
1. Generates and saves random secret key TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only
2. Removes the taskapp if celery isn't going to be used
3. Removes the .idea directory if PyCharm isn't going to be used
4. Copy files from /docs/ to {{ cookiecutter.project_slug }}/docs/
TODO: this might have to be moved to a pre_gen_hook
A portion of this code was adopted from Django's standard crypto functions and
utilities, specifically:
https://github.com/django/django/blob/master/django/utils/crypto.py
""" """
from __future__ import print_function
import os import os
import random import random
import shutil import shutil
import string import string
import sys
# Get the root project directory
PROJECT_DIRECTORY = os.path.realpath(os.path.curdir)
# Use the system PRNG if possible
try: try:
# Inspired by
# https://github.com/django/django/blob/master/django/utils/crypto.py
random = random.SystemRandom() random = random.SystemRandom()
using_sysrandom = True using_sysrandom = True
except NotImplementedError: except NotImplementedError:
using_sysrandom = False using_sysrandom = False
PROJECT_DIR_PATH = os.path.realpath(os.path.curdir)
def get_random_string(length=50):
"""
Returns a securely generated random string.
The default length of 12 with the a-z, A-Z, 0-9 character set returns
a 71-bit value. log_2((26+26+10)^12) =~ 71 bits
"""
punctuation = string.punctuation.replace('"', '').replace("'", '')
punctuation = punctuation.replace('\\', '')
if using_sysrandom:
return ''.join(random.choice(
string.digits + string.ascii_letters + punctuation
) for i in range(length))
print(
"Cookiecutter Django couldn't find a secure pseudo-random number generator on your system."
" Please change change your SECRET_KEY variables in conf/settings/local.py and env.example"
" manually."
)
return "CHANGEME!!"
def set_secret_key(setting_file_location): def remove_file(file_path):
# Open locals.py if os.path.exists(file_path):
with open(setting_file_location) as f: os.remove(file_path)
file_ = f.read()
# Generate a SECRET_KEY that matches the Django standard
SECRET_KEY = get_random_string()
# Replace "CHANGEME!!!" with SECRET_KEY
file_ = file_.replace('CHANGEME!!!', SECRET_KEY, 1)
# Write the results to the locals.py module
with open(setting_file_location, 'w') as f:
f.write(file_)
def make_secret_key(project_directory): def remove_open_source_project_only_files():
"""Generates and saves random secret key""" file_names = [
# Determine the local_setting_file_location 'CONTRIBUTORS.txt',
local_setting = os.path.join( ]
project_directory, for file_name in file_names:
'config/settings/local.py' os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
)
# local.py settings file
set_secret_key(local_setting)
env_file = os.path.join(
project_directory,
'env.example'
)
# env.example file
set_secret_key(env_file)
def remove_file(file_name): def remove_gplv3_files():
if os.path.exists(file_name): file_names = [
os.remove(file_name) 'COPYING',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_task_app(project_directory): def remove_pycharm_files():
"""Removes the taskapp if celery isn't going to be used""" idea_dir_path = os.path.join(PROJECT_DIR_PATH, '.idea')
# Determine the local_setting_file_location if os.path.exists(idea_dir_path):
task_app_location = os.path.join( shutil.rmtree(idea_dir_path)
PROJECT_DIRECTORY,
'{{ cookiecutter.project_slug }}/taskapp'
)
shutil.rmtree(task_app_location)
docs_dir_path = os.path.join(PROJECT_DIR_PATH, 'docs', 'pycharm')
def remove_pycharm_dir(project_directory): if os.path.exists(docs_dir_path):
""" shutil.rmtree(docs_dir_path)
Removes directories related to PyCharm
if it isn't going to be used
"""
idea_dir_location = os.path.join(PROJECT_DIRECTORY, '.idea/')
if os.path.exists(idea_dir_location):
shutil.rmtree(idea_dir_location)
docs_dir_location = os.path.join(PROJECT_DIRECTORY, 'docs/pycharm/')
if os.path.exists(docs_dir_location):
shutil.rmtree(docs_dir_location)
def remove_heroku_files():
"""
Removes files needed for heroku if it isn't going to be used
"""
filenames = ["Procfile", "runtime.txt"]
for filename in ["Procfile", "runtime.txt"]:
file_name = os.path.join(PROJECT_DIRECTORY, filename)
remove_file(file_name)
def remove_docker_files(): def remove_docker_files():
""" shutil.rmtree(os.path.join(PROJECT_DIR_PATH, 'compose'))
Removes files needed for docker if it isn't going to be used
"""
for filename in ["local.yml", "production.yml", ".dockerignore"]:
filename = os.path.join(PROJECT_DIRECTORY, filename)
if os.path.exists(filename):
os.remove(filename)
shutil.rmtree(os.path.join( file_names = [
PROJECT_DIRECTORY, "compose" 'local.yml',
)) 'production.yml',
'.dockerignore',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_heroku_files():
file_names = [
'Procfile',
'runtime.txt',
]
for file_name in file_names:
remove_file(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_paas_files():
none_paas_files_left = True
if '{{ cookiecutter.use_heroku }}'.lower() == 'n':
remove_heroku_files()
none_paas_files_left &= True
else:
none_paas_files_left &= False
if none_paas_files_left:
remove_file(os.path.join(PROJECT_DIR_PATH, 'requirements.txt'))
def remove_grunt_files(): def remove_grunt_files():
""" file_names = [
Removes files needed for grunt if it isn't going to be used 'Gruntfile.js',
""" ]
for filename in ["Gruntfile.js"]: for file_name in file_names:
os.remove(os.path.join( os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
PROJECT_DIRECTORY, filename
))
def remove_gulp_files(): def remove_gulp_files():
file_names = [
'gulpfile.js',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_packagejson_file():
file_names = [
'package.json',
]
for file_name in file_names:
os.remove(os.path.join(PROJECT_DIR_PATH, file_name))
def remove_celery_app():
shutil.rmtree(os.path.join(PROJECT_DIR_PATH, '{{ cookiecutter.project_slug }}', 'taskapp'))
def append_to_project_gitignore(path):
gitignore_file_path = os.path.join(PROJECT_DIR_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):
""" """
Removes files needed for grunt if it isn't going to be used Example:
opting out for 50 symbol-long, [a-z][A-Z][0-9] string
would yield log_2((26+26+50)^50) ~= 334 bit strength.
""" """
for filename in ["gulpfile.js"]: if not using_sysrandom:
os.remove(os.path.join( return None
PROJECT_DIRECTORY, filename
))
def remove_packageJSON_file(): symbols = []
""" if using_digits:
Removes files needed for grunt if it isn't going to be used symbols += string.digits
""" if using_ascii_letters:
for filename in ["package.json"]: symbols += string.ascii_letters
os.remove(os.path.join( if using_punctuation:
PROJECT_DIRECTORY, filename symbols += string.punctuation \
)) .replace('"', '') \
.replace("'", '') \
def remove_copying_files(): .replace('\\', '')
""" return ''.join([random.choice(symbols) for _ in range(length)])
Removes files needed for the GPLv3 licence if it isn't going to be used
"""
for filename in ["COPYING"]:
os.remove(os.path.join(
PROJECT_DIRECTORY, filename
))
# IN PROGRESS def set_flag(file_path,
# def copy_doc_files(project_directory): flag,
# cookiecutters_dir = DEFAULT_CONFIG['cookiecutters_dir'] value=None,
# cookiecutter_django_dir = os.path.join( *args,
# cookiecutters_dir, **kwargs):
# 'cookiecutter-django', if value is None:
# 'docs' random_string = generate_random_string(*args, **kwargs)
# ) if random_string is None:
# target_dir = os.path.join( import sys
# project_directory, sys.stdout.write(
# 'docs' "We couldn't find a secure pseudo-random number generator on your system. "
# ) "Please, make sure to manually {} later.".format(flag)
# for name in os.listdir(cookiecutter_django_dir): )
# if name.endswith('.rst') and not name.startswith('index'): random_string = flag
# src = os.path.join(cookiecutter_django_dir, name) value = random_string
# dst = os.path.join(target_dir, name)
# shutil.copyfile(src, dst)
# 1. Generates and saves random secret key with open(file_path, 'r+') as f:
make_secret_key(PROJECT_DIRECTORY) file_contents = f.read().replace(flag, value)
f.seek(0)
f.write(file_contents)
f.truncate()
# 2. Removes the taskapp if celery isn't going to be used return value
if '{{ cookiecutter.use_celery }}'.lower() == 'n':
remove_task_app(PROJECT_DIRECTORY)
# 3. Removes the .idea directory if PyCharm isn't going to be used
if '{{ cookiecutter.use_pycharm }}'.lower() != 'y':
remove_pycharm_dir(PROJECT_DIRECTORY)
# 4. Removes all heroku files if it isn't going to be used def set_django_secret_key(file_path):
if '{{ cookiecutter.use_heroku }}'.lower() != 'y': django_secret_key = set_flag(
remove_heroku_files() file_path,
'!!!SET DJANGO_SECRET_KEY!!!',
length=50,
using_digits=True,
using_ascii_letters=True
)
return django_secret_key
# 5. Removes all docker files if it isn't going to be used
if '{{ cookiecutter.use_docker }}'.lower() != 'y': def set_postgres_user(file_path,
value=None):
postgres_user = set_flag(
file_path,
'!!!SET POSTGRES_USER!!!',
value=value,
length=8,
using_ascii_letters=True
)
return postgres_user
def set_postgres_password(file_path):
postgres_password = set_flag(
file_path,
'!!!SET POSTGRES_PASSWORD!!!',
length=42,
using_digits=True,
using_ascii_letters=True
)
return postgres_password
def initialize_dotenv(postgres_user):
# Initializing `env.example` first.
envexample_file_path = os.path.join(PROJECT_DIR_PATH, 'env.example')
set_django_secret_key(envexample_file_path)
set_postgres_user(envexample_file_path, value=postgres_user)
set_postgres_password(envexample_file_path)
# Renaming `env.example` to `.env`.
dotenv_file_path = os.path.join(PROJECT_DIR_PATH, '.env')
shutil.move(envexample_file_path, dotenv_file_path)
def initialize_localyml(postgres_user):
set_postgres_user(os.path.join(PROJECT_DIR_PATH, 'local.yml'), value=postgres_user)
def initialize_local_settings():
set_django_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'local.py'))
def initialize_test_settings():
set_django_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'test.py'))
def main():
postgres_user = generate_random_string(length=16, using_ascii_letters=True)
initialize_dotenv(postgres_user)
initialize_localyml(postgres_user)
initialize_local_settings()
initialize_test_settings()
if '{{ cookiecutter.open_source_license }}' == 'Not open source':
remove_open_source_project_only_files()
elif '{{ cookiecutter.open_source_license}}' != 'GPLv3':
remove_gplv3_files()
if '{{ cookiecutter.use_pycharm }}'.lower() == 'n':
remove_pycharm_files()
if '{{ cookiecutter.use_docker }}'.lower() == 'n':
remove_docker_files() remove_docker_files()
# 6. Removes all JS task manager files if it isn't going to be used remove_paas_files()
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':
@ -221,18 +259,25 @@ elif '{{ cookiecutter.js_task_runner}}'.lower() == 'grunt':
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'] \
# 9. Display a warning if use_docker and use_grunt are selected. Grunt isn't and '{{ cookiecutter.use_docker }}'.lower() == 'y':
# supported by our docker config atm. TERMINATOR = "\x1b[0m"
if '{{ cookiecutter.js_task_runner }}'.lower() in ['grunt', 'gulp'] and '{{ cookiecutter.use_docker }}'.lower() == 'y': INFO = "\x1b[1;33m [INFO]: "
print( sys.stdout.write(
"You selected to use docker and a JS task runner. This is NOT supported out of the box for now. You " INFO +
"can continue to use the project like you normally would, but you will need to add a " "Docker and {} JS task runner ".format('{{ cookiecutter.js_task_runner }}'.lower().capitalize()) +
"js task runner service to your docker configuration manually." "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
) )
# 10. Removes files needed for the GPLv3 licence if it isn't going to be used. if '{{ cookiecutter.use_celery }}'.lower() == 'n':
if '{{ cookiecutter.open_source_license}}' != 'GPLv3': remove_celery_app()
remove_copying_files()
if __name__ == '__main__':
main()

View File

@ -1,27 +1,62 @@
"""
NOTE:
the below code is to be maintained Python 2.x-compatible
as the whole Cookiecutter Django project initialization
can potentially be run in Python 2.x environment.
TODO: ? restrict Cookiecutter Django project initialization to Python 3.x environments only
"""
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 should be valid Python identifier!' assert project_slug.isidentifier(), "'{}' project slug is not a valid Python identifier.".format(project_slug)
heroku = '{{ cookiecutter.use_heroku }}'.lower() assert "\\" not in "{{ cookiecutter.author_name }}", "Don't include backslashes in author name."
docker = '{{ cookiecutter.use_docker }}'.lower()
using_docker = '{{ cookiecutter.use_docker }}'.lower()
if using_docker == 'n':
TERMINATOR = "\x1b[0m"
WARNING = "\x1b[1;33m [WARNING]: "
INFO = "\x1b[1;33m [INFO]: "
HINT = "\x1b[3;33m"
SUCCESS = "\x1b[1;32m [SUCCESS]: "
if docker == 'n':
import sys import sys
python_major_version = sys.version_info[0] python_major_version = sys.version_info[0]
if python_major_version == 2: if python_major_version == 2:
sys.stdout.write("WARNING: Cookiecutter Django does not support Python 2! Stability is guaranteed with Python 3.4+ only. Are you sure you want to proceed? (y/n)") sys.stdout.write(
WARNING +
yes_options = set(['y']) "Cookiecutter Django does not support Python 2. "
no_options = set(['n', '']) "Stability is guaranteed with Python 3.6+ only, "
"are you sure you want to proceed (y/n)? " +
TERMINATOR
)
yes_options, no_options = frozenset(['y']), frozenset(['n'])
while True:
choice = raw_input().lower() choice = raw_input().lower()
if choice in no_options: if choice in yes_options:
break
elif choice in no_options:
sys.stdout.write(
INFO +
"Generation process stopped as requested." +
TERMINATOR
)
sys.exit(1) sys.exit(1)
elif choice in yes_options:
pass
else: else:
sys.stdout.write("Please respond with %s or %s" sys.stdout.write(
% (', '.join([o for o in yes_options if not o == '']) HINT +
, ', '.join([o for o in no_options if not o == '']))) "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
)
sys.stdout.write(
SUCCESS +
"Project initialized, keep up the good work!" +
TERMINATOR
)

View File

@ -4,7 +4,7 @@ sh==1.12.14
binaryornot==0.4.4 binaryornot==0.4.4
# Testing # Testing
pytest==3.3.2 pytest==3.4.1
pycodestyle==2.3.1 pycodestyle==2.3.1
pyflakes==1.6.0 pyflakes==1.6.0
tox==2.9.1 tox==2.9.1

View File

@ -1,4 +1,2 @@
# These requirements prevented an upgrade to Django 1.10. # These requirements prevented an upgrade to Django 1.11.
django-coverage-plugin==1.5.0
django-autoslug==1.9.3 django-autoslug==1.9.3

View File

@ -10,7 +10,7 @@ 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 = '1.11.9' 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))
@ -34,16 +34,14 @@ setup(
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
'Environment :: Console', 'Environment :: Console',
'Framework :: Django :: 1.10', '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.4', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Software Development', 'Topic :: Software Development',
], ],
keywords=( keywords=(

View File

@ -19,3 +19,6 @@ docker-compose -f local.yml run django python manage.py test
# return non-zero status code if there are migrations that have not been created # return non-zero status code if there are migrations that have not been created
docker-compose -f local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; } docker-compose -f local.yml run django python manage.py makemigrations --dry-run --check || { echo "ERROR: there were changes in the models, but migration listed above have not been created and are not saved in version control"; exit 1; }
# Test support for translations
docker-compose -f local.yml run --rm django python manage.py makemessages

View File

@ -8,4 +8,4 @@ before_install:
- sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm - sudo apt-get install -qq libsqlite3-dev libxml2 libxml2-dev libssl-dev libbz2-dev wget curl llvm
language: python language: python
python: python:
- "3.5" - "3.6"

View File

@ -1,7 +1,18 @@
FROM python:3.5 FROM python:3.6-alpine
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
RUN apk update \
# psycopg2 dependencies
&& apk add --virtual build-deps gcc python3-dev musl-dev \
&& apk add postgresql-dev \
# Pillow dependencies
&& apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
# CFFI dependencies
&& apk add libffi-dev openssl-dev py-cffi \
# Translations dependencies
&& apk add gettext
# Requirements have to be pulled and installed here, otherwise caching won't work # Requirements have to be pulled and installed here, otherwise caching won't work
COPY ./requirements /requirements COPY ./requirements /requirements
RUN pip install -r /requirements/local.txt RUN pip install -r /requirements/local.txt

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/bin/sh
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/bin/sh
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/bin/sh
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -1,9 +1,18 @@
FROM python:3.5 FROM python:3.6-alpine
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
RUN groupadd -r django \ RUN apk update \
&& useradd -r -g django django # psycopg2 dependencies
&& apk add --virtual build-deps gcc python3-dev musl-dev \
&& apk add postgresql-dev \
# Pillow dependencies
&& apk add jpeg-dev zlib-dev freetype-dev lcms2-dev openjpeg-dev tiff-dev tk-dev tcl-dev \
# CFFI dependencies
&& apk add libffi-dev openssl-dev py-cffi
RUN addgroup -S django \
&& adduser -S -G django django
# Requirements have to be pulled and installed here, otherwise caching won't work # Requirements have to be pulled and installed here, otherwise caching won't work
COPY ./requirements /requirements COPY ./requirements /requirements

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/bin/sh
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/bin/sh
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/bin/sh
set -o errexit set -o errexit
set -o pipefail set -o pipefail
@ -25,7 +25,7 @@ export DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@postgres:5432/$
export CELERY_BROKER_URL=$REDIS_URL/0 export CELERY_BROKER_URL=$REDIS_URL/0
{% endif %} {% endif %}
function postgres_ready(){ postgres_ready() {
python << END python << END
import sys import sys
import psycopg2 import psycopg2

View File

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/bin/sh
set -o errexit set -o errexit
set -o pipefail set -o pipefail

View File

@ -24,7 +24,7 @@ TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
# Note: This key only used for development and testing. # Note: This key only used for development and testing.
SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!') SECRET_KEY = env('DJANGO_SECRET_KEY', default='!!!SET DJANGO_SECRET_KEY!!!')
# Mail settings # Mail settings
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -103,12 +103,8 @@ AWS_QUERYSTRING_AUTH = False
# AWS cache settings, don't change unless you know what you're doing: # AWS cache settings, don't change unless you know what you're doing:
AWS_EXPIRY = 60 * 60 * 24 * 7 AWS_EXPIRY = 60 * 60 * 24 * 7
# TODO See: https://github.com/jschneier/django-storages/issues/47 AWS_S3_OBJECT_PARAMETERS = {
# Revert the following and use str after the above-mentioned bug is fixed in 'CacheControl': 'max-age=%d, s-maxage=%d, must-revalidate' % (AWS_EXPIRY, AWS_EXPIRY),
# either django-storage-redux or boto
control = 'max-age=%d, s-maxage=%d, must-revalidate' % (AWS_EXPIRY, AWS_EXPIRY)
AWS_HEADERS = {
'Cache-Control': bytes(control, encoding='latin-1')
} }
# URL that handles the media served from MEDIA_ROOT, used for managing # URL that handles the media served from MEDIA_ROOT, used for managing
@ -177,7 +173,7 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
DATABASES['default'] = env.db('DATABASE_URL') DATABASES['default'] = env.db('DATABASE_URL')
DATABASES['default']['CONN_MAX_AGE'] = env.int('CONN_MAX_AGE', default={{ _DEFAULT_CONN_MAX_AGE }}) DATABASES['default']['CONN_MAX_AGE'] = env.int('CONN_MAX_AGE', default={{ _DEFAULT_CONN_MAX_AGE }})
DATABASES['default']['ATOMIC_REQUESTS'] = True
# CACHING # CACHING
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -17,7 +17,7 @@ TEMPLATES[0]['OPTIONS']['debug'] = False
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
# Note: This key only used for development and testing. # Note: This key only used for development and testing.
SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!') SECRET_KEY = env('DJANGO_SECRET_KEY', default='!!!SET DJANGO_SECRET_KEY!!!')
# Mail settings # Mail settings
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -58,7 +58,7 @@ The `Docker compose documentation`_ explains in detail what you can accomplish i
build: database build: database
webapp: webapp:
build: webapp: build: webapp:
command: /usr/bin/python3.4 manage.py runserver 0.0.0.0:8000 # dev setting command: /usr/bin/python3.6 manage.py runserver 0.0.0.0:8000 # dev setting
# command: gunicorn -b 0.0.0.0:8000 wsgi:application # production setting # command: gunicorn -b 0.0.0.0:8000 wsgi:application # production setting
volumes: volumes:
- webapp/your_project_name:/path/to/container/workdir/ - webapp/your_project_name:/path/to/container/workdir/

View File

@ -1,7 +1,7 @@
# PostgreSQL # PostgreSQL
POSTGRES_PASSWORD=mysecretpass POSTGRES_PASSWORD=!!!SET POSTGRES_PASSWORD!!!
POSTGRES_USER=postgresuser POSTGRES_USER=!!!SET POSTGRES_USER!!!
CONN_MAX_AGE= CONN_MAX_AGE=
# Domain name, used by caddy # Domain name, used by caddy
@ -11,7 +11,7 @@ DOMAIN_NAME={{ cookiecutter.domain_name }}
# DJANGO_READ_DOT_ENV_FILE=True # DJANGO_READ_DOT_ENV_FILE=True
DJANGO_ADMIN_URL= DJANGO_ADMIN_URL=
DJANGO_SETTINGS_MODULE=config.settings.production DJANGO_SETTINGS_MODULE=config.settings.production
DJANGO_SECRET_KEY=CHANGEME!!! DJANGO_SECRET_KEY=!!!SET DJANGO_SECRET_KEY!!!
DJANGO_ALLOWED_HOSTS=.{{ cookiecutter.domain_name }} DJANGO_ALLOWED_HOSTS=.{{ cookiecutter.domain_name }}
# AWS Settings # AWS Settings

View File

@ -15,7 +15,7 @@ services:
volumes: volumes:
- .:/app - .:/app
environment: environment:
- POSTGRES_USER={{cookiecutter.project_slug}} - POSTGRES_USER=!!!SET POSTGRES_USER!!!
- USE_DOCKER=yes - USE_DOCKER=yes
ports: ports:
- "8000:8000" - "8000:8000"
@ -29,7 +29,7 @@ services:
- postgres_data_local:/var/lib/postgresql/data - postgres_data_local:/var/lib/postgresql/data
- postgres_backup_local:/backups - postgres_backup_local:/backups
environment: environment:
- POSTGRES_USER={{cookiecutter.project_slug}} - POSTGRES_USER=!!!SET POSTGRES_USER!!!
{% if cookiecutter.use_mailhog == 'y' %} {% if cookiecutter.use_mailhog == 'y' %}
mailhog: mailhog:
image: mailhog/mailhog:v1.0.0 image: mailhog/mailhog:v1.0.0

View File

@ -0,0 +1,6 @@
Translations
============
Translations will be placed in this folder when running::
python manage.py makemessages

View File

@ -6,7 +6,7 @@ wheel==0.30.0
# Conservative Django # Conservative Django
django==1.11.10 # pyup: <2.0 django==2.0.2 # pyup: < 2.1
# Configuration # Configuration
django-environ==0.4.4 django-environ==0.4.4
@ -36,21 +36,21 @@ django-allauth==0.35.0
# from http://www.lfd.uci.edu/~gohlke/pythonlibs/#psycopg # from http://www.lfd.uci.edu/~gohlke/pythonlibs/#psycopg
{% else %} {% else %}
# Python-PostgreSQL Database Adapter # Python-PostgreSQL Database Adapter
psycopg2==2.7.3.2 psycopg2==2.7.4 --no-binary psycopg2
{%- endif %} {%- endif %}
# Unicode slugification # Unicode slugification
awesome-slugify==1.6.5 awesome-slugify==1.6.5
# Time zones support # Time zones support
pytz==2017.3 pytz==2018.3
# Redis support # Redis support
django-redis==4.8.0 django-redis==4.8.0
redis>=2.10.5 redis>=2.10.5
{% if cookiecutter.use_celery == "y" %} {% if cookiecutter.use_celery == "y" %}
celery==3.1.25 celery==3.1.25 # pyup: <4.0
{% endif %} {% endif %}
{% if cookiecutter.use_compressor == "y" %} {% if cookiecutter.use_compressor == "y" %}

View File

@ -1,11 +1,11 @@
# Local development dependencies go here # Local development dependencies go here
-r base.txt -r base.txt
coverage==4.5 coverage==4.5.1
django-coverage-plugin==1.5.0 django-coverage-plugin==1.5.0
Sphinx==1.6.7 Sphinx==1.7.1
django-extensions==1.9.9 django-extensions==2.0.0
Werkzeug==0.14.1 Werkzeug==0.14.1
django-test-plus==1.0.22 django-test-plus==1.0.22
factory-boy==2.10.0 factory-boy==2.10.0
@ -13,7 +13,7 @@ factory-boy==2.10.0
django-debug-toolbar==1.9.1 django-debug-toolbar==1.9.1
# improved REPL # improved REPL
ipdb==0.10.3 ipdb==0.11
pytest-django==3.1.2 pytest-django==3.1.2
pytest-sugar==0.9.0 pytest-sugar==0.9.1

View File

@ -6,7 +6,7 @@
# Python-PostgreSQL Database Adapter # Python-PostgreSQL Database Adapter
# Assuming Windows is used locally, and *nix -- in production. # Assuming Windows is used locally, and *nix -- in production.
# ------------------------------------------------------------ # ------------------------------------------------------------
psycopg2==2.7.3.2 psycopg2==2.7.4 --no-binary psycopg2
{%- endif %} {%- endif %}
# WSGI Handler # WSGI Handler
@ -16,7 +16,7 @@ gunicorn==19.7.1
# Static and Media Storage # Static and Media Storage
# ------------------------------------------------ # ------------------------------------------------
boto3==1.5.22 boto3==1.5.36
django-storages==1.6.5 django-storages==1.6.5
{% if cookiecutter.use_whitenoise != 'y' -%} {% if cookiecutter.use_whitenoise != 'y' -%}
Collectfast==0.6.0 Collectfast==0.6.0
@ -24,7 +24,7 @@ Collectfast==0.6.0
# Email backends for Mailgun, Postmark, SendGrid and more # Email backends for Mailgun, Postmark, SendGrid and more
# ------------------------------------------------------- # -------------------------------------------------------
django-anymail==1.3 django-anymail==1.4
{% if cookiecutter.use_sentry_for_error_reporting == "y" -%} {% if cookiecutter.use_sentry_for_error_reporting == "y" -%}
# Raven is the Sentry client # Raven is the Sentry client

View File

@ -4,10 +4,10 @@
{% if cookiecutter.windows == 'y' -%} {% if cookiecutter.windows == 'y' -%}
# Python-PostgreSQL Database Adapter # Python-PostgreSQL Database Adapter
# If using Win for dev, this assumes Unix in test/prod # If using Win for dev, this assumes Unix in test/prod
psycopg2==2.7.3.2 psycopg2==2.7.4
{%- endif %} {%- endif %}
coverage==4.5 coverage==4.5.1
flake8==3.5.0 # pyup: != 2.6.0 flake8==3.5.0 # pyup: != 2.6.0
django-test-plus==1.0.22 django-test-plus==1.0.22
factory-boy==2.10.0 factory-boy==2.10.0
@ -15,4 +15,4 @@ django-coverage-plugin==1.5.0
# pytest # pytest
pytest-django==3.1.2 pytest-django==3.1.2
pytest-sugar==0.9.0 pytest-sugar==0.9.1

View File

@ -1 +1 @@
python-3.6.2 python-3.6.4

View File

@ -0,0 +1,23 @@
##basic build dependencies of various Django apps for Debian Jessie 9.x
#build-essential metapackage install: make, gcc, g++,
build-essential
#required to translate
gettext
python3-dev
##shared dependencies of:
##Pillow, pylibmc
zlib1g-dev
##Postgresql and psycopg2 dependencies
libpq-dev
##Pillow dependencies
libtiff5-dev
libjpeg62-turbo-dev
libfreetype6-dev
liblcms2-dev
libwebp-dev
##django-extensions
graphviz-dev

View File

@ -22,7 +22,7 @@
<!-- Your stuff: Third-party CSS libraries go here --> <!-- Your stuff: Third-party CSS libraries go here -->
{% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress css %}{% endraw %}{% endif %}{% raw %} {% endraw %}{% if cookiecutter.use_compressor == "y" %}{% raw %}{% compress css %}{% endraw %}{% endif %}{% raw %}
<!-- This file stores project-specific CSS --> <!-- This file stores project-specific CSS -->
{% endraw %}{% if cookiecutter.js_task_runner == "Gulp" %}{% raw %} {% endraw %}{% if cookiecutter.js_task_runner == "Gulp" and cookiecutter.use_compressor == "n" %}{% raw %}
<link href="{% static 'css/project.min.css' %}" rel="stylesheet"> <link href="{% static 'css/project.min.css' %}" rel="stylesheet">
{% endraw %}{% else %}{% raw %} {% endraw %}{% else %}{% raw %}
<link href="{% static 'css/project.css' %}" rel="stylesheet"> <link href="{% static 'css/project.css' %}" rel="stylesheet">

View File

@ -22,7 +22,7 @@ class Migration(migrations.Migration):
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), ('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')), ('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')), ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=30, verbose_name='last 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')), ('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_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')), ('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')),

View File

@ -1,11 +1,9 @@
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core.urlresolvers import reverse
from django.db import models from django.db import models
from django.utils.encoding import python_2_unicode_compatible from django.urls import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@python_2_unicode_compatible
class User(AbstractUser): class User(AbstractUser):
# First Name and Last Name do not cover name patterns # First Name and Last Name do not cover name patterns

View File

@ -1,4 +1,4 @@
from django.core.urlresolvers import reverse, resolve from django.urls import reverse, resolve
from test_plus.test import TestCase from test_plus.test import TestCase

View File

@ -2,6 +2,7 @@ from django.conf.urls import url
from . import views from . import views
app_name = 'users'
urlpatterns = [ urlpatterns = [
url( url(
regex=r'^$', regex=r'^$',

View File

@ -1,7 +1,6 @@
from django.core.urlresolvers import reverse
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from .models import User from .models import User