├── .gitignore ├── Procfile ├── Procfile.dev ├── README.md ├── __init__.py ├── apps ├── __init__.py ├── examples │ ├── __init__.py │ ├── forms.py │ ├── tasks.py │ ├── urls.py │ └── views.py └── urls.py ├── fabfile.py ├── manage.py ├── requirements.txt ├── settings ├── __init__.py ├── aws.py ├── celerybeat.py ├── common.py ├── dev.py ├── prod.py └── static.py ├── static ├── images │ └── welcome │ │ └── icon1.png ├── scripts │ └── README └── stylesheets │ └── README ├── templates ├── examples │ └── email.html └── welcome.html └── tools ├── __init__.py ├── apps.py ├── database.py ├── git.py └── heroku.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | 3 | # Local data files 4 | *.db 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | 19 | # Installer logs 20 | pip-log.txt 21 | 22 | # Unit test / coverage reports 23 | .coverage 24 | .tox 25 | 26 | #Translations 27 | *.mo 28 | 29 | #Mr Developer 30 | .mr.developer.cfg 31 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn_django -b 0.0.0.0:$PORT -w 9 -k gevent --max-requests 250 --preload settings.prod 2 | celeryd: python manage.py celeryd -E -B --loglevel=INFO --settings=settings.prod 3 | celerybeat: python manage.py celerybeat -S djcelery.schedulers.DatabaseScheduler --settings=settings.prod -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: gunicorn_django -b 0.0.0.0:$PORT -w 9 -k gevent --max-requests 250 --preload settings.dev 2 | celeryd: python manage.py celeryd -E -B --loglevel=INFO --settings=settings.dev 3 | celerybeat: python manage.py celerybeat -S djcelery.schedulers.DatabaseScheduler --settings=settings.dev -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Django Heroku Bootstrap (DHB) 2 | 3 | Get your Django app running on Heroku in less than 5 minutes. Really. 4 | 5 | ## Quick start 6 | 7 | * Clone DHB 8 | ``` 9 | git clone https://github.com/callmephilip/django-heroku-bootstrap.git 10 | ``` 11 | * Make sure you are logged in to Heroku 12 | ``` 13 | heroku login 14 | ``` 15 | * Create a new heroku application 16 | ``` 17 | heroku create 18 | ``` 19 | * Sort your remotes out 20 | ``` 21 | git remote -v 22 | ``` 23 | You are most likely to see 2 distinct remotes at this point. Origin is pointing to DHB's Github and heroku pointing to the git repository for your Heroku application. Keep heroku and feel free to do whatever with the origin (remove or rename if you want to keep the upstream reference). Just saying. 24 | * Being a smart developer you are using a virtual environment for your project. Create one. Activate it. 25 | * Run pip install -r requirements.txt. Grab a cup of tea/coffee. Come back to find all packages successfully installed. 26 | ``` 27 | pip install -r requirements.txt 28 | ``` 29 | * Head to settings/aws.py and update your AWS credentials 30 | 31 | ```python 32 | #Your Amazon Web Services access key, as a string. 33 | AWS_ACCESS_KEY_ID = "" 34 | 35 | #Your Amazon Web Services secret access key, as a string. 36 | AWS_SECRET_ACCESS_KEY = "" 37 | ``` 38 | 39 | * Put a database on your Heroku 40 | 41 | ``` 42 | heroku addons:add heroku-postgresql:dev 43 | ``` 44 | 45 | * Running your app locally 46 | 47 | ``` 48 | fab run 49 | ``` 50 | 51 | * Deploy your application 52 | 53 | ``` 54 | fab deploy 55 | ``` 56 | 57 | ## What's in the box 58 | 59 | Check out requirements.txt for details on the dependencies. 60 | 61 | DHB comes with an opinionated but powerful arsenal of tools that will help you take over the 62 | world with your web app in no time 63 | 64 | * Postgres for storing data 65 | * Amazon S3 for static content 66 | * Amazon SES for emails 67 | * Redis as a key-value store 68 | * Celery for background tasks 69 | * Celerybeat for periodic tasks 70 | * Fabric for housekeeping 71 | 72 | All the settings are spread accross 6 files in the settings/ directory. 73 | * aws.py contains AWS credentials and settings 74 | * celerybeat.py has Celerybeat's schedule configuration 75 | * common.py has all your standard Django jazz that is identical for dev and production environments 76 | * dev.py contains development specific settings 77 | * prod.py contains production specific settings 78 | * static.py is used for collecstatic routine 79 | 80 | ### Static files 81 | 82 | In settings/aws.py set the name of your S3 bucket for the project 83 | 84 | ```python 85 | #Your Amazon Web Services storage bucket name, as a string. 86 | AWS_STORAGE_BUCKET_NAME = "" 87 | ``` 88 | 89 | ### Email 90 | 91 | Assuming you've provided your AWS credentials in the settings file, email will just work. When running locally, emails will be dumped in the console. In production, DHB will send your love letters through Amazon SES (make sure you use a verified sender address when sending emails with SES) 92 | 93 | ```python 94 | from django.core.mail import send_mail 95 | 96 | send_mail('testing mail', 'Here is the message.', 'bob@mysite.com', ['bob@gmail.com'], fail_silently=False) 97 | ``` 98 | 99 | ### Database please 100 | 101 | Of course. You first need to get Postrgres on your Heroku 102 | 103 | ``` 104 | heroku addons:add heroku-postgresql:dev 105 | ``` 106 | 107 | And you should be set. 108 | 109 | ### Redis 110 | 111 | Vegetables are good for you. Especially when they help you build amazing apps. DHB uses Redis as a broker for Celery, which in turn is used for running background tasks. 112 | 113 | If you don't have Redis running on your dev machine, get it [here](http://redis.io/download). 114 | 115 | Heroku offers an add-on called "Redis to go". Let's activate it 116 | 117 | ``` 118 | heroku addons:add redistogo:nano 119 | ``` 120 | 121 | To see if it works 122 | 123 | ```python 124 | import redis 125 | import os 126 | 127 | redis_url = os.getenv('REDISTOGO_URL', 'redis://localhost:6379') 128 | redis_instance = redis.from_url(redis_url) 129 | redis_instance.set('answer', 42) 130 | ``` 131 | 132 | Once again, when running locally, make sure you have Redis server running on your machine. 133 | 134 | 135 | ### Celery 136 | 137 | [Celery](http://celeryproject.org) allows you to run bacground tasks. DHB uses Celery coupled with Redis. 138 | 139 | ### Celerybeat 140 | 141 | Celerybeat allows you to have periodic tasks associated with your app. Tasks configuration is stored in settings/celerybeat.py (cunning, I know). 142 | 143 | ## Running 144 | 145 | * Let's make sure all the code is up to date 146 | 147 | ``` 148 | git commit -a -m "initial commit" 149 | ``` 150 | 151 | * Now you can deploy your app 152 | 153 | ``` 154 | fab deploy 155 | ``` 156 | 157 | Deployment script takes care of several things 158 | 159 | ** Pushing code to Heroku 160 | ** Moving static assets to S3 161 | ** Synchronizing database 162 | 163 | * Make sure both web instance and the celeryd worker are up 164 | 165 | ``` 166 | heroku ps 167 | ``` 168 | You should see both celeryd and web running. If celeryd is not there, run the following 169 | 170 | ``` 171 | heroku ps:scale celeryd=1 172 | ``` 173 | 174 | Same applies to celerybeat (assuming you need it): 175 | 176 | ``` 177 | heroku ps:scale celerybeat=1 178 | ``` 179 | 180 | * Ta da! Your app is up running on Heroku 181 | 182 | ``` 183 | heroku open 184 | ``` 185 | 186 | * To run the local version: 187 | 188 | ``` 189 | fab run 190 | ``` 191 | 192 | ## Example App 193 | 194 | /apps/examples contains a simple email form which you can use to test the setup. Navigate to /examples/email/ to try it. 195 | 196 | ## Roadmap 197 | 198 | * Schema migrations with South 199 | * Tests 200 | * Caching 201 | * File uploads 202 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmephilip/django-heroku-bootstrap/97a03ed4427ff7b853b9fc19611f6aa7995e94e8/__init__.py -------------------------------------------------------------------------------- /apps/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmephilip/django-heroku-bootstrap/97a03ed4427ff7b853b9fc19611f6aa7995e94e8/apps/__init__.py -------------------------------------------------------------------------------- /apps/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmephilip/django-heroku-bootstrap/97a03ed4427ff7b853b9fc19611f6aa7995e94e8/apps/examples/__init__.py -------------------------------------------------------------------------------- /apps/examples/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | class EmailForm(forms.Form): 4 | subject = forms.CharField(max_length=100) 5 | message = forms.CharField(widget=forms.Textarea) 6 | sender = forms.EmailField() 7 | recipient = forms.EmailField() -------------------------------------------------------------------------------- /apps/examples/tasks.py: -------------------------------------------------------------------------------- 1 | from django.core.mail import send_mail 2 | from celery import task 3 | import logging 4 | 5 | @task() 6 | def send_a_letter(sender, recipient, subject, body): 7 | send_mail(subject, body, sender, [recipient], fail_silently=False) 8 | 9 | @task() 10 | def report_errors(): 11 | logging.error("reporting errors") -------------------------------------------------------------------------------- /apps/examples/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | from django.views.generic.simple import redirect_to, direct_to_template 3 | 4 | 5 | urlpatterns = patterns('', 6 | url(r'^email/$', 'apps.examples.views.email', name='email_example'), 7 | ) -------------------------------------------------------------------------------- /apps/examples/views.py: -------------------------------------------------------------------------------- 1 | import os 2 | import redis 3 | from django.http import HttpResponse 4 | from django.shortcuts import render 5 | from tasks import send_a_letter 6 | from forms import EmailForm 7 | 8 | def email(request): 9 | if request.method == "POST": 10 | form = EmailForm(request.POST) 11 | if form.is_valid(): 12 | send_a_letter.delay(form.cleaned_data["sender"],form.cleaned_data["recipient"], 13 | form.cleaned_data["subject"],form.cleaned_data["message"]) 14 | return HttpResponse("all sent. thanks.") 15 | else: 16 | form = EmailForm() 17 | 18 | return render(request, 'examples/email.html', { 19 | 'form' : form 20 | }) -------------------------------------------------------------------------------- /apps/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls.defaults import patterns, include, url 2 | from django.views.generic.simple import redirect_to, direct_to_template 3 | from django.conf import settings 4 | from django.contrib import admin 5 | 6 | 7 | admin.autodiscover() 8 | 9 | urlpatterns = patterns('', 10 | url(r'^$', direct_to_template, { "template" : "welcome.html"}), 11 | url(r'^examples/', include('apps.examples.urls')), 12 | url(r'^admin/', include(admin.site.urls)), 13 | ) 14 | 15 | if getattr(settings,"DEBUG"): 16 | urlpatterns += patterns('django.contrib.staticfiles.views', 17 | url(r'^static/(?P.*)$', 'serve'), 18 | ) 19 | 20 | 21 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | from fabric.api import * 2 | from functools import wraps 3 | import os, sys 4 | 5 | __all__ = ['deploy','run','collectstatic'] 6 | 7 | def patch_python_path(f): 8 | @wraps(f) 9 | def wrap(*args, **kwargs): 10 | ROOT = os.pathsep.join([os.path.abspath(os.path.dirname(__file__))]) 11 | 12 | if not os.environ.has_key("PYTHONPATH"): 13 | os.environ["PYTHONPATH"] = "" 14 | 15 | if not (ROOT in os.environ["PYTHONPATH"].split(":")): 16 | os.environ["PYTHONPATH"] = "%s:%s" % (os.environ["PYTHONPATH"], ROOT) 17 | 18 | if not ROOT in sys.path: 19 | sys.path.append(ROOT) 20 | 21 | return f(*args, **kwargs) 22 | return wrap 23 | 24 | @patch_python_path 25 | def deploy(): 26 | from tools.git import check_git_state, is_git_clean 27 | from tools.database import needsdatabase, local_migrate, remote_migrate, remote_syncdb 28 | from tools.apps import enumerate_apps 29 | 30 | @check_git_state 31 | @needsdatabase 32 | def __deploy(): 33 | print "Deploying your application" 34 | print "----------------------------" 35 | 36 | print "Migrations..." 37 | 38 | for app in enumerate_apps(): 39 | local_migrate(app) 40 | 41 | if is_git_clean(): 42 | print "Pushing code on Heroku" 43 | local("git push heroku master") 44 | else: 45 | print "Committing migrations..." 46 | local("git add .") 47 | local("git commit -a -m '[DHB] data migrations'") 48 | 49 | 50 | print "Sync remote database" 51 | remote_syncdb() 52 | 53 | 54 | for app in ["djcelery"]: 55 | with settings(warn_only=True): 56 | print "Migrating %s ..." % app 57 | local("heroku run python manage.py migrate %s --settings=settings.prod" % (app)) 58 | 59 | for app in enumerate_apps(): 60 | remote_migrate(app) 61 | 62 | print "Transferring static files to S3" 63 | collectstatic() 64 | 65 | __deploy() 66 | 67 | @patch_python_path 68 | def run(): 69 | print sys.path 70 | 71 | from tools.database import needsdatabase, local_migrate 72 | from tools.apps import enumerate_apps 73 | from tools.heroku import start_foreman 74 | 75 | @needsdatabase 76 | def __run(): 77 | for app in ["djcelery"]: 78 | with settings(warn_only=True): 79 | print "Migrating %s ..." % app 80 | local("python manage.py migrate %s --settings=settings.dev" % (app)) 81 | 82 | for app in enumerate_apps(): 83 | with settings(warn_only=True): 84 | local_migrate(app) 85 | 86 | start_foreman() 87 | 88 | __run() 89 | 90 | @patch_python_path 91 | def collectstatic(): 92 | local("python manage.py collectstatic --noinput --settings=settings.static") -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | import imp 4 | try: 5 | imp.find_module('settings') # Assumed to be in the same directory. 6 | except ImportError: 7 | import sys 8 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__) 9 | sys.exit(1) 10 | 11 | import settings 12 | 13 | if __name__ == "__main__": 14 | execute_manager(settings) 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.4 2 | simplejson==2.2.1 3 | lxml==2.3.5 4 | pyyaml==3.10 5 | fabric==1.4.3 6 | unittest2==0.5.1 7 | mock==1.0b1 8 | psycopg2==2.4.5 9 | dj-database-url==0.2.1 10 | gunicorn==0.14.6 11 | # make sure libevent is installed (if not, brew install libevent) 12 | gevent==0.13.7 13 | greenlet==0.4.0 14 | # django-storages + boto to get your static goodness to the Amazon land 15 | boto==2.5.2 16 | django-storages==1.1.5 17 | django-ses==0.4.1 #remove this if you don't use Amazon SES 18 | redis==2.7.1 19 | django-celery==3.0.11 20 | South==0.7.6 -------------------------------------------------------------------------------- /settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmephilip/django-heroku-bootstrap/97a03ed4427ff7b853b9fc19611f6aa7995e94e8/settings/__init__.py -------------------------------------------------------------------------------- /settings/aws.py: -------------------------------------------------------------------------------- 1 | # AWS settings 2 | 3 | #Your Amazon Web Services access key, as a string. 4 | AWS_ACCESS_KEY_ID = "" 5 | 6 | #Your Amazon Web Services secret access key, as a string. 7 | AWS_SECRET_ACCESS_KEY = "" 8 | 9 | #Your Amazon Web Services storage bucket name, as a string. 10 | AWS_STORAGE_BUCKET_NAME = "" 11 | 12 | #Additional headers to pass to S3 13 | AWS_HEADERS = {} 14 | -------------------------------------------------------------------------------- /settings/celerybeat.py: -------------------------------------------------------------------------------- 1 | from datetime import timedelta 2 | 3 | CELERYBEAT_SCHEDULE = { 4 | 'runs-every-minute': { 5 | 'task': 'apps.examples.tasks.report_errors', 6 | 'schedule': timedelta(minutes=15) 7 | }, 8 | } 9 | 10 | CELERY_TIMEZONE = 'UTC' -------------------------------------------------------------------------------- /settings/common.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | ADMINS = ( 4 | # ('Your Name', 'your_email@example.com'), 5 | ) 6 | 7 | MANAGERS = ADMINS 8 | 9 | # Local time zone for this installation. Choices can be found here: 10 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 11 | # although not all choices may be available on all operating systems. 12 | # On Unix systems, a value of None will cause Django to use the same 13 | # timezone as the operating system. 14 | # If running in a Windows environment this must be set to the same as your 15 | # system time zone. 16 | TIME_ZONE = 'America/Chicago' 17 | 18 | # Language code for this installation. All choices can be found here: 19 | # http://www.i18nguy.com/unicode/language-identifiers.html 20 | LANGUAGE_CODE = 'en-us' 21 | 22 | SITE_ID = 1 23 | 24 | # If you set this to False, Django will make some optimizations so as not 25 | # to load the internationalization machinery. 26 | USE_I18N = True 27 | 28 | # If you set this to False, Django will not format dates, numbers and 29 | # calendars according to the current locale 30 | USE_L10N = True 31 | 32 | # Absolute filesystem path to the directory that will hold user-uploaded files. 33 | # Example: "/home/media/media.lawrence.com/media/" 34 | MEDIA_ROOT = '' 35 | 36 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 37 | # trailing slash. 38 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 39 | MEDIA_URL = '' 40 | 41 | STATICFILES_DIRS = ( 42 | os.path.join(os.path.realpath(os.path.dirname(__file__)), "../static"), 43 | ) 44 | 45 | # URL prefix for admin static files -- CSS, JavaScript and images. 46 | # Make sure to use a trailing slash. 47 | # Examples: "http://foo.com/static/admin/", "/static/admin/". 48 | ADMIN_MEDIA_PREFIX = '/static/admin/' 49 | 50 | 51 | # List of finder classes that know how to find static files in 52 | # various locations. 53 | STATICFILES_FINDERS = ( 54 | 'django.contrib.staticfiles.finders.FileSystemFinder', 55 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 56 | ) 57 | 58 | # Make this unique, and don't share it with anybody. 59 | SECRET_KEY = '' 60 | 61 | # List of callables that know how to import templates from various sources. 62 | TEMPLATE_LOADERS = ( 63 | 'django.template.loaders.filesystem.Loader', 64 | 'django.template.loaders.app_directories.Loader', 65 | ) 66 | 67 | MIDDLEWARE_CLASSES = ( 68 | 'django.middleware.common.CommonMiddleware', 69 | 'django.contrib.sessions.middleware.SessionMiddleware', 70 | 'django.middleware.csrf.CsrfViewMiddleware', 71 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 72 | 'django.contrib.messages.middleware.MessageMiddleware', 73 | ) 74 | 75 | ROOT_URLCONF = 'apps.urls' 76 | 77 | TEMPLATE_DIRS = ( 78 | os.path.join(os.path.realpath(os.path.dirname(__file__)), "../templates"), 79 | ) 80 | 81 | TEMPLATE_CONTEXT_PROCESSORS = ( 82 | 'django.contrib.auth.context_processors.auth', 83 | 'django.core.context_processors.debug', 84 | 'django.core.context_processors.i18n', 85 | 'django.core.context_processors.media', 86 | 'django.core.context_processors.static', 87 | 'django.core.context_processors.request', 88 | 'django.contrib.messages.context_processors.messages', 89 | ) 90 | 91 | INSTALLED_APPS = ( 92 | 'django.contrib.auth', 93 | 'django.contrib.contenttypes', 94 | 'django.contrib.sessions', 95 | 'django.contrib.sites', 96 | 'django.contrib.messages', 97 | 'django.contrib.staticfiles', 98 | 'django.contrib.admin', 99 | 'djcelery', 100 | 'south', 101 | 'apps.examples', 102 | ) 103 | 104 | #CELERY SETUP 105 | 106 | BROKER_URL = os.getenv('REDISTOGO_URL','redis://localhost:6379') 107 | 108 | import djcelery 109 | djcelery.setup_loader() 110 | 111 | try: 112 | from celerybeat import * 113 | except: 114 | pass -------------------------------------------------------------------------------- /settings/dev.py: -------------------------------------------------------------------------------- 1 | import os 2 | from common import * 3 | 4 | DEBUG = TEMPLATE_DEBUG = True 5 | INTERNAL_IPS = ["127.0.0.1"] 6 | 7 | STATIC_URL = '/static/' 8 | 9 | DATABASES = {'default' : {'ENGINE' : 'django.db.backends.sqlite3', 'NAME' : os.path.join(os.path.dirname(__file__),'../data.db')}} 10 | 11 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 12 | -------------------------------------------------------------------------------- /settings/prod.py: -------------------------------------------------------------------------------- 1 | import os 2 | from common import * 3 | from aws import * 4 | from tools.heroku import database_config 5 | 6 | INSTALLED_APPS += ( 7 | 'storages', 8 | ) 9 | 10 | DEBUG = TEMPLATE_DEBUG = False 11 | 12 | DATABASES = {'default' : database_config() } 13 | 14 | #Configure static content to be served form S3 15 | DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' 16 | STATICFILES_STORAGE = DEFAULT_FILE_STORAGE 17 | STATIC_URL = '//s3.amazonaws.com/%s/' % AWS_STORAGE_BUCKET_NAME 18 | ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/' 19 | 20 | #Email : using Amazon SES 21 | EMAIL_BACKEND = 'django_ses.SESBackend' -------------------------------------------------------------------------------- /settings/static.py: -------------------------------------------------------------------------------- 1 | from common import * 2 | from aws import * 3 | 4 | INSTALLED_APPS += ( 5 | 'storages', 6 | ) 7 | 8 | #Configure static content to be served form S3 9 | DEFAULT_FILE_STORAGE = 'storages.backends.s3boto.S3BotoStorage' 10 | STATICFILES_STORAGE = DEFAULT_FILE_STORAGE -------------------------------------------------------------------------------- /static/images/welcome/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmephilip/django-heroku-bootstrap/97a03ed4427ff7b853b9fc19611f6aa7995e94e8/static/images/welcome/icon1.png -------------------------------------------------------------------------------- /static/scripts/README: -------------------------------------------------------------------------------- 1 | Your scripts live here 2 | -------------------------------------------------------------------------------- /static/stylesheets/README: -------------------------------------------------------------------------------- 1 | Your stylesheets live here 2 | -------------------------------------------------------------------------------- /templates/examples/email.html: -------------------------------------------------------------------------------- 1 |
2 |

Email someone

3 | {{ form.as_p }} 4 | {% csrf_token %} 5 | 6 |
-------------------------------------------------------------------------------- /templates/welcome.html: -------------------------------------------------------------------------------- 1 | {% load staticfiles %} 2 |

Django on Heroku

3 | 4 | 5 | -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callmephilip/django-heroku-bootstrap/97a03ed4427ff7b853b9fc19611f6aa7995e94e8/tools/__init__.py -------------------------------------------------------------------------------- /tools/apps.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def enumerate_apps(): 4 | return [ name for name in os.listdir('./apps') if os.path.isdir(os.path.join('./apps', name)) ] -------------------------------------------------------------------------------- /tools/database.py: -------------------------------------------------------------------------------- 1 | import os 2 | from functools import wraps 3 | from fabric.api import local, settings 4 | 5 | 6 | def needsdatabase(f): 7 | @wraps(f) 8 | def wrap(*args, **kwargs): 9 | local("python manage.py syncdb --settings=settings.dev") 10 | return f(*args, **kwargs) 11 | return wrap 12 | 13 | def remote_syncdb(): 14 | local("heroku run python manage.py syncdb --settings=settings.prod") 15 | 16 | def what_is_my_database_url(): 17 | local("heroku config | grep POSTGRESQL") 18 | 19 | def remote_migrate(app_name): 20 | if os.path.exists(os.path.join("./apps", app_name, "migrations")): 21 | with settings(warn_only=True): 22 | r = local("heroku run python manage.py migrate apps.%s --settings=settings.prod" % (app_name), capture=True) 23 | if r.find("django.db.utils.DatabaseError") != -1: 24 | print "Normal migration failed. Running a fake migration..." 25 | local("heroku run python manage.py migrate apps.%s --settings=settings.prod --fake" % (app_name)) 26 | 27 | def local_migrate(app_name): 28 | #TODO: figure out if there are actual models within the app 29 | if not os.path.exists(os.path.join("./apps", app_name, "models.py")): 30 | return 31 | 32 | if not os.path.exists(os.path.join("./apps", app_name, "migrations")): 33 | with settings(warn_only=True): 34 | r = local("python manage.py convert_to_south apps.%s --settings=settings.dev" % app_name, capture=True) 35 | if r.return_code != 0: 36 | return 37 | else: 38 | #app has been converted and ready to roll 39 | 40 | with settings(warn_only=True): 41 | r = local("python manage.py schemamigration apps.%s --auto --settings=settings.dev" % app_name) 42 | 43 | if r.return_code != 0: 44 | print "Scema migration return code != 0 -> nothing to migrate" 45 | else: 46 | local("python manage.py migrate apps.%s --settings=settings.dev" % (app_name)) -------------------------------------------------------------------------------- /tools/git.py: -------------------------------------------------------------------------------- 1 | from fabric.api import * 2 | from functools import wraps 3 | 4 | def is_git_clean(): 5 | git_status = local("git status", capture=True).lower() 6 | print "is_git_clean reports: %s" % git_status 7 | 8 | msgs = ["untracked files", "changes to be committed", "changes not staged for commit"] 9 | 10 | for msg in msgs: 11 | if git_status.find(msg) != -1: 12 | return False 13 | 14 | return True 15 | 16 | def check_git_state(f): 17 | @wraps(f) 18 | def wrap(*args, **kwargs): 19 | if not is_git_clean(): 20 | print "Cannot deploy: make sure your git is clean" 21 | else: 22 | return f(*args, **kwargs) 23 | return wrap -------------------------------------------------------------------------------- /tools/heroku.py: -------------------------------------------------------------------------------- 1 | import os, re 2 | import dj_database_url 3 | from fabric.api import local, settings 4 | 5 | def start_foreman(proc_file="Procfile.dev"): 6 | local("foreman start -f %s" % proc_file) 7 | 8 | def database_config(env_varibale_pattern="HEROKU_POSTGRESQL_\S+_URL", default_env_variable="DATABASE_URL"): 9 | 10 | r = re.compile(env_varibale_pattern) 11 | 12 | urls = filter(lambda k : r.match(k) is not None, os.environ.keys()) 13 | 14 | if len(urls) > 1: 15 | if not os.environ.has_key(default_env_variable): 16 | print "Multiple env variables matching %s detected. Using %s" % (env_varibale_pattern, urls[0]) 17 | 18 | 19 | if len(urls) == 0: 20 | if not os.environ.has_key(default_env_variable): 21 | raise Exception("No database detected. Make sure you enable database on your heroku instance (e.g. heroku addons:add heroku-postgresql:dev)") 22 | 23 | return dj_database_url.config(default_env_variable, os.environ[urls[0]] if len(urls) !=0 else None) 24 | --------------------------------------------------------------------------------