├── .gitignore
├── CHANGELOG.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── herokuapp
├── __init__.py
├── bin
│ ├── __init__.py
│ └── herokuapp_startproject.py
├── commands.py
├── env.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ ├── base.py
│ │ └── heroku_audit.py
├── middleware.py
├── project_template
│ ├── .gitignore
│ ├── .slugignore
│ ├── manage.py
│ └── project_name
│ │ ├── __init__.py
│ │ ├── apps
│ │ └── __init__.py
│ │ ├── settings
│ │ ├── .gitignore
│ │ ├── __init__.py
│ │ ├── local.py
│ │ └── production.py
│ │ ├── static
│ │ └── js
│ │ │ └── .gitignore
│ │ ├── templates
│ │ └── .gitignore
│ │ ├── urls.py
│ │ └── wsgi.py
└── settings.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .project
3 | .pydevproject
4 | .settings
5 | *.pyc
6 | *.pyo
7 | .bzr
8 | .bzrignore
9 | Thumbs.db
10 | dist
11 | build
12 | MANIFEST
13 | /venv
14 | /*.egg-info/
15 | /*.egg
16 |
--------------------------------------------------------------------------------
/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | django-herokuapp changelog
2 | ==========================
3 |
4 |
5 | 0.9.20 - 05/05/2014
6 | -------------------
7 |
8 | - Fixing error if heroku config command returns no config vars. (@ducheneaut)
9 | - Removed heroku deploy command, since it's now broken with the removal of Anvil.
10 |
11 |
12 | 0.9.19 - 05/12/2014
13 | -------------------
14 |
15 | - Fixing error if heroku config command returns no config vars. (@brad)
16 | - Removing dependency on South.
17 |
18 |
19 | 0.9.18 - 21/01/2014
20 | -------------------
21 |
22 | - Fixing issues with executable permissions on scripts installed via herokuapp_startproject.
23 |
24 |
25 | 0.9.17 - 19/01/2014
26 | -------------------
27 |
28 | - An improved Heroku toolbelt installation should work on all Linux distros (not just on Ubuntu).
29 | - Django 1.5 compatibility.
30 | - Ability to specify a Heroku buildpack using the ``HEROKU_BUILDPACK_URL`` setting.
31 | - Faster deploys by bypassing Dyno scaling when possible.
32 |
33 |
34 | 0.9.16 - 14/01/2014
35 | -------------------
36 |
37 | - Intelligently determining if syncdb or migrate has to be run during a deploy, and not running if not needed.
38 |
39 |
40 | 0.9.15 - 13/01/2014
41 | -------------------
42 |
43 | - Unwrapping Django management commands output and error streams for correct unicode handling.
44 |
45 |
46 | 0.9.14 - 10/01/2014
47 | -------------------
48 |
49 | - Better detection and fallback for scripts that require the Heroku toolbelt, but cannot locate it.
50 | - Default deploy script is aware of popular CI build systems, and only builds on the master branch by default.
51 | - Information about forcing SSL on an app.
52 | - Improving heroku_audit command with detection for more potential problems.
53 | - Allowing email providers other than SendGrid to be used without breaking heroku_audit.
54 |
55 |
56 | 0.9.13 - 03/01/2014
57 | -------------------
58 |
59 | - Correctly interpreting Django BASE_DIR setting.
60 |
61 |
62 | 0.9.12 - 03/01/2014
63 | -------------------
64 |
65 | - Adding in Heroku-compatible logging configuration to project template settings.
66 | - Updated project template to match Django 1.6 default settings.
67 | - Added heroku_audit command.
68 | - Removing herokuapp subcommand. Loading the config in manage.py is preferred.
69 |
70 |
71 | 0.9.11 - 20/02/2013
72 | -------------------
73 |
74 | - Adding an --app parameter to the herokuapp and heroku_deploy management commands.
75 | - Updating Django version for new security release.
76 | - Package updates for all base requirements.
77 | - Changing default HTTP server to waitress.
78 | - Adding default settings for HTTPS proxy headers and allowed hosts.
79 |
80 |
81 | 0.9.9 - 15/01/2012
82 | ------------------
83 |
84 | - Fixing hash generation for gzipped content.
85 |
86 |
87 | 0.9.8 - 14/01/2012
88 | ------------------
89 |
90 | - Updating packages, notably django-storages.
91 | - Removing hacked S3 storage, now that django-storages has fixed gzipping.
92 |
93 |
94 | 0.9.7 - 09/01/2012
95 | ------------------
96 |
97 | - Adding in ``herokuapp`` subcommand and ``heroku_deploy`` command.
98 | - Adding in ``CanonicalDomainMiddleware``.
99 |
100 |
101 | 0.9.6 - 27/12/2012
102 | ------------------
103 |
104 | - Bugfix for gzipped Amazon S3 static files.
105 |
106 |
107 | 0.9.5 - 26/12/2012
108 | ------------------
109 |
110 | - Adding support for gzipped Amazon S3 static files.
111 |
112 |
113 | 0.9.4 - 19/12/2012
114 | ------------------
115 |
116 | - Updating requirements.txt for latest versions of 3rd party packages.
117 |
118 |
119 | 0.9.3 - 28/11/2012
120 | ------------------
121 |
122 | - Updating requirements.txt for latest versions of 3rd party packages.
123 |
124 |
125 | 0.9.2 - 09/11/2012
126 | ------------------
127 |
128 | - Updating requirements.txt for latest versions of 3rd party packages.
129 |
130 |
131 | 0.9.1 - 02/11/2012
132 | ------------------
133 |
134 | - Fixing missing entry in default requirements.txt.
135 | - Fixing typos in README.md.
136 |
137 |
138 | 0.9.0 - 02/11/2012
139 | ------------------
140 |
141 | - First beta release of django-herokuapp.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009, David Hall.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in the
12 | documentation and/or other materials provided with the distribution.
13 |
14 | 3. Neither the name of David Hall nor the names of its contributors may be
15 | used to endorse or promote products derived from this software without
16 | specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
3 | include .gitignore
4 | recursive-include herokuapp .gitignore .slugignore *.py *.sh
5 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | django-herokuapp
2 | ================
3 |
4 | **django-herokuapp** is a set of utilities and a project template for running
5 | Django sites on `Heroku `_.
6 |
7 |
8 | Why not just use Heroku's Django guide?
9 | ---------------------------------------
10 |
11 | Heroku provides `a guide `_
12 | that describes a reasonable Django project setup. The django-herokuapp project suggests a more advanced approach
13 | with the following benefits:
14 |
15 | - `waitress `_ is used as an app server instead of
16 | `gunicorn `_. Gunicorn is not recommended for use as a public-facing server,
17 | whereas waitress is hardened for production use.
18 | - Amazon S3 is used to serve static files instead of `django-static `_.
19 | Django `does not recommend `_
20 | serving static files using a Python app server.
21 | - Various minor security and logging improvements.
22 |
23 |
24 | Starting from scratch
25 | ---------------------
26 |
27 | If you're creating a new Django site for hosting on Heroku, then you can give youself a headstart by running
28 | the ``herokuapp_startproject.py`` script that's bundled with this package from within a fresh virtual environment.
29 |
30 | ::
31 |
32 | $ mkdir your/project/location
33 | $ cd your/project/location
34 | $ git init
35 | $ virtualenv venv
36 | $ source venv/bin/activate
37 | $ pip install django-herokuapp
38 | $ herokuapp_startproject.py
39 |
40 |
41 | The rest of this guide describes the process of adapting an existing Django site to run on Heroku. Even if
42 | you've started from scratch using ``herokuapp_startproject.py``, it's still worth reading, as it will
43 | give you a better understanding of the way your site has been configured.
44 |
45 |
46 | Installing in an existing project
47 | ---------------------------------
48 |
49 | 1. Install django-herokuapp using pip ``pip install django-herokuapp``.
50 | 2. Add ``'herokuapp'`` to your ``INSTALLED_APPS`` setting.
51 | 3. Read the rest of this README for pointers on setting up your Heroku site.
52 |
53 |
54 | Site hosting - waitress
55 | ^^^^^^^^^^^^^^^^^^^^^^^
56 |
57 | A site hosted on Heroku has to handle traffic without the benefit of a buffering reverse proxy like nginx, which means
58 | that the normal approach of using a small pool of worker threads won't scale in production, particularly if
59 | serving clients over a slow connection.
60 |
61 | The solution is to use a buffering async master thread with sync workers instead, and the
62 | `waitress `_ project provides an excellent implementation of this approach.
63 |
64 | Simply create a file called ``Procfile`` in the root of your project, and add the following line to it:
65 |
66 | ::
67 |
68 | web: waitress-serve --port=$PORT .wsgi:application
69 |
70 |
71 | Database hosting - Heroku Postgres
72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
73 |
74 | Heroku provides an excellent `Postgres Add-on `_ that you can use for your site.
75 | The recommended settings for using Heroku Postgres are as follows:
76 |
77 | ::
78 |
79 | import dj_database_url
80 | DATABASES = {
81 | "default": dj_database_url.config(default='postgres://localhost'),
82 | }
83 |
84 | This configuration relies on the `dj-database-url `_ package, which
85 | is included in the dependencies for django-herokuapp.
86 |
87 | You can provision a starter package with Heroku Postgres using the following Heroku command:
88 |
89 | ::
90 |
91 | $ heroku addons:create heroku-postgresql:dev
92 |
93 |
94 | Static file hosting - Amazon S3
95 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
96 |
97 | A pure-python webserver like waitress isn't best suited to serving high volumes of static files. For this, a cloud-based
98 | service like `Amazon S3 `_ is ideal.
99 |
100 | The recommended settings for hosting your static content with Amazon S3 is as follows:
101 |
102 | ::
103 |
104 | # Use Amazon S3 for storage for uploaded media files.
105 | DEFAULT_FILE_STORAGE = "storages.backends.s3boto.S3BotoStorage"
106 |
107 | # Use Amazon S3 for static files storage.
108 | STATICFILES_STORAGE = "require_s3.storage.OptimizedCachedStaticFilesStorage"
109 |
110 | # Amazon S3 settings.
111 | AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "")
112 | AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "")
113 | AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME", "")
114 | AWS_AUTO_CREATE_BUCKET = True
115 | AWS_HEADERS = {
116 | "Cache-Control": "public, max-age=86400",
117 | }
118 | AWS_S3_FILE_OVERWRITE = False
119 | AWS_QUERYSTRING_AUTH = False
120 | AWS_S3_SECURE_URLS = True
121 | AWS_REDUCED_REDUNDANCY = False
122 | AWS_IS_GZIPPED = False
123 |
124 | # Cache settings.
125 | CACHES = {
126 | # Long cache timeout for staticfiles, since this is used heavily by the optimizing storage.
127 | "staticfiles": {
128 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
129 | "TIMEOUT": 60 * 60 * 24 * 365,
130 | "LOCATION": "staticfiles",
131 | },
132 | }
133 |
134 | You can set your AWS account details by running the following command:
135 |
136 | ::
137 |
138 | $ heroku config:set AWS_ACCESS_KEY_ID=your_key_id \
139 | AWS_SECRET_ACCESS_KEY=your_secret_access_key \
140 | AWS_STORAGE_BUCKET_NAME=your_bucket_name
141 |
142 | This configuration relies on the `django-require-s3 `_ package, which
143 | is included in the dependencies for django-herokuapp. In particular, the use of `django-require `_
144 | to compress and serve your assets is recommended, since it allows assets to be precompiled during the project's
145 | build step, rather than on-the-fly as the site is running.
146 |
147 |
148 | Email hosting - SendGrid
149 | ^^^^^^^^^^^^^^^^^^^^^^^^
150 |
151 | Heroku does not provide an SMTP server in its default package. Instead, it's recommended that you use
152 | the `SendGrid Add-on `_ to send your site's emails.
153 |
154 | ::
155 |
156 | # Email settings.
157 | EMAIL_HOST = "smtp.sendgrid.net"
158 | EMAIL_HOST_USER = os.environ.get("SENDGRID_USERNAME", "")
159 | EMAIL_HOST_PASSWORD = os.environ.get("SENDGRID_PASSWORD", "")
160 | EMAIL_PORT = 25
161 | EMAIL_USE_TLS = False
162 |
163 | You can provision a starter package with SendGrid using the following Heroku command:
164 |
165 | ::
166 |
167 | $ heroku addons:create sendgrid:starter
168 |
169 |
170 | Optimizing compiled slug size
171 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
172 |
173 | The smaller the size of your compiled project, the faster it can be redeployed on Heroku servers. To this end,
174 | django-herokuapp provides a suggested `.slugignore `_
175 | file that should be placed in the root of your project. If you've used the ``herokuapp_startproject.py`` script
176 | to set up your project, then this will have already been taken care of for you.
177 |
178 |
179 | Improving site security
180 | ^^^^^^^^^^^^^^^^^^^^^^^
181 |
182 | Ideally, you should not store your site's ``SECRET_KEY`` setting in version control. Instead, it should be read
183 | from the Heroku config as follows:
184 |
185 | ::
186 |
187 | from django.utils.crypto import get_random_string
188 | SECRET_KEY = os.environ.get("SECRET_KEY", get_random_string(50, "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)"))
189 |
190 | You can then generate a secret key in your Heroku config with the following command:
191 |
192 | ::
193 |
194 | $ heroku config:set SECRET_KEY=`openssl rand -base64 32`
195 |
196 | It's also recommended that you configure Python to generate a new random seed every time it boots.
197 |
198 | ::
199 |
200 | $ heroku config:set PYTHONHASHSEED=random
201 |
202 |
203 | Adding support for Heroku SSL
204 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
205 |
206 | Heroku provides a free `piggyback SSL `_
207 | service for all of its apps, as well as a `SSL endpoint addon `_
208 | for custom domains. It order to detect when a request is made via SSL in Django (for use in `request.is_secure()`),
209 | you should add the following setting to your app:
210 |
211 | ::
212 |
213 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
214 |
215 | If you intend to serve your entire app over SSL, then it's a good idea to force all requests to use SSL. The
216 | `django-sslify `_ app provides a middleware for this. Simply `pip install django-sslify`,
217 | then add ``"sslify.middleware.SSLifyMiddleware"`` to the start of your ``MIDDLEWARE_CLASSES``.
218 |
219 |
220 | Outputting logs to Heroku logplex
221 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
222 |
223 | By default, Django does not log errors to STDERR in production, which is the correct behaviour for most WSGI
224 | apps. Heroku, however, provides an excellent logging service that expects to receive error messages on STDERR.
225 | You can take advantage of this by updating your logging configuration to the following:
226 |
227 | ::
228 |
229 | LOGGING = {
230 | "version": 1,
231 | "disable_existing_loggers": False,
232 | "handlers": {
233 | "console": {
234 | "level": "INFO",
235 | "class": "logging.StreamHandler",
236 | },
237 | },
238 | "loggers": {
239 | "django": {
240 | "handlers": ["console"],
241 | }
242 | }
243 | }
244 |
245 |
246 | Running your site in the Heroku environment
247 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
248 |
249 | Because your site is setup to read some of its configuration from environmental variables stored on
250 | Heroku, running a development server can be tricky. django-herokuapp provides a configuration utility
251 | that should be added to your project to load the heroku config dynamically. Simply add
252 | the following lines to your ``manage.py`` script, at the top of the run block:
253 |
254 | ::
255 |
256 | if __name__ == "__main__": # << This line will already be present in manage.py
257 |
258 | # Load the Heroku environment.
259 | from herokuapp.env import load_env
260 | load_env(__file__, "your-app-name")
261 |
262 | Django management commands can then be run normally:
263 |
264 | ::
265 |
266 | $ python manage.py runserver
267 |
268 | Accessing the live Heroku Postgres database is a bad idea. Instead, you should provide a local settings file,
269 | exclude it from version control, and connect to a local PostgreSQL server. If you're
270 | on OSX, then the excellent `Postgres.app `_ will make this very easy.
271 |
272 | A suggested settings file layout, including the appropriate local settings, can be found in the `django-herokuapp
273 | template project settings directory `_.
274 |
275 |
276 | Validating your Heroku setup
277 | ----------------------------
278 |
279 | Once you've completed the above steps, and are confident that your site is suitable to deploy to Heroku,
280 | you can validate against common errors by running the ``heroku_audit`` management command.
281 |
282 | ::
283 |
284 | $ python manage.py heroku_audit
285 |
286 | Many of the issues detected by ``heroku_audit`` have simple fixes. For a guided walkthrough of solutions, try
287 | running:
288 |
289 | ::
290 |
291 | $ python manage.py heroku_audit --fix
292 |
293 |
294 | Common error messages
295 | ---------------------
296 |
297 | Things don't always go right first time. Here are some common error messages you may encounter:
298 |
299 |
300 | "No app specified" when running Heroku commands
301 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
302 |
303 | The Heroku CLI looks up your app's name from a git remote named ``heroku``. You can either specify the app
304 | to manage by adding ``-a your-app-name`` every time you call a Heroku command, or update your git repo with a
305 | Heroku remote using the following command:
306 |
307 | ::
308 |
309 | $ git remote add heroku git@heroku.com:your-app-name.git
310 |
311 |
312 | "AttributeError: 'Settings' object has no attribute 'BASE_DIR'"
313 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
314 |
315 | Many django-herokuapp commands need to know the root of the project's file stucture. Django 1.6 provides
316 | this setting automatically as ``settings.BASE_DIR``. If this setting is not present in your settings file,
317 | it should be added as an absolute path. You can look it up dynamically from the settings file like this:
318 |
319 | ::
320 |
321 | import os.path
322 | # Assumes the settings file is located in `your_project.settings` package.
323 | BASE_DIR = os.path.abspath(os.path.join(__file__, "..", ".."))
324 |
325 |
326 | Support and announcements
327 | -------------------------
328 |
329 | Downloads and bug tracking can be found at the `main project website `_.
330 |
331 |
332 | More information
333 | ----------------
334 |
335 | The django-herokuapp project was developed by Dave Hall. You can get the code
336 | from the `django-herokuapp project site `_.
337 |
338 | Dave Hall is a freelance web developer, based in Cambridge, UK. You can usually
339 | find him on the Internet in a number of different places:
340 |
341 | - `Website `_
342 | - `Twitter `_
343 | - `Google Profile `_
344 |
--------------------------------------------------------------------------------
/herokuapp/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | A set of utilities and a project template for running Django sites on heroku.
3 |
4 | Developed by Dave Hall.
5 |
6 |
7 | """
8 |
9 |
10 | __version__ = (0, 9, 20)
--------------------------------------------------------------------------------
/herokuapp/bin/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etianen/django-herokuapp/1d3ec10f6e83b7556a443150aa0658bbf341f6d1/herokuapp/bin/__init__.py
--------------------------------------------------------------------------------
/herokuapp/bin/herokuapp_startproject.py:
--------------------------------------------------------------------------------
1 | import sys, os, stat, os.path, getpass, subprocess, argparse
2 |
3 | from django.core import management
4 |
5 |
6 | parser = argparse.ArgumentParser(
7 | description = "Start a new herokuapp Django project.",
8 | )
9 | parser.add_argument("project_name",
10 | help = "The name of the project to create.",
11 | )
12 | parser.add_argument("dest_dir",
13 | default = ".",
14 | nargs = "?",
15 | help = "The destination dir for the created project.",
16 | )
17 | parser.add_argument("-a", "--app",
18 | default = None,
19 | dest = "app",
20 | required = False,
21 | help = "The name of the Heroku app. Defaults to the project name, with underscores replaced by hyphens.",
22 | )
23 | parser.add_argument("--noinput",
24 | action = "store_false",
25 | default = True,
26 | dest = "interactive",
27 | help = "Tells Django to NOT prompt the user for input of any kind.",
28 | )
29 |
30 |
31 | def make_executable(path):
32 | st = os.stat(path)
33 | os.chmod(path, st.st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
34 |
35 |
36 | def main():
37 | args = parser.parse_args()
38 | # Generate Heroku app name.
39 | app_name = args.app or args.project_name.replace("_", "-")
40 | # Create the project.
41 | try:
42 | os.makedirs(args.dest_dir)
43 | except OSError:
44 | pass
45 | management.call_command("startproject",
46 | args.project_name,
47 | args.dest_dir,
48 | template = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "project_template")),
49 | extensions = ("py", "txt", "slugignore", "conf", "gitignore", "sh",),
50 | app_name = app_name,
51 | user = getpass.getuser(),
52 | )
53 | # Make management scripts executable.
54 | make_executable(os.path.join(args.dest_dir, "manage.py"))
55 | # Audit and configure the project for Heroku.
56 | audit_args = ["python", os.path.join(args.dest_dir, "manage.py"), "heroku_audit", "--fix"]
57 | if not args.interactive:
58 | audit_args.append("--noinput")
59 | audit_returncode = subprocess.call(audit_args)
60 | if audit_returncode != 0:
61 | sys.exit(audit_returncode)
62 | # Give some help to the user.
63 | print "Heroku project created."
64 |
--------------------------------------------------------------------------------
/herokuapp/commands.py:
--------------------------------------------------------------------------------
1 | import re, os
2 | from collections import Counter
3 | from functools import partial
4 |
5 | import sh
6 |
7 | from django.core.management import CommandError
8 | from django.utils.encoding import force_text
9 |
10 |
11 | RE_PARSE_SHELL = re.compile("^.*=.*$", re.MULTILINE)
12 |
13 | RE_PS = re.compile("^(\w+)\.")
14 |
15 | RE_POSTGRES = re.compile("^HEROKU_POSTGRESQL_\w+?_URL$")
16 |
17 |
18 | def parse_shell(lines):
19 | """ Parse config variables from the lines """
20 |
21 | # If there are no config variables, return an empty dict
22 | if not RE_PARSE_SHELL.search(str(lines)):
23 | return dict()
24 | return dict(
25 | line.strip().split("=", 1)
26 | for line
27 | in lines
28 | )
29 |
30 |
31 | def format_command(prefix, args, kwargs):
32 | return u"COMMAND: {prefix} {args} {kwargs}".format(
33 | prefix = prefix,
34 | args = u" ".join(map(force_text, args)),
35 | kwargs = u" ".join(
36 | u"--{key}={value}".format(
37 | key = key.replace("_", "-"),
38 | value = value,
39 | )
40 | for key, value
41 | in kwargs.items()
42 | )
43 | )
44 |
45 |
46 | class HerokuCommandError(CommandError):
47 |
48 | pass
49 |
50 |
51 | class HerokuCommand(object):
52 |
53 | def __init__(self, app, cwd, stdout=None, stderr=None, dry_run=False):
54 | # Store the dry run state.
55 | self.dry_run = dry_run
56 | self._stdout = stdout
57 | # Check that the heroku command is available.
58 | if hasattr(sh, "heroku"):
59 | heroku_command = sh.heroku
60 | else:
61 | raise HerokuCommandError("Heroku toolbelt is not installed. Install from https://toolbelt.heroku.com/")
62 | # Create the Heroku command wrapper.
63 | self._heroku = partial(heroku_command,
64 | _cwd = cwd,
65 | _out = stdout,
66 | _err = stderr,
67 | ) # Not using bake(), as it gets the command order wrong.
68 | # Ensure that the user is logged in.
69 | def auth_token_interact(line, stdin, process):
70 | if line == "\n":
71 | stdin.put("\n")
72 | try:
73 | self("auth:token", _force_live_run=True, _in=None, _tty_in=True, _out=auth_token_interact, _out_bufsize=0).wait()
74 | except sh.ErrorReturnCode:
75 | raise HerokuCommandError("Please log in to the Heroku Toolbelt using `heroku auth:login`.")
76 | # Apply the app argument.
77 | if app:
78 | self._heroku = partial(self._heroku, app=app)
79 |
80 | def __call__(self, *args, **kwargs):
81 | # Allow dry run to be overridden for selective (non-mutating) commands.
82 | force_live_run = kwargs.pop("_force_live_run", False)
83 | # Run the command.
84 | if self.dry_run and not force_live_run:
85 | # Allow a dry run to be processed.
86 | self._stdout.write(format_command("heroku", args, kwargs) + "\n")
87 | else:
88 | # Call a live command.
89 | try:
90 | return self._heroku(*args, **kwargs)
91 | except sh.ErrorReturnCode as ex:
92 | raise HerokuCommandError(force_text(ex))
93 |
94 | def config_set(self, **kwargs):
95 | return self("config:set", *[
96 | "{key}={value}".format(
97 | key = key,
98 | value = value,
99 | )
100 | for key, value
101 | in kwargs.items()
102 | ], _out=None)
103 |
104 | def config_get(self, name=None):
105 | if name:
106 | return str(self("config:get", name, _out=None, _force_live_run=True)).strip()
107 | return parse_shell(self("config", shell=True, _out=None))
108 |
109 | def ps(self):
110 | counter = Counter()
111 | for line in self("ps", _out=None, _iter=True, _force_live_run=True):
112 | match = RE_PS.match(line)
113 | if match:
114 | process_name = match.group(1)
115 | if process_name not in ("run"):
116 | counter[process_name] += 1
117 | return counter
118 |
119 | def scale(self, **kwargs):
120 | return self("ps:scale", *[
121 | "{name}={count}".format(
122 | name = name,
123 | count = count,
124 | )
125 | for name, count
126 | in kwargs.items()
127 | ])
128 |
129 | def postgres_url(self):
130 | for line in self("config", shell=True, _out=None, _force_live_run=True):
131 | key = line.split("=", 1)[0]
132 | if RE_POSTGRES.match(key):
133 | return key
134 |
--------------------------------------------------------------------------------
/herokuapp/env.py:
--------------------------------------------------------------------------------
1 | import os, os.path
2 |
3 | from herokuapp.commands import HerokuCommand, HerokuCommandError
4 |
5 |
6 | def load_env(entrypoint, app=None):
7 | try:
8 | heroku = HerokuCommand(
9 | app = app,
10 | cwd = os.path.dirname(entrypoint),
11 | )
12 | heroku_config = heroku.config_get()
13 | except HerokuCommandError:
14 | pass
15 | else:
16 | for key, value in heroku_config.items():
17 | os.environ.setdefault(key, value)
18 |
--------------------------------------------------------------------------------
/herokuapp/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etianen/django-herokuapp/1d3ec10f6e83b7556a443150aa0658bbf341f6d1/herokuapp/management/__init__.py
--------------------------------------------------------------------------------
/herokuapp/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etianen/django-herokuapp/1d3ec10f6e83b7556a443150aa0658bbf341f6d1/herokuapp/management/commands/__init__.py
--------------------------------------------------------------------------------
/herokuapp/management/commands/base.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | from optparse import make_option
4 |
5 | from django.utils.functional import cached_property
6 | from django.conf import settings
7 | from django.core.management import call_command
8 |
9 | from herokuapp.commands import HerokuCommand, format_command
10 | from herokuapp.settings import HEROKU_APP_NAME
11 |
12 |
13 | class HerokuCommandMixin(object):
14 |
15 | option_list = (
16 | make_option("-a", "--app",
17 | default = HEROKU_APP_NAME,
18 | dest = "app",
19 | help = "The name of the Heroku app to use. Defaults to HEROKU_APP_NAME.",
20 | ),
21 | make_option("--dry-run",
22 | action = "store_true",
23 | default = False,
24 | dest = "dry_run",
25 | help = "Outputs the heroku and django managment commands that will be run, but doesn't execute them.",
26 | ),
27 | )
28 |
29 | def call_command(self, *args, **kwargs):
30 | """
31 | Calls the given management command, but only if it's not a dry run.
32 |
33 | If it's a dry run, then a notice about the command will be printed.
34 | """
35 | if self.dry_run:
36 | self.stdout.write(format_command("python manage.py", args, kwargs))
37 | else:
38 | call_command(*args, **kwargs)
39 |
40 | @cached_property
41 | def heroku(self):
42 | return HerokuCommand(
43 | app = self.app,
44 | cwd = settings.BASE_DIR,
45 | stdout = self.stdout._out,
46 | stderr = self.stderr._out,
47 | dry_run = self.dry_run,
48 | )
49 |
--------------------------------------------------------------------------------
/herokuapp/management/commands/heroku_audit.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | import os, os.path, sys
4 | from optparse import make_option
5 |
6 | import sh
7 |
8 | from django.conf import settings
9 | from django.core.management.base import NoArgsCommand, BaseCommand
10 | from django.utils.crypto import get_random_string
11 | from django.core.files.storage import default_storage
12 |
13 | from storages.backends.s3boto import S3BotoStorage
14 |
15 | from herokuapp.commands import HerokuCommandError
16 | from herokuapp.management.commands.base import HerokuCommandMixin
17 |
18 |
19 | class Command(HerokuCommandMixin, NoArgsCommand):
20 |
21 | help = "Tests this app for common Heroku deployment issues."
22 |
23 | option_list = BaseCommand.option_list + (
24 | make_option("--noinput",
25 | action = "store_false",
26 | default = True,
27 | dest = "interactive",
28 | help = "Tells Django to NOT prompt the user for input of any kind.",
29 | ),
30 | make_option("--fix",
31 | action = "store_true",
32 | default = False,
33 | dest = "fix",
34 | help = "If specified, then the user will be prompted to fix problems as they are found. Combine with --noinput to auto-fix problems.",
35 | ),
36 | ) + HerokuCommandMixin.option_list
37 |
38 | def exit_with_error(self, error):
39 | self.stderr.write(error)
40 | self.stderr.write("Heroku audit aborted.")
41 | self.stderr.write("Run `python manage.py heroku_audit --fix` to fix problems.")
42 | sys.exit(1)
43 |
44 | def prompt_for_fix(self, error, message):
45 | if self.fix:
46 | self.stdout.write(error)
47 | if self.interactive:
48 | # Ask to fix the issue.
49 | answer = ""
50 | while not answer in ("y", "n"):
51 | answer = raw_input("{message} (y/n) > ".format(
52 | message = message,
53 | )).lower().strip()
54 | answer_bool = answer == "y"
55 | else:
56 | # Attempt to auto-fix the issue.
57 | answer_bool = True
58 | else:
59 | answer_bool = False
60 | # Exit if no fix provided.
61 | if not answer_bool:
62 | self.exit_with_error(error)
63 |
64 | def read_string(self, message, default):
65 | if self.interactive:
66 | answer = ""
67 | while not answer:
68 | answer = raw_input("{message} {default}> ".format(
69 | message = message,
70 | default = "({default}) ".format(
71 | default = default,
72 | ) if default else ""
73 | )).strip() or default
74 | return answer
75 | else:
76 | return default
77 |
78 | def handle(self, **kwargs):
79 | self.app = kwargs["app"]
80 | self.dry_run = kwargs["dry_run"]
81 | self.interactive = kwargs["interactive"]
82 | self.fix = kwargs["fix"]
83 | # Check app exists.
84 | try:
85 | self.heroku("apps:info")
86 | except HerokuCommandError:
87 | self.prompt_for_fix("No Heroku app named '{app}' detected.".format(app=self.app), "Create app?")
88 | self.heroku("apps:create", self.app)
89 | self.stdout.write("Heroku app created.")
90 | # Check that Amazon S3 is being used for media.
91 | default_storage._setup()
92 | if not isinstance(default_storage._wrapped, S3BotoStorage):
93 | self.exit_with_error("settings.DEFAULT_FILE_STORAGE should be set to a subclass of `storages.backends.s3boto.S3BotoStorage`.")
94 | # Check for AWS access details.
95 | if not self.heroku.config_get("AWS_ACCESS_KEY_ID"):
96 | self.prompt_for_fix("Amazon S3 access details not present in Heroku config.", "Setup now?")
97 | aws_env = {}
98 | aws_env["AWS_ACCESS_KEY_ID"] = self.read_string("AWS access key", os.environ.get("AWS_ACCESS_KEY_ID"))
99 | aws_env["AWS_SECRET_ACCESS_KEY"] = self.read_string("AWS access secret", os.environ.get("AWS_SECRET_ACCESS_KEY"))
100 | aws_env["AWS_STORAGE_BUCKET_NAME"] = self.read_string("S3 bucket name", self.app)
101 | # Save Heroku config.
102 | self.heroku.config_set(**aws_env)
103 | self.stdout.write("Amazon S3 config written to Heroku config.")
104 | # Check for SendGrid settings.
105 | if settings.EMAIL_HOST == "smtp.sendgrid.net" and not self.heroku.config_get("SENDGRID_USERNAME"):
106 | self.prompt_for_fix("SendGrid addon not installed.", "Provision SendGrid starter addon (free)?")
107 | self.heroku("addons:add", "sendgrid:starter")
108 | self.stdout.write("SendGrid addon provisioned.")
109 | # Check for promoted database URL.
110 | if not self.heroku.config_get("DATABASE_URL"):
111 | database_url = self.heroku.postgres_url()
112 | if not database_url:
113 | self.prompt_for_fix("Database URL not present in Heroku config.", "Provision Heroku Postgres dev addon (free)?")
114 | self.heroku("addons:add", "heroku-postgresql")
115 | self.heroku("pg:wait")
116 | self.stdout.write("Heroku Postgres addon provisioned.")
117 | # Load the new database URL.
118 | database_url = self.heroku.postgres_url()
119 | # Promote the database URL.
120 | self.prompt_for_fix("No primary database URL set.", "Promote {database_url}?".format(database_url=database_url))
121 | self.heroku("pg:promote", database_url)
122 | self.stdout.write("Heroku primary database URL set.")
123 | # Check for secret key.
124 | heroku_secret_key = self.heroku.config_get("SECRET_KEY")
125 | if not heroku_secret_key:
126 | self.prompt_for_fix("Secret key not set in Heroku config.", "Generate now?")
127 | self.heroku.config_set(SECRET_KEY=get_random_string(50, "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)"))
128 | self.stdout.write("Secret key written to Heroku config. Ensure your settings file is configured to read the secret key: https://github.com/etianen/django-herokuapp#improving-site-security")
129 | # Check for Python hash seed.
130 | if not self.heroku.config_get("PYTHONHASHSEED"):
131 | self.prompt_for_fix("Python hash seed not set in Heroku config.", "Set now?")
132 | self.heroku.config_set(PYTHONHASHSEED="random")
133 | self.stdout.write("Python hash seed written to Heroku config.")
134 | # Check for SSL header settings.
135 | if not getattr(settings, "SECURE_PROXY_SSL_HEADER", None) == ("HTTP_X_FORWARDED_PROTO", "https"):
136 | self.exit_with_error("Missing SECURE_PROXY_SSL_HEADER settings. Please add `SECURE_PROXY_SSL_HEADER = (\"HTTP_X_FORWARDED_PROTO\", \"https\")` to your settings.py file.")
137 | # Check for Procfile.
138 | procfile_path = os.path.join(settings.BASE_DIR, "Procfile")
139 | if not os.path.exists(procfile_path):
140 | self.prompt_for_fix("Procfile must to be created to deploy to Heroku.", "Create now?")
141 | with open(procfile_path, "wb") as procfile_handle:
142 | procfile_handle.write("web: waitress-serve --port=$PORT {project_name}.wsgi:application\n".format(
143 | project_name = os.environ["DJANGO_SETTINGS_MODULE"].split(".", 1)[0],
144 | ))
145 | self.stdout.write("Default Procfile generated.")
146 | # Check for requirements.txt.
147 | requirements_path = os.path.join(settings.BASE_DIR, "requirements.txt")
148 | if not os.path.exists(requirements_path):
149 | self.prompt_for_fix("A requirements.txt file must be created to deploy to Heroku.", "Generate now?")
150 | sh.pip.freeze(_out=requirements_path)
151 | self.stdout.write("Dependencies frozen to requirements.txt.")
152 |
--------------------------------------------------------------------------------
/herokuapp/middleware.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import redirect
2 | from django.core.exceptions import MiddlewareNotUsed
3 | from django.conf import settings
4 |
5 | from herokuapp.settings import SITE_DOMAIN
6 |
7 |
8 | class CanonicalDomainMiddleware(object):
9 |
10 | """Middleware that redirects to a canonical domain."""
11 |
12 | def __init__(self):
13 | if settings.DEBUG or not SITE_DOMAIN:
14 | raise MiddlewareNotUsed
15 |
16 | def process_request(self, request):
17 | """If the request domain is not the canonical domain, redirect."""
18 | hostname = request.get_host().split(":", 1)[0]
19 | # Don't perform redirection for testing or local development.
20 | if hostname in ("testserver", "localhost", "127.0.0.1"):
21 | return
22 | # Check against the site domain.
23 | canonical_hostname = SITE_DOMAIN.split(":", 1)[0]
24 | if hostname != canonical_hostname:
25 | if request.is_secure():
26 | canonical_url = "https://"
27 | else:
28 | canonical_url = "http://"
29 | canonical_url += SITE_DOMAIN + request.get_full_path()
30 | return redirect(canonical_url, permanent=True)
--------------------------------------------------------------------------------
/herokuapp/project_template/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .project
3 | .pydevproject
4 | .settings
5 | *.pyc
6 | *.pyo
7 | .bzr
8 | .bzrignore
9 | Thumbs.db
10 | /dist
11 | /build
12 | /MANIFEST
13 | /venv
14 | /.env
15 | /static
16 | /media
17 |
--------------------------------------------------------------------------------
/herokuapp/project_template/.slugignore:
--------------------------------------------------------------------------------
1 | tests
2 | tests.py
3 | tests.pyc
4 | static
5 | local.py
6 | local.pyc
7 | node_modules
8 |
--------------------------------------------------------------------------------
/herokuapp/project_template/manage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import os
3 | import sys
4 |
5 | if __name__ == "__main__":
6 |
7 | # Load the Heroku environment.
8 | from herokuapp.env import load_env
9 | load_env(__file__, "{{ app_name }}")
10 |
11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
12 |
13 | from django.core.management import execute_from_command_line
14 |
15 | execute_from_command_line(sys.argv)
16 |
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etianen/django-herokuapp/1d3ec10f6e83b7556a443150aa0658bbf341f6d1/herokuapp/project_template/project_name/__init__.py
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/apps/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/etianen/django-herokuapp/1d3ec10f6e83b7556a443150aa0658bbf341f6d1/herokuapp/project_template/project_name/apps/__init__.py
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/settings/.gitignore:
--------------------------------------------------------------------------------
1 | /local.py
2 |
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/settings/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Settings used by {{ project_name }} project.
3 |
4 | This consists of the general production settings, with an optional import of any local
5 | settings.
6 | """
7 |
8 | # Import production settings.
9 | from {{ project_name }}.settings.production import *
10 |
11 | # Import optional local settings.
12 | try:
13 | from {{ project_name }}.settings.local import *
14 | except ImportError:
15 | pass
16 |
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/settings/local.py:
--------------------------------------------------------------------------------
1 | """
2 | Settings for local development.
3 |
4 | These settings are not fast or efficient, but allow local servers to be run
5 | using the django-admin.py utility.
6 |
7 | This file should be excluded from version control to keep the settings local.
8 | """
9 |
10 | import os.path
11 |
12 | from production import BASE_DIR
13 |
14 |
15 | # Run in debug mode.
16 |
17 | DEBUG = True
18 |
19 | TEMPLATE_DEBUG = DEBUG
20 |
21 |
22 | # Serve staticfiles locally for development.
23 |
24 | STATICFILES_STORAGE = "require.storage.OptimizedCachedStaticFilesStorage"
25 |
26 | STATIC_URL = "/static/"
27 |
28 | STATIC_ROOT = os.path.join(BASE_DIR, "static")
29 |
30 |
31 | # Use local server.
32 |
33 | SITE_DOMAIN = "localhost:8000"
34 |
35 | PREPEND_WWW = False
36 |
37 | ALLOWED_HOSTS = ("*",)
38 |
39 |
40 | # Disable the template cache for development.
41 |
42 | TEMPLATE_LOADERS = (
43 | "django.template.loaders.filesystem.Loader",
44 | "django.template.loaders.app_directories.Loader",
45 | )
46 |
47 |
48 | # Local database settings. These should work well with http://postgresapp.com/.
49 |
50 | DATABASES = {
51 | "default": {
52 | "ENGINE": "django.db.backends.postgresql_psycopg2",
53 | "HOST": "localhost",
54 | "NAME": "{{ project_name }}",
55 | "USER": "{{ user }}",
56 | "PASSWORD": "",
57 | },
58 | }
59 |
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/settings/production.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for bar project.
3 |
4 | For more information on this file, see
5 | https://docs.djangoproject.com/en/1.6/topics/settings/
6 |
7 | For the full list of settings and their values, see
8 | https://docs.djangoproject.com/en/1.6/ref/settings/
9 | """
10 |
11 | import os
12 | import dj_database_url
13 | from django.utils.crypto import get_random_string
14 |
15 |
16 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
17 |
18 | SITE_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
19 |
20 | BASE_DIR = os.path.abspath(os.path.join(SITE_ROOT, ".."))
21 |
22 |
23 | # Heroku platform settings.
24 |
25 | HEROKU_APP_NAME = "{{ app_name }}"
26 |
27 | HEROKU_BUILDPACK_URL = "https://github.com/heroku/heroku-buildpack-python.git"
28 |
29 |
30 | # The name and domain of this site.
31 |
32 | SITE_NAME = "Example"
33 |
34 | SITE_DOMAIN = "{{ app_name }}.herokuapp.com"
35 |
36 | PREPEND_WWW = False
37 |
38 |
39 | # Security settings.
40 |
41 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
42 |
43 | ALLOWED_HOSTS = (
44 | SITE_DOMAIN,
45 | "{HEROKU_APP_NAME}.herokuapp.com".format(
46 | HEROKU_APP_NAME = HEROKU_APP_NAME,
47 | ),
48 | )
49 |
50 |
51 | # Database settings.
52 |
53 | DATABASES = {
54 | "default": dj_database_url.config(default="postgresql://"),
55 | }
56 |
57 |
58 | # Use Amazon S3 for storage for uploaded media files.
59 |
60 | DEFAULT_FILE_STORAGE = "storages.backends.s3boto.S3BotoStorage"
61 |
62 |
63 | # Use Amazon S3 and RequireJS for static files storage.
64 |
65 | STATICFILES_STORAGE = "require_s3.storage.OptimizedCachedStaticFilesStorage"
66 |
67 |
68 | # Amazon S3 settings.
69 |
70 | AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
71 |
72 | AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
73 |
74 | AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_STORAGE_BUCKET_NAME")
75 |
76 | AWS_AUTO_CREATE_BUCKET = True
77 |
78 | AWS_HEADERS = {
79 | "Cache-Control": "public, max-age=86400",
80 | }
81 |
82 | AWS_S3_FILE_OVERWRITE = False
83 |
84 | AWS_QUERYSTRING_AUTH = False
85 |
86 | AWS_S3_SECURE_URLS = True
87 |
88 | AWS_REDUCED_REDUNDANCY = False
89 |
90 | AWS_IS_GZIPPED = False
91 |
92 | STATIC_URL = "https://{bucket_name}.s3.amazonaws.com/".format(
93 | bucket_name = AWS_STORAGE_BUCKET_NAME,
94 | )
95 |
96 |
97 | # Email settings.
98 |
99 | EMAIL_HOST = "smtp.sendgrid.net"
100 |
101 | EMAIL_HOST_USER = os.environ.get("SENDGRID_USERNAME")
102 |
103 | EMAIL_HOST_PASSWORD = os.environ.get("SENDGRID_PASSWORD")
104 |
105 | EMAIL_PORT = 25
106 |
107 | EMAIL_USE_TLS = False
108 |
109 | SERVER_EMAIL = u"{name} ".format(
110 | name = SITE_NAME,
111 | domain = SITE_DOMAIN,
112 | )
113 |
114 | DEFAULT_FROM_EMAIL = SERVER_EMAIL
115 |
116 | EMAIL_SUBJECT_PREFIX = "[%s] " % SITE_NAME
117 |
118 |
119 | # Error reporting settings. Use these to set up automatic error notifications.
120 |
121 | ADMINS = ()
122 |
123 | MANAGERS = ()
124 |
125 | SEND_BROKEN_LINK_EMAILS = False
126 |
127 |
128 | # Locale settings.
129 |
130 | TIME_ZONE = "UTC"
131 |
132 | LANGUAGE_CODE = "en-gb"
133 |
134 | USE_I18N = True
135 |
136 | USE_L10N = True
137 |
138 | USE_TZ = True
139 |
140 |
141 | # A list of additional installed applications.
142 |
143 | INSTALLED_APPS = (
144 | "django.contrib.sessions",
145 | "django.contrib.auth",
146 | "django.contrib.contenttypes",
147 | "django.contrib.messages",
148 | "django.contrib.staticfiles",
149 | "django.contrib.admin",
150 | "herokuapp",
151 | )
152 |
153 |
154 | # Additional static file locations.
155 |
156 | STATICFILES_DIRS = (
157 | os.path.join(SITE_ROOT, "static"),
158 | )
159 |
160 |
161 | # Dispatch settings.
162 |
163 | MIDDLEWARE_CLASSES = (
164 | "django.middleware.gzip.GZipMiddleware",
165 | "herokuapp.middleware.CanonicalDomainMiddleware",
166 | "django.contrib.sessions.middleware.SessionMiddleware",
167 | "django.middleware.common.CommonMiddleware",
168 | "django.middleware.csrf.CsrfViewMiddleware",
169 | "django.contrib.auth.middleware.AuthenticationMiddleware",
170 | "django.contrib.messages.middleware.MessageMiddleware",
171 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
172 | )
173 |
174 | ROOT_URLCONF = "{{ project_name }}.urls"
175 |
176 | WSGI_APPLICATION = "{{ project_name }}.wsgi.application"
177 |
178 | SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
179 |
180 | MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage"
181 |
182 | SITE_ID = 1
183 |
184 |
185 | # Absolute path to the directory where templates are stored.
186 |
187 | TEMPLATE_DIRS = (
188 | os.path.join(SITE_ROOT, "templates"),
189 | )
190 |
191 | TEMPLATE_LOADERS = (
192 | ("django.template.loaders.cached.Loader", (
193 | "django.template.loaders.filesystem.Loader",
194 | "django.template.loaders.app_directories.Loader",
195 | )),
196 | )
197 |
198 | TEMPLATE_CONTEXT_PROCESSORS = (
199 | "django.contrib.auth.context_processors.auth",
200 | "django.core.context_processors.debug",
201 | "django.core.context_processors.i18n",
202 | "django.core.context_processors.media",
203 | "django.core.context_processors.static",
204 | "django.core.context_processors.tz",
205 | # "django.core.context_processors.request",
206 | "django.contrib.messages.context_processors.messages",
207 | )
208 |
209 |
210 | # Namespace for cache keys, if using a process-shared cache.
211 |
212 | CACHE_MIDDLEWARE_KEY_PREFIX = "{{ project_name }}"
213 |
214 | CACHES = {
215 | "default": {
216 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
217 | },
218 | # Long cache timeout for staticfiles, since this is used heavily by the optimizing storage.
219 | "staticfiles": {
220 | "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
221 | "TIMEOUT": 60 * 60 * 24 * 365,
222 | "LOCATION": "staticfiles",
223 | },
224 | }
225 |
226 |
227 | # A secret key used for cryptographic algorithms.
228 |
229 | SECRET_KEY = os.environ.get("SECRET_KEY", get_random_string(50, "abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)"))
230 |
231 |
232 | # Logging configuration.
233 |
234 | LOGGING = {
235 | "version": 1,
236 | # Don't throw away default loggers.
237 | "disable_existing_loggers": False,
238 | "handlers": {
239 | # Redefine console logger to run in production.
240 | "console": {
241 | "level": "INFO",
242 | "class": "logging.StreamHandler",
243 | },
244 | },
245 | "loggers": {
246 | # Redefine django logger to use redefined console logging.
247 | "django": {
248 | "handlers": ["console"],
249 | }
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/static/js/.gitignore:
--------------------------------------------------------------------------------
1 | # Preserves folder structure.
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/templates/.gitignore:
--------------------------------------------------------------------------------
1 | # Preserves folder structure.
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import patterns, url, include
2 | from django.contrib import admin
3 | from django.views import generic
4 |
5 |
6 | admin.autodiscover()
7 |
8 |
9 | urlpatterns = patterns("",
10 |
11 | # Admin URLs.
12 | url(r"^admin/", include(admin.site.urls)),
13 |
14 | # There's no favicon here!
15 | url(r"^favicon.ico$", generic.RedirectView.as_view()),
16 |
17 | )
18 |
--------------------------------------------------------------------------------
/herokuapp/project_template/project_name/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for {{ project_name }} project.
3 |
4 | It exposes the WSGI callable as a module-level variable named ``application``.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/
8 | """
9 |
10 | import os
11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings")
12 |
13 | from django.core.wsgi import get_wsgi_application
14 | application = get_wsgi_application()
15 |
--------------------------------------------------------------------------------
/herokuapp/settings.py:
--------------------------------------------------------------------------------
1 | """Settings used by django-herokuapp."""
2 |
3 | from django.conf import settings
4 |
5 |
6 | # The name of the app on the Heroku platform.
7 | HEROKU_APP_NAME = getattr(settings, "HEROKU_APP_NAME", None)
8 |
9 | # The optional explicit buildpack URL.
10 | HEROKU_BUILDPACK_URL = getattr(settings, "HEROKU_BUILDPACK_URL", None)
11 |
12 | # The canonical site domain.
13 | SITE_DOMAIN = getattr(settings, "SITE_DOMAIN", None)
14 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 | from herokuapp import __version__
4 |
5 |
6 | version_str = ".".join(str(n) for n in __version__)
7 |
8 |
9 | setup(
10 | name = "django-herokuapp",
11 | version = version_str,
12 | license = "BSD",
13 | description = "A set of utilities and a project template for running Django sites on heroku.",
14 | author = "Dave Hall",
15 | author_email = "dave@etianen.com",
16 | url = "https://github.com/etianen/django-herokuapp",
17 | entry_points = {
18 | "console_scripts": [
19 | "herokuapp_startproject.py = herokuapp.bin.herokuapp_startproject:main",
20 | ],
21 | },
22 | packages = find_packages(),
23 | include_package_data = True,
24 | test_suite = "herokuapp.tests",
25 | install_requires = [
26 | "django>=1.7",
27 | "pytz",
28 | "waitress",
29 | "dj-database-url",
30 | "psycopg2",
31 | "django-require-s3",
32 | "boto",
33 | "sh",
34 | ],
35 | classifiers = [
36 | "Development Status :: 4 - Beta",
37 | "Environment :: Web Environment",
38 | "Intended Audience :: Developers",
39 | "License :: OSI Approved :: BSD License",
40 | "Operating System :: OS Independent",
41 | "Programming Language :: Python",
42 | "Framework :: Django",
43 | ],
44 | )
--------------------------------------------------------------------------------