Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Srinivas Nyayapati 2016-10-17 17:26:54 -04:00
commit 9d9e799ba5
49 changed files with 566 additions and 192 deletions

View File

@ -21,3 +21,7 @@ trim_trailing_whitespace = false
[Makefile] [Makefile]
indent_style = tab indent_style = tab
[nginx.conf]
indent_style = space
indent_size = 2

View File

@ -2,6 +2,38 @@
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/).
## [2015-10-08]
### Changed
- Elastic Beanstalk: Added --noinput to migrate command (@MightySCollins )
## [2015-10-07]
### Added
- Finished first pass at Elastic Beanstalk docs (@pydanny & @audreyr)
### Deleted
- Removed Heroku instant deploy button (@pydanny)
##[2016-09-29]
### Added
- Added default `AUTH_PASSWORD_VALIDATORS` configuration, generated by django 1.10 startproject. See [Password Validation docs](https://docs.djangoproject.com/en/1.10/topics/auth/passwords/#module-django.contrib.auth.password_validation") (@luzfcb)
- Rename `MIDDLEWARE_CLASSES` to `MIDDLEWARE` to enable support to [new style middleware](https://github.com/django/deps/blob/master/final/0005-improved-middleware.rst) introduced in Django 1.10 (@luzfcb)
- New setting `MAILGUN_SENDER_DOMAIN` to allow sending mail from any domain other than those registered with mailgun (@jangeador)
- add `urlpatterns` configuration to django-debug-toolbar, because the automatic configuration of `urlpatterns` was removed from django-debug-toolbar (@luzfcb)
- Added Temporary workaround on `requirements/local.txt` to fix django-debug-toolbar issue: https://github.com/pydanny/cookiecutter-django/issues/827 (@luzfcb)
### Changed
- Upgrade to Django 1.10.1 (@luzfcb)
- Upgrade django-model-utils to 2.6, django-redis to 4.5.0, redis to 2.10.5, Sphinx to 1.4.6, pytest-django to 3.0.0, django-anymail to 0.5, raven to 5.27.1, whitenoise to 3.2.2 (@luzfcb)
- Upgrade to Bootstrap 4 Alpha 4, jQuery to 3.1.1, tether.js to 1.3.7 (@luzfcb)
- Update `manage.py` to use same code of `manage.py` from Django 1.10 (@luzfcb)
- Sync `sites` app migrations with django 1.10, and fix aditional migrations to `sites` and `user` app (@luzfcb)
d changed 'admin' url on `config/urls.py`, to stay the same as generated by django 1.10 (@luzfcb)
- Make test_docker.sh tests pass by passing new password auth rules (@ssteinerx)
### Removed
- Removed django-autoslug because not support django 1.10 at this date (@luzfcb)
##[2016-09-10] ##[2016-09-10]
### Changed ### Changed
- Use app registry instead of INSTALLED_APPS to discover celery tasks (@dhepper) - Use app registry instead of INSTALLED_APPS to discover celery tasks (@dhepper)

View File

@ -95,6 +95,7 @@ Listed in alphabetical order.
Kaveh `@ka7eh`_ Kaveh `@ka7eh`_
Kevin A. Stone Kevin A. Stone
Kevin Ndung'u `@kevgathuku`_ Kevin Ndung'u `@kevgathuku`_
Keith Webber `@townie`_
Krzysztof Szumny `@noisy`_ Krzysztof Szumny `@noisy`_
Krzysztof Żuraw `@krzysztofzuraw`_ Krzysztof Żuraw `@krzysztofzuraw`_
Leonardo Jimenez `@xpostudio4`_ Leonardo Jimenez `@xpostudio4`_
@ -115,9 +116,11 @@ Listed in alphabetical order.
Peter Bittner `@bittner`_ Peter Bittner `@bittner`_
Raphael Pierzina `@hackebrot`_ Raphael Pierzina `@hackebrot`_
Raony Guimarães Corrêa `@raonyguimaraes`_ Raony Guimarães Corrêa `@raonyguimaraes`_
René Muhl `@rm--`_
Roman Afanaskin `@siauPatrick`_ Roman Afanaskin `@siauPatrick`_
Roman Osipenko `@romanosipenko`_ Roman Osipenko `@romanosipenko`_
Russell Davies Russell Davies
Sam Collins `@MightySCollins`_
stepmr `@stepmr`_ stepmr `@stepmr`_
Sławek Ehlert `@slafs`_ Sławek Ehlert `@slafs`_
Srinivas Nyayapati `@shireenrao`_ Srinivas Nyayapati `@shireenrao`_
@ -195,6 +198,7 @@ Listed in alphabetical order.
.. _@oubiga: https://github.com/oubiga .. _@oubiga: https://github.com/oubiga
.. _@parbhat: https://github.com/parbhat .. _@parbhat: https://github.com/parbhat
.. _@raonyguimaraes: https://github.com/raonyguimaraes .. _@raonyguimaraes: https://github.com/raonyguimaraes
.. _@rm--: https://github.com/rm--
.. _@romanosipenko: https://github.com/romanosipenko .. _@romanosipenko: https://github.com/romanosipenko
.. _@shireenrao: https://github.com/shireenrao .. _@shireenrao: https://github.com/shireenrao
.. _@show0k: https://github.com/show0k .. _@show0k: https://github.com/show0k
@ -214,6 +218,8 @@ Listed in alphabetical order.
.. _@sladinji: https://github.com/sladinji .. _@sladinji: https://github.com/sladinji
.. _@andresgz: https://github.com/andresgz .. _@andresgz: https://github.com/andresgz
.. _@jangeador: https://github.com/jangeador .. _@jangeador: https://github.com/jangeador
.. _@townie: https://github.com/townie
.. _@MightySCollins: https://github.com/MightySCollins
Special Thanks Special Thanks
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@ -26,9 +26,9 @@ Powered by Cookiecutter_, Cookiecutter Django is a framework for jumpstarting pr
Features Features
--------- ---------
* For Django 1.9 * For Django 1.10
* Renders Django projects with 100% starting test coverage * Renders Django projects with 100% starting test coverage
* Twitter Bootstrap_ v4.0.0 - `alpha 3`_ * Twitter Bootstrap_ v4.0.0 - `alpha 4`_
* 12-Factor_ based settings via django-environ_ * 12-Factor_ based settings via django-environ_
* Optimized development and production settings * Optimized development and production settings
* Registration via django-allauth_ * Registration via django-allauth_
@ -42,6 +42,7 @@ Features
* Works with Python 2.7.x or 3.5.x * Works with Python 2.7.x or 3.5.x
* Run tests with unittest or py.test * Run tests with unittest or py.test
* Customizable PostgreSQL version * Customizable PostgreSQL version
* Experimental support for Amazon Elastic Beanstalk
Optional Integrations Optional Integrations
@ -55,7 +56,7 @@ Optional Integrations
* Integration with Sentry_ for error logging * Integration with Sentry_ for error logging
* Integration with Opbeat_ for performance monitoring * Integration with Opbeat_ for performance monitoring
.. _`alpha 3`: http://blog.getbootstrap.com/2016/07/27/bootstrap-4-alpha-3/ .. _`alpha 4`: http://blog.getbootstrap.com/2016/09/05/bootstrap-4-alpha-4/
.. _Bootstrap: https://github.com/twbs/bootstrap .. _Bootstrap: https://github.com/twbs/bootstrap
.. _django-environ: https://github.com/joke2k/django-environ .. _django-environ: https://github.com/joke2k/django-environ
.. _12-Factor: http://12factor.net/ .. _12-Factor: http://12factor.net/
@ -89,7 +90,7 @@ and then editing the results to include your name, email, and various configurat
First, get Cookiecutter. Trust me, it's awesome:: First, get Cookiecutter. Trust me, it's awesome::
$ pip install cookiecutter $ pip install "cookiecutter>=1.4.0"
Now run it against this repo:: Now run it against this repo::
@ -146,6 +147,7 @@ Answer the prompts with your own desired options_. For example::
4 - Apache Software License 2.0 4 - Apache Software License 2.0
5 - Not open source 5 - Not open source
Choose from 1, 2, 3, 4, 5 [1]: 1 Choose from 1, 2, 3, 4, 5 [1]: 1
use_elasticbeanstalk_experimental: n
Enter the project and take a look around:: Enter the project and take a look around::

View File

@ -17,6 +17,7 @@
"use_python3": "y", "use_python3": "y",
"use_docker": "y", "use_docker": "y",
"use_heroku": "n", "use_heroku": "n",
"use_elasticbeanstalk_experimental": "n",
"use_compressor": "n", "use_compressor": "n",
"postgresql_version": ["9.5", "9.4", "9.3", "9.2"], "postgresql_version": ["9.5", "9.4", "9.3", "9.2"],
"js_task_runner": ["Gulp", "Grunt", "None"], "js_task_runner": ["Gulp", "Grunt", "None"],

View File

@ -27,9 +27,10 @@ You can either push the 'deploy' button in your generated README.rst or run thes
heroku config:set DJANGO_MAILGUN_SERVER_NAME=YOUR_MALGUN_SERVER heroku config:set DJANGO_MAILGUN_SERVER_NAME=YOUR_MALGUN_SERVER
heroku config:set DJANGO_MAILGUN_API_KEY=YOUR_MAILGUN_API_KEY heroku config:set DJANGO_MAILGUN_API_KEY=YOUR_MAILGUN_API_KEY
heroku config:set MAILGUN_SENDER_DOMAIN=YOUR_MAILGUN_SENDER_DOMAIN
heroku config:set PYTHONHASHSEED=random heroku config:set PYTHONHASHSEED=random
heroku config:set DJANGO_ADMIN_URL=\^somelocation/ heroku config:set DJANGO_ADMIN_URL=\^somelocation/
git push heroku master git push heroku master
heroku run python manage.py migrate heroku run python manage.py migrate

View File

@ -69,6 +69,7 @@ Add these exports
export DJANGO_ADMIN_URL='<not admin/>' export DJANGO_ADMIN_URL='<not admin/>'
export DJANGO_MAILGUN_API_KEY='<mailgun key>' export DJANGO_MAILGUN_API_KEY='<mailgun key>'
export DJANGO_MAILGUN_SERVER_NAME='<mailgun server name>' export DJANGO_MAILGUN_SERVER_NAME='<mailgun server name>'
export MAILGUN_SENDER_DOMAIN='<mailgun sender domain (e.g. mg.yourdomain.com)>'
export DJANGO_AWS_ACCESS_KEY_ID= export DJANGO_AWS_ACCESS_KEY_ID=
export DJANGO_AWS_SECRET_ACCESS_KEY= export DJANGO_AWS_SECRET_ACCESS_KEY=
export DJANGO_AWS_STORAGE_BUCKET_NAME= export DJANGO_AWS_STORAGE_BUCKET_NAME=
@ -84,7 +85,7 @@ Go to the PythonAnywhere **Databases tab** and configure your database.
* For Postgres, setup your superuser password, then open a Postgres console and run a `CREATE DATABASE my-db-name`. You should probably also set up a specific role and permissions for your app, rather than using the superuser credentials. Make a note of the address and port of your postgres server. * For Postgres, setup your superuser password, then open a Postgres console and run a `CREATE DATABASE my-db-name`. You should probably also set up a specific role and permissions for your app, rather than using the superuser credentials. Make a note of the address and port of your postgres server.
* For MySQL, set the password and create a database. More info here: https://help.pythonanywhere.com/pages/UsingMySQL * For MySQL, set the password and create a database. More info here: https://help.pythonanywhere.com/pages/UsingMySQL
* You can also use sqlite if you like! Not recommended for anything beyond toy projects though. * You can also use sqlite if you like! Not recommended for anything beyond toy projects though.

View File

@ -0,0 +1,72 @@
Deployment with Elastic Beanstalk
==========================================
.. index:: Elastic Beanstalk
Warning: Experimental
---------------------
This is experimental. For the time being there will be bugs and issues. If you've never used Elastic Beanstalk before, please hold off before trying this option.
On the other hand, we need help cleaning this up. If you do have knowledge of Elastic Beanstalk, we would appreciate the help. :)
Prerequisites
-------------
* awsebcli
Instructions
-------------
If you haven't done so, create a directory of environments::
eb init -p python3.4 MY_PROJECT_SLUG
Replace `MY_PROJECT_SLUG` with the value you entered for `project_slug`.
Once that is done, create the environment (server) where the app will run::
eb create MY_PROJECT_SLUG
# Note: This will eventually fail on a postgres error, because postgres doesn't exist yet
Now make sure you are in the right environment::
eb list
If you are not in the right environment, then put yourself in the correct one::
eb use MY_PROJECT_SLUG
Set the environment variables. Notes: You will be prompted if the `.env` file is missing. The script will ignore any PostgreSQL values, as RDS uses it's own system::
# Set the environment variables
python ebsetenv.py
Speaking of PostgreSQL, go to the Elasting Beanstalk configuration panel for RDS. Create new RDS database, with these attributes:
* PostgreSQL
* Version 9.4.9
* Size db.t2.micro (You can upgrade later)
(Get some coffee, this is going to take a while)
Once you have a database specified, deploy again so your instance can pick up the new PostgreSQL values::
eb deploy
Take a look::
eb open
FAQ
-----
Why Not Use Docker on Elastic Beanstalk?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Because I didn't want to add an abstraction (Docker) on top of an abstraction (Elastic Beanstalk) on top of an abstraction (Cookiecutter Django).
Why Can't I Use Both Docker/Heroku with Elastic Beanstalk?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Because the environment variables that our Docker and Heroku setups use for PostgreSQL access is different then how Amazon RDS handles this access. At this time we're just trying to get things to work reliably with Elastic Beanstalk, and full integration will come later.

View File

@ -8,9 +8,9 @@ Why is there a django.contrib.sites directory in Cookiecutter Django?
It is there to add a migration so you don't have to manually change the ``sites.Site`` record from ``example.com`` to whatever your domain is. Instead, your ``{{cookiecutter.domain_name}}`` and {{cookiecutter.project_name}} value is placed by **Cookiecutter** in the domain and name fields respectively. It is there to add a migration so you don't have to manually change the ``sites.Site`` record from ``example.com`` to whatever your domain is. Instead, your ``{{cookiecutter.domain_name}}`` and {{cookiecutter.project_name}} value is placed by **Cookiecutter** in the domain and name fields respectively.
See `0002_set_site_domain_and_name.py`_. See `0003_set_site_domain_and_name.py`_.
.. _`0002_set_site_domain_and_name.py`: https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/contrib/sites/migrations/0002_set_site_domain_and_name.py .. _`0003_set_site_domain_and_name.py`: https://github.com/pydanny/cookiecutter-django/blob/master/%7B%7Bcookiecutter.project_slug%7D%7D/%7B%7Bcookiecutter.project_slug%7D%7D/contrib/sites/migrations/0003_set_site_domain_and_name.py
Why aren't you using just one configuration file (12-Factor App) Why aren't you using just one configuration file (12-Factor App)

View File

@ -25,6 +25,7 @@ Contents:
faq faq
troubleshooting troubleshooting
my-favorite-cookie my-favorite-cookie
deployment-with-elastic-beanstalk
Indices and tables Indices and tables
================== ==================

View File

@ -40,6 +40,7 @@ DJANGO_SENTRY_CLIENT SENTRY_CLIENT n/a
DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO DJANGO_SENTRY_LOG_LEVEL SENTRY_LOG_LEVEL n/a logging.INFO
DJANGO_MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error DJANGO_MAILGUN_API_KEY MAILGUN_ACCESS_KEY n/a raises error
DJANGO_MAILGUN_SERVER_NAME MAILGUN_SERVER_NAME n/a raises error DJANGO_MAILGUN_SERVER_NAME MAILGUN_SERVER_NAME n/a raises error
MAILGUN_SENDER_DOMAIN MAILGUN_SENDER_DOMAIN n/a raises error
NEW_RELIC_APP_NAME NEW_RELIC_APP_NAME n/a raises error NEW_RELIC_APP_NAME NEW_RELIC_APP_NAME n/a raises error
NEW_RELIC_LICENSE_KEY NEW_RELIC_LICENSE_KEY n/a raises error NEW_RELIC_LICENSE_KEY NEW_RELIC_LICENSE_KEY n/a raises error
DJANGO_OPBEAT_APP_ID OPBEAT['APP_ID'] n/a raises error DJANGO_OPBEAT_APP_ID OPBEAT['APP_ID'] n/a raises error

View File

@ -115,7 +115,10 @@ def remove_heroku_files():
""" """
Removes files needed for heroku if it isn't going to be used Removes files needed for heroku if it isn't going to be used
""" """
for filename in ["app.json", "Procfile", "requirements.txt", "runtime.txt"]: filenames = ["Procfile", "runtime.txt"]
if '{{ cookiecutter.use_elasticbeanstalk_experimental }}'.lower() != 'y':
filenames.append("requirements.txt")
for filename in ["Procfile", "runtime.txt"]:
file_name = os.path.join(PROJECT_DIRECTORY, filename) file_name = os.path.join(PROJECT_DIRECTORY, filename)
remove_file(file_name) remove_file(file_name)
@ -179,6 +182,22 @@ def remove_copying_files():
PROJECT_DIRECTORY, filename PROJECT_DIRECTORY, filename
)) ))
def remove_elasticbeanstalk():
"""
Removes elastic beanstalk components
"""
docs_dir_location = os.path.join(PROJECT_DIRECTORY, '.ebextensions')
if os.path.exists(docs_dir_location):
shutil.rmtree(docs_dir_location)
filenames = ["ebsetenv.py", ]
if '{{ cookiecutter.use_heroku }}'.lower() != 'y':
filenames.append("requirements.txt")
for filename in filenames:
os.remove(os.path.join(
PROJECT_DIRECTORY, filename
))
# IN PROGRESS # IN PROGRESS
# def copy_doc_files(project_directory): # def copy_doc_files(project_directory):
# cookiecutters_dir = DEFAULT_CONFIG['cookiecutters_dir'] # cookiecutters_dir = DEFAULT_CONFIG['cookiecutters_dir']
@ -258,5 +277,6 @@ if '{{ cookiecutter.use_lets_encrypt }}'.lower() == 'y' and '{{ cookiecutter.use
if '{{ cookiecutter.open_source_license}}' != 'GPLv3': if '{{ cookiecutter.open_source_license}}' != 'GPLv3':
remove_copying_files() remove_copying_files()
# 4. Copy files from /docs/ to {{ cookiecutter.project_slug }}/docs/ # 12. Remove Elastic Beanstalk files
# copy_doc_files(PROJECT_DIRECTORY) if '{{ cookiecutter.use_elasticbeanstalk_experimental }}'.lower() != 'y':
remove_elasticbeanstalk()

View File

@ -3,4 +3,9 @@ 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 should be valid Python identifier!'
elasticbeanstalk = '{{ cookiecutter.use_elasticbeanstalk_experimental }}'.lower()
heroku = '{{ cookiecutter.use_heroku }}'.lower()
docker = '{{ cookiecutter.use_docker }}'.lower()
if elasticbeanstalk == 'y' and (heroku == 'y' or docker == 'y'):
raise Exception("Cookiecutter Django's EXPERIMENTAL Elastic Beanstalk support is incompatible with Heroku and Docker setups.")

View File

@ -4,8 +4,8 @@ sh==1.11
binaryornot==0.4.0 binaryornot==0.4.0
# Testing # Testing
pytest==3.0.2 pytest==3.0.3
pep8==1.7.0 pep8==1.7.0
pyflakes==1.3.0 pyflakes==1.3.0
tox==2.3.1 tox==2.4.1
pytest-cookies==0.2.0 pytest-cookies==0.2.0

View File

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

View File

@ -1,3 +1,3 @@
[pytest] [tool:pytest]
python_paths = . python_paths = .
norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/* norecursedirs = .tox .git */migrations/* */static/* docs venv */{{cookiecutter.project_slug}}/*

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.9.9-03' version = '1.10.1'
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,7 +34,7 @@ setup(
classifiers=[ classifiers=[
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
'Environment :: Console', 'Environment :: Console',
'Framework :: Django :: 1.9', 'Framework :: Django :: 1.10',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Natural Language :: English', 'Natural Language :: English',
'License :: OSI Approved :: BSD License', 'License :: OSI Approved :: BSD License',

0
tests/test_cookiecutter_generation.py Normal file → Executable file
View File

3
tests/test_docker.sh Normal file → Executable file
View File

@ -16,3 +16,6 @@ cd project_name
# run the project's tests # run the project's tests
docker-compose -f dev.yml run django python manage.py test docker-compose -f dev.yml run django python manage.py test
# return non-zero status code if there are migrations that have not been created
docker-compose -f dev.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; }

View File

@ -0,0 +1,5 @@
packages:
yum:
git: []
postgresql94-devel: []
libjpeg-turbo-devel: []

View File

@ -0,0 +1,46 @@
#This sample requires you to create a separate configuration file that defines the custom
# option settings for CacheCluster properties.
Resources:
MyCacheSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Lock cache down to webserver access only"
SecurityGroupIngress :
- IpProtocol : "tcp"
FromPort :
Fn::GetOptionSetting:
OptionName : "CachePort"
DefaultValue: "6379"
ToPort :
Fn::GetOptionSetting:
OptionName : "CachePort"
DefaultValue: "6379"
SourceSecurityGroupName:
Ref: "AWSEBSecurityGroup"
MyElastiCache:
Type: "AWS::ElastiCache::CacheCluster"
Properties:
CacheNodeType:
Fn::GetOptionSetting:
OptionName : "CacheNodeType"
DefaultValue : "cache.t1.micro"
NumCacheNodes:
Fn::GetOptionSetting:
OptionName : "NumCacheNodes"
DefaultValue : "1"
Engine:
Fn::GetOptionSetting:
OptionName : "Engine"
DefaultValue : "redis"
VpcSecurityGroupIds:
-
Fn::GetAtt:
- MyCacheSecurityGroup
- GroupId
Outputs:
ElastiCache:
Description : "ID of ElastiCache Cache Cluster with Redis Engine"
Value :
Ref : "MyElastiCache"

View File

@ -0,0 +1,6 @@
option_settings:
"aws:elasticbeanstalk:customoption":
CacheNodeType : cache.t1.micro
NumCacheNodes : 1
Engine : redis
CachePort : 6379

View File

@ -0,0 +1,17 @@
container_commands:
01_migrate:
command: "source /opt/python/run/venv/bin/activate && python manage.py migrate --noinput"
leader_only: True
02_collectstatic:
command: "source /opt/python/run/venv/bin/activate && python manage.py collectstatic --noinput"
option_settings:
"aws:elasticbeanstalk:application:environment":
DJANGO_SETTINGS_MODULE: "config.settings.production"
REDIS_ENDPOINT_ADDRESS: '`{ "Fn::GetAtt" : [ "MyElastiCache", "RedisEndpoint.Address"]}`'
REDIS_PORT: '`{ "Fn::GetAtt" : [ "MyElastiCache", "RedisEndpoint.Port"]}`'
"aws:elasticbeanstalk:container:python":
WSGIPath: "config/wsgi.py"
NumProcesses: 3
NumThreads: 20
"aws:elasticbeanstalk:container:python:staticfiles":
"/static/": "www/static/"

View File

@ -27,3 +27,7 @@ trim_trailing_whitespace = false
[Makefile] [Makefile]
indent_style = tab indent_style = tab
[nginx.conf]
indent_style = space
indent_size = 2

View File

@ -24,8 +24,10 @@ sftp-config.json
__pycache__ __pycache__
# Logs # Logs
logs
*.log *.log
pip-log.txt pip-log.txt
npm-debug.log*
# Unit test / coverage reports # Unit test / coverage reports
.coverage .coverage
@ -68,9 +70,9 @@ node_modules/
# User-uploaded media # User-uploaded media
{{ cookiecutter.project_slug }}/media/ {{ cookiecutter.project_slug }}/media/
# Hitch directory {% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n' %}
tests/.hitch
{% if cookiecutter.use_mailhog == 'y' and cookiecutter.use_docker == 'n'%}
# MailHog binary # MailHog binary
mailhog mailhog
{% endif %} {% endif %}
staticfiles/

View File

@ -117,19 +117,16 @@ Deployment
---------- ----------
The following details how to deploy this application. The following details how to deploy this application.
{% if cookiecutter.use_heroku == "y" %} {% if cookiecutter.use_heroku.lower() == "y" %}
Heroku Heroku
^^^^^^ ^^^^^^
.. image:: https://www.herokucdn.com/deploy/button.png
:target: https://heroku.com/deploy
See detailed `cookiecutter-django Heroku documentation`_. See detailed `cookiecutter-django Heroku documentation`_.
.. _`cookiecutter-django Heroku documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-on-heroku.html .. _`cookiecutter-django Heroku documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-on-heroku.html
{% endif %} {% endif %}
{% if cookiecutter.use_docker == "y" %} {% if cookiecutter.use_docker.lower() == "y" %}
Docker Docker
^^^^^^ ^^^^^^
@ -138,3 +135,13 @@ See detailed `cookiecutter-django Docker documentation`_.
.. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html .. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-docker.html
{% endif %} {% endif %}
{% if cookiecutter.use_elasticbeanstalk_experimental.lower() == 'y' %}
Elastic Beanstalk
~~~~~~~~~~~~~~~~~~
See detailed `cookiecutter-django Elastic Beanstalk documentation`_.
.. _`cookiecutter-django Docker documentation`: http://cookiecutter-django.readthedocs.io/en/latest/deployment-with-elastic-beanstalk.html
{% endif %}

View File

@ -1,39 +0,0 @@
{
"name": "{{cookiecutter.project_slug}}",
"description": "{{cookiecutter.description}}",
"env": {
"BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-python",
"DJANGO_SETTINGS_MODULE": "config.settings.production",
"DJANGO_SECRET_KEY": {
"description": "A secret key for verifying the integrity of signed cookies.",
"generator": "secret"
},
"DJANGO_ALLOWED_HOSTS": {
"description": "Comma-separated list of hosts",
"value": ".herokuapp.com"
},
"DJANGO_ADMIN_URL": {
"description": "A secret URL for the Django admin",
"generator": "secret"
},
"DJANGO_AWS_ACCESS_KEY_ID": "",
"DJANGO_AWS_SECRET_ACCESS_KEY": "",
"DJANGO_AWS_STORAGE_BUCKET_NAME": "",
"DJANGO_MAILGUN_SERVER_NAME": "",
"DJANGO_MAILGUN_API_KEY": ""{% if cookiecutter.use_sentry_for_error_reporting == "y" -%},
"DJANGO_SENTRY_DSN": ""{%- endif %}
},
"scripts": {
"postdeploy": "python manage.py migrate"
},
"addons": [
{
"plan": "heroku-postgresql:hobby-dev",
"options": {
"version": "{{ cookiecutter.postgresql_version }}"
}
},
"heroku-redis:hobby-dev",
"mailgun"
]
}

View File

@ -4,67 +4,58 @@ worker_processes 1;
error_log /var/log/nginx/error.log warn; error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid; pid /var/run/nginx.pid;
events { events {
worker_connections 1024; worker_connections 1024;
} }
http { http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
include /etc/nginx/mime.types; log_format main '$remote_addr - $remote_user [$time_local] "$request" '
default_type application/octet-stream; '$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' access_log /var/log/nginx/access.log main;
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main; sendfile on;
#tcp_nopush on;
sendfile on; keepalive_timeout 65;
#tcp_nopush on;
keepalive_timeout 65; #gzip on;
#gzip on; upstream app {
server django:5000;
}
upstream app { server {
server django:5000; listen 80;
charset utf-8;
{% if cookiecutter.use_lets_encrypt == 'y' and cookiecutter.use_docker == 'y' %}
server_name ___my.example.com___ ;
location /.well-known/acme-challenge {
proxy_pass http://certbot:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
{% endif %}
location / {
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
} }
server { # cookiecutter-django app
listen 80; location @proxy_to_app {
charset utf-8; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app;
{% if cookiecutter.use_lets_encrypt == 'y' and cookiecutter.use_docker == 'y' %} }
server_name ___my.example.com___ ; }
location /.well-known/acme-challenge {
proxy_pass http://certbot:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
{% endif %}
location / {
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
}
# cookiecutter-django app
location @proxy_to_app {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app;
}
}
} }

View File

@ -53,7 +53,7 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
# MIDDLEWARE CONFIGURATION # MIDDLEWARE CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
MIDDLEWARE_CLASSES = ( MIDDLEWARE = (
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
@ -200,6 +200,26 @@ ROOT_URLCONF = 'config.urls'
# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application # See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
WSGI_APPLICATION = 'config.wsgi.application' WSGI_APPLICATION = 'config.wsgi.application'
# PASSWORD VALIDATION
# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
# ------------------------------------------------------------------------------
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# AUTHENTICATION CONFIGURATION # AUTHENTICATION CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (

View File

@ -52,7 +52,7 @@ CACHES = {
# django-debug-toolbar # django-debug-toolbar
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',) MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
INSTALLED_APPS += ('debug_toolbar', ) INSTALLED_APPS += ('debug_toolbar', )
INTERNAL_IPS = ['127.0.0.1', '10.0.2.2', ] INTERNAL_IPS = ['127.0.0.1', '10.0.2.2', ]

View File

@ -42,11 +42,11 @@ INSTALLED_APPS += ('raven.contrib.django.raven_compat', )
# Use Whitenoise to serve static files # Use Whitenoise to serve static files
# See: https://whitenoise.readthedocs.io/ # See: https://whitenoise.readthedocs.io/
WHITENOISE_MIDDLEWARE = ('whitenoise.middleware.WhiteNoiseMiddleware', ) WHITENOISE_MIDDLEWARE = ('whitenoise.middleware.WhiteNoiseMiddleware', )
MIDDLEWARE_CLASSES = WHITENOISE_MIDDLEWARE + MIDDLEWARE_CLASSES MIDDLEWARE = WHITENOISE_MIDDLEWARE + MIDDLEWARE
{% endif %} {% endif %}
{%- if cookiecutter.use_sentry_for_error_reporting == 'y' -%} {%- if cookiecutter.use_sentry_for_error_reporting == 'y' -%}
RAVEN_MIDDLEWARE = ('raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', ) RAVEN_MIDDLEWARE = ('raven.contrib.django.raven_compat.middleware.SentryResponseErrorIdMiddleware', )
MIDDLEWARE_CLASSES = RAVEN_MIDDLEWARE + MIDDLEWARE_CLASSES MIDDLEWARE = RAVEN_MIDDLEWARE + MIDDLEWARE
{% endif %} {% endif %}
{%- if cookiecutter.use_opbeat == 'y' -%} {%- if cookiecutter.use_opbeat == 'y' -%}
# opbeat integration # opbeat integration
@ -57,9 +57,9 @@ OPBEAT = {
'APP_ID': env('DJANGO_OPBEAT_APP_ID'), 'APP_ID': env('DJANGO_OPBEAT_APP_ID'),
'SECRET_TOKEN': env('DJANGO_OPBEAT_SECRET_TOKEN') 'SECRET_TOKEN': env('DJANGO_OPBEAT_SECRET_TOKEN')
} }
MIDDLEWARE_CLASSES = ( MIDDLEWARE = (
'opbeat.contrib.django.middleware.OpbeatAPMMiddleware', 'opbeat.contrib.django.middleware.OpbeatAPMMiddleware',
) + MIDDLEWARE_CLASSES ) + MIDDLEWARE
{% endif %} {% endif %}
# SECURITY CONFIGURATION # SECURITY CONFIGURATION
@ -163,6 +163,7 @@ SERVER_EMAIL = env('DJANGO_SERVER_EMAIL', default=DEFAULT_FROM_EMAIL)
INSTALLED_APPS += ("anymail", ) INSTALLED_APPS += ("anymail", )
ANYMAIL = { ANYMAIL = {
"MAILGUN_API_KEY": env('DJANGO_MAILGUN_API_KEY'), "MAILGUN_API_KEY": env('DJANGO_MAILGUN_API_KEY'),
"MAILGUN_SENDER_DOMAIN": env('MAILGUN_SENDER_DOMAIN')
} }
EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend" EMAIL_BACKEND = "anymail.backends.mailgun.MailgunBackend"
@ -177,16 +178,39 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
# DATABASE CONFIGURATION # DATABASE CONFIGURATION
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
{% if cookiecutter.use_elasticbeanstalk_experimental.lower() == 'y' -%}
# Uses Amazon RDS for database hosting, which doesn't follow the Heroku-style spec
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': env('RDS_DB_NAME'),
'USER': env('RDS_USERNAME'),
'PASSWORD': env('RDS_PASSWORD'),
'HOST': env('RDS_HOSTNAME'),
'PORT': env('RDS_PORT'),
}
}
{% else %}
# Use the Heroku-style specification
# 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')
{%- endif %}
# CACHING # CACHING
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
{% if cookiecutter.use_elasticbeanstalk_experimental.lower() == 'y' -%}
REDIS_LOCATION = "redis://{}:{}/0".format(
env('REDIS_ENDPOINT_ADDRESS'),
env('REDIS_PORT')
)
{% else %}
REDIS_LOCATION = '{0}/{1}'.format(env('REDIS_URL', default='redis://127.0.0.1:6379'), 0)
{%- endif %}
# Heroku URL does not pass the DB number, so we parse it in # Heroku URL does not pass the DB number, so we parse it in
CACHES = { CACHES = {
'default': { 'default': {
'BACKEND': 'django_redis.cache.RedisCache', 'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': '{0}/{1}'.format(env('REDIS_URL', default='redis://127.0.0.1:6379'), 0), 'LOCATION': REDIS_LOCATION,
'OPTIONS': { 'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient', 'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'IGNORE_EXCEPTIONS': True, # mimics memcache behavior. 'IGNORE_EXCEPTIONS': True, # mimics memcache behavior.

View File

@ -13,7 +13,7 @@ urlpatterns = [
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, include(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(r'^users/', include('{{ cookiecutter.project_slug }}.users.urls', namespace='users')),
@ -33,3 +33,9 @@ if settings.DEBUG:
url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception('Page not Found')}), url(r'^404/$', default_views.page_not_found, kwargs={'exception': Exception('Page not Found')}),
url(r'^500/$', default_views.server_error), url(r'^500/$', default_views.server_error),
] ]
if 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns += [
url(r'^__debug__/', include(debug_toolbar.urls)),
]

View File

@ -0,0 +1,37 @@
"""Converts a .env file to Elastic Beanstalk environment variables"""
import os
from sys import exit
from subprocess import check_call
try:
import dotenv
except ImportError:
print("Please install the 'dotenv' library: 'pip install dotenv'")
exit()
def main():
if not os.path.exists('.env'):
print('ERROR!! .env file is missing!')
print("Please copy 'env.example' to '.env' and add appropriate values")
exit()
command = ['eb', 'setenv']
failures = []
for key, value in dotenv.Dotenv('.env').items():
if key.startswith('POSTGRES'):
print('Skipping POSTGRES values - Amazon RDS provides these')
continue
if value:
command.append("{}={}".format(key, value))
else:
failures.append(key)
if failures:
for failure in failures:
print("{} requires a value".format(failure))
else:
print(' '.join(command))
check_call(command)
if __name__ == '__main__':
main()

View File

@ -1,3 +1,4 @@
# PostgreSQL # PostgreSQL
POSTGRES_PASSWORD=mysecretpass POSTGRES_PASSWORD=mysecretpass
POSTGRES_USER=postgresuser POSTGRES_USER=postgresuser
@ -16,6 +17,7 @@ DJANGO_AWS_STORAGE_BUCKET_NAME=
# Used with email # Used with email
DJANGO_MAILGUN_API_KEY= DJANGO_MAILGUN_API_KEY=
DJANGO_SERVER_EMAIL= DJANGO_SERVER_EMAIL=
MAILGUN_SENDER_DOMAIN=
# Security! Better to use DNS for this task, but you can use redirect # Security! Better to use DNS for this task, but you can use redirect
DJANGO_SECURE_SSL_REDIRECT=False DJANGO_SECURE_SSL_REDIRECT=False
@ -33,4 +35,4 @@ DJANGO_OPBEAT_SECRET_TOKEN
{% endif %} {% endif %}
{% if cookiecutter.use_compressor == 'y' -%} {% if cookiecutter.use_compressor == 'y' -%}
COMPRESS_ENABLED= COMPRESS_ENABLED=
{% endif %} {% endif %}

View File

@ -5,6 +5,19 @@ 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')
from django.core.management import execute_from_command_line try:
from django.core.management import execute_from_command_line
except ImportError:
# The above import may fail for some other reason. Ensure that the
# issue is really that Django is missing to avoid masking other
# exceptions on Python 2.
try:
import django # noqa
except ImportError:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
)
raise
execute_from_command_line(sys.argv) execute_from_command_line(sys.argv)

View File

@ -7,28 +7,28 @@ wheel==0.29.0
{%- endif %} {%- endif %}
# Bleeding edge Django # Bleeding edge Django
django==1.9.9 django==1.10.2
# Configuration # Configuration
django-environ==0.4.0 django-environ==0.4.0
{% if cookiecutter.use_whitenoise == 'y' -%} {% if cookiecutter.use_whitenoise == 'y' -%}
whitenoise==3.2.1 whitenoise==3.2.2
{%- endif %} {%- endif %}
# Forms # Forms
django-braces==1.9.0 django-braces==1.9.0
django-crispy-forms==1.6.0 django-crispy-forms==1.6.1
# Models # Models
django-model-utils==2.5.2 django-model-utils==2.6
# Images # Images
Pillow==3.3.1 Pillow==3.4.1
# For user registration, either via email or social # For user registration, either via email or social
# Well-built with regular release cycles! # Well-built with regular release cycles!
django-allauth==0.27.0 django-allauth==0.28.0
{% if cookiecutter.windows == 'y' -%} {% if cookiecutter.windows == 'y' -%}
# On Windows, you must download/install psycopg2 manually # On Windows, you must download/install psycopg2 manually
@ -40,17 +40,16 @@ psycopg2==2.6.2
# Unicode slugification # Unicode slugification
awesome-slugify==1.6.5 awesome-slugify==1.6.5
django-autoslug==1.9.3
# Time zones support # Time zones support
pytz==2016.6.1 pytz==2016.7
# Redis support # Redis support
django-redis==4.4.4 django-redis==4.5.0
redis>=2.10.0 redis>=2.10.5
{% if cookiecutter.use_celery == "y" %} {% if cookiecutter.use_celery == "y" %}
celery==3.1.23 celery==3.1.24
{% endif %} {% endif %}
{% if cookiecutter.use_compressor == "y" %} {% if cookiecutter.use_compressor == "y" %}

View File

@ -2,15 +2,16 @@
-r base.txt -r base.txt
coverage==4.2 coverage==4.2
django-coverage-plugin==1.3.1 django-coverage-plugin==1.3.1
Sphinx==1.4.5 Sphinx==1.4.8
django-extensions==1.7.4 django-extensions==1.7.4
Werkzeug==0.11.11 Werkzeug==0.11.11
django-test-plus==1.0.15 django-test-plus==1.0.15
factory_boy==2.7.0 factory_boy==2.7.0
django-debug-toolbar==1.5
django-debug-toolbar==1.6
# improved REPL # improved REPL
ipdb==0.10.1 ipdb==0.10.1
pytest-django==2.9.1 pytest-django==3.0.0
pytest-sugar==0.7.1 pytest-sugar==0.7.1

View File

@ -24,12 +24,12 @@ Collectfast==0.2.3
# Email backends for Mailgun, Postmark, SendGrid and more # Email backends for Mailgun, Postmark, SendGrid and more
# ------------------------------------------------------- # -------------------------------------------------------
django-anymail==0.4.2 django-anymail==0.5
{% 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
# -------------------------- # --------------------------
raven==5.26.0 raven==5.27.1
{%- endif %} {%- endif %}
{% if cookiecutter.use_opbeat == "y" -%} {% if cookiecutter.use_opbeat == "y" -%}

View File

@ -13,5 +13,5 @@ django-test-plus==1.0.15
factory_boy==2.7.0 factory_boy==2.7.0
# pytest # pytest
pytest-django==2.9.1 pytest-django==3.0.0
pytest-sugar==0.7.1 pytest-sugar==0.7.1

View File

@ -1,31 +1,34 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations
import django.contrib.sites.models import django.contrib.sites.models
from django.contrib.sites.models import _simple_domain_name_validator
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='Site', name='Site',
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('domain', models.CharField(verbose_name='domain name', max_length=100, validators=[django.contrib.sites.models._simple_domain_name_validator])), ('domain', models.CharField(
('name', models.CharField(verbose_name='display name', max_length=50)), max_length=100, verbose_name='domain name', validators=[_simple_domain_name_validator]
)),
('name', models.CharField(max_length=50, verbose_name='display name')),
], ],
options={ options={
'verbose_name_plural': 'sites',
'verbose_name': 'site',
'db_table': 'django_site',
'ordering': ('domain',), 'ordering': ('domain',),
'db_table': 'django_site',
'verbose_name': 'site',
'verbose_name_plural': 'sites',
}, },
bases=(models.Model,),
managers=[ managers=[
(b'objects', django.contrib.sites.models.SiteManager()), ('objects', django.contrib.sites.models.SiteManager()),
], ],
), ),
] ]

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import django.contrib.sites.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sites', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='site',
name='domain',
field=models.CharField(
max_length=100, unique=True, validators=[django.contrib.sites.models._simple_domain_name_validator],
verbose_name='domain name'
),
),
]

View File

@ -38,7 +38,7 @@ def update_site_backward(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('sites', '0001_initial'), ('sites', '0002_alter_domain_unique'),
] ]
operations = [ operations = [

View File

@ -0,0 +1,9 @@
{% raw %}{% extends "base.html" %}
{% block title %}Forbidden (403){% endblock %}
{% block content %}
<h1>Forbidden (403)</h1>
<p>CSRF verification failed. Request aborted.</p>
{% endblock content %}{% endraw %}

View File

@ -14,8 +14,8 @@
<![endif]--> <![endif]-->
{% block css %} {% block css %}
<!-- Latest compiled and minified Bootstrap 4 Alpha 3 CSS --> <!-- Latest compiled and minified Bootstrap 4 Alpha 4 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.3/css/bootstrap.min.css" integrity="sha384-MIwDKRSSImVFAZCVLtU0LMDdON6KVCrZHyVQQj6e8wIEJkW4tvwqXrbMIya1vriY" crossorigin="anonymous"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.4/css/bootstrap.min.css" integrity="sha384-2hfp1SzUoho7/TsGGGDaFdsuuDL0LX2hnUp6VkX3CUQ2K4K+xjboZdsXyp4oUHZj" crossorigin="anonymous">
<!-- 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 %}
@ -53,7 +53,7 @@
<a class="nav-link" href="{% url 'users:detail' request.user.username %}">{% trans "My Profile" %}</a> <a class="nav-link" href="{% url 'users:detail' request.user.username %}">{% trans "My Profile" %}</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'account_logout' %}">{% trans "Sign out" %}</a> <a class="nav-link" href="{% url 'account_logout' %}">{% trans "Sign Out" %}</a>
</li> </li>
{% else %} {% else %}
<li class="nav-item"> <li class="nav-item">
@ -89,10 +89,10 @@
================================================== --> ================================================== -->
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
{% block javascript %} {% block javascript %}
<!-- Required by Bootstrap v4 Alpha 3 --> <!-- Required by Bootstrap v4 Alpha 4 -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js" integrity="sha384-THPy051/pYDQGanwU6poAc/hOdQxjnOEXzbT+OuUAFqNqFjL+4IGLBgCJC3ZOShY" crossorigin="anonymous"></script> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha384-3ceskX3iaEnIogmQchP8opvBy3Mi7Ce34nWjpBIwVTHfGYWQS9jwHDVRnpKKHJg7" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.2.0/js/tether.min.js" integrity="sha384-Plbmg8JY28KFelvJVai01l8WyZzrYWG825m+cZ0eDDS1f7d/js6ikvy1+X+guPIB" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/js/tether.min.js" integrity="sha384-XTs3FgkjiBgo8qjEjBk0tGmf3wPrWtA6coPfQDfFEY8AnYJwjalXCiosYRBIBZX8" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.3/js/bootstrap.min.js" integrity="sha384-ux8v3A6CPtOTqOzMKiuo3d/DomGaaClxFYdCu2HPMBEkf6x2xiDyJ7gkXU0MWwaD" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.4/js/bootstrap.min.js" integrity="sha384-VjEeINv9OSwtWFLAtmc4JCtEJXXBub00gtSnszmspDLCtC0I4z4nqz7rEFbIZLLU" crossorigin="anonymous"></script>
<!-- Your stuff: Third-party javascript libraries go here --> <!-- Your stuff: Third-party javascript libraries go here -->

View File

@ -20,9 +20,9 @@
<!-- Action buttons --> <!-- Action buttons -->
<div class="row"> <div class="row">
<div class="col-sm-12 "> <div class="col-sm-12">
<a class="btn btn-primary" href="{% url 'users:update' %}">My Info</a> <a class="btn btn-primary" href="{% url 'users:update' %}" role="button">My Info</a>
<a class="btn btn-primary" href="{% url 'account_email' %}">E-Mail</a> <a class="btn btn-primary" href="{% url 'account_email' %}" role="button">E-Mail</a>
<!-- Your Stuff: Custom user template urls --> <!-- Your Stuff: Custom user template urls -->
</div> </div>

View File

@ -1,44 +1,47 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-23 04:36
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
import django.contrib.auth.models import django.contrib.auth.models
import django.core.validators import django.contrib.auth.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True
dependencies = [ dependencies = [
('auth', '0006_require_contenttypes_0002'), ('auth', '0008_alter_user_username_max_length'),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='User', name='User',
fields=[ fields=[
('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')), ('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)), ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status', default=False)), ('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(max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], verbose_name='username', error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=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')),
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), ('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), ('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')),
('email', models.EmailField(max_length=254, verbose_name='email address', blank=True)), ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)), ('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(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)), ('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(verbose_name='date joined', default=django.utils.timezone.now)), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='groups', to='auth.Group', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_query_name='user')), ('name', models.CharField(blank=True, max_length=255, verbose_name='Name of User')),
('user_permissions', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='user permissions', to='auth.Permission', help_text='Specific permissions for this user.', related_query_name='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')),
('name', models.CharField(max_length=255, verbose_name='Name of User', blank=True)), ('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': 'user', 'verbose_name': 'user',
'abstract': False, 'abstract': False,
'verbose_name_plural': 'users',
}, },
managers=[ managers=[
(b'objects', django.contrib.auth.models.UserManager()), ('objects', django.contrib.auth.models.UserManager()),
], ],
), ),
] ]

View File

@ -6,14 +6,14 @@ from ..admin import MyUserCreationForm
class TestMyUserCreationForm(TestCase): class TestMyUserCreationForm(TestCase):
def setUp(self): def setUp(self):
self.user = self.make_user() 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', 'username': 'alamode',
'password1': '123456', 'password1': '7jefB#f@Cc7YJB]2v',
'password2': '123456', '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()
@ -27,8 +27,8 @@ class TestMyUserCreationForm(TestCase):
# 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, 'username': self.user.username,
'password1': '123456', 'password1': 'notalamodespassword',
'password2': '123456', '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

View File

@ -0,0 +1,51 @@
from django.core.urlresolvers import reverse, resolve
from test_plus.test import TestCase
class TestUserURLs(TestCase):
"""Test URL patterns for users app."""
def setUp(self):
self.user = self.make_user()
def test_list_reverse(self):
"""users:list should reverse to /users/."""
self.assertEqual(reverse('users:list'), '/users/')
def test_list_resolve(self):
"""/users/ should resolve to users:list."""
self.assertEqual(resolve('/users/').view_name, 'users:list')
def test_redirect_reverse(self):
"""users:redirect should reverse to /users/~redirect/."""
self.assertEqual(reverse('users:redirect'), '/users/~redirect/')
def test_redirect_resolve(self):
"""/users/~redirect/ should resolve to users:redirect."""
self.assertEqual(
resolve('/users/~redirect/').view_name,
'users:redirect'
)
def test_detail_reverse(self):
"""users:detail should reverse to /users/testuser/."""
self.assertEqual(
reverse('users:detail', kwargs={'username': 'testuser'}),
'/users/testuser/'
)
def test_detail_resolve(self):
"""/users/testuser/ should resolve to users:detail."""
self.assertEqual(resolve('/users/testuser/').view_name, 'users:detail')
def test_update_reverse(self):
"""users:update should reverse to /users/~update/."""
self.assertEqual(reverse('users:update'), '/users/~update/')
def test_update_resolve(self):
"""/users/~update/ should resolve to users:update."""
self.assertEqual(
resolve('/users/~update/').view_name,
'users:update'
)

View File

@ -6,28 +6,21 @@ from django.conf.urls import url
from . import views from . import views
urlpatterns = [ urlpatterns = [
# URL pattern for the UserListView
url( url(
regex=r'^$', regex=r'^$',
view=views.UserListView.as_view(), view=views.UserListView.as_view(),
name='list' name='list'
), ),
# URL pattern for the UserRedirectView
url( url(
regex=r'^~redirect/$', regex=r'^~redirect/$',
view=views.UserRedirectView.as_view(), view=views.UserRedirectView.as_view(),
name='redirect' name='redirect'
), ),
# URL pattern for the UserDetailView
url( url(
regex=r'^(?P<username>[\w.@+-]+)/$', regex=r'^(?P<username>[\w.@+-]+)/$',
view=views.UserDetailView.as_view(), view=views.UserDetailView.as_view(),
name='detail' name='detail'
), ),
# URL pattern for the UserUpdateView
url( url(
regex=r'^~update/$', regex=r'^~update/$',
view=views.UserUpdateView.as_view(), view=views.UserUpdateView.as_view(),