First commit

master
Nick Sergeant 2011-06-02 00:50:18 -04:00
commit f4d828fa39
26 changed files with 835 additions and 0 deletions

12
.hgignore Normal file
View File

@ -0,0 +1,12 @@
syntax: glob
*.json
*.gunicorn.*
*.pyc
db.db
local_settings.py
logs
media/admin
media/cache
media/css/style.css

1
.venv Normal file
View File

@ -0,0 +1 @@
snipt

0
__init__.py Normal file
View File

28
debug_wsgi.py Executable file
View File

@ -0,0 +1,28 @@
import os
import sys
import site
parent = os.path.dirname
site_dir = parent(os.path.abspath(__file__))
project_dir = parent(parent(os.path.abspath(__file__)))
sys.path.insert(0, project_dir)
sys.path.insert(0, site_dir)
import local_settings
site.addsitedir(local_settings.VIRTUALENV_PATH)
from django.core.management import setup_environ
import settings
setup_environ(settings)
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()
from werkzeug.debug import DebuggedApplication
application = DebuggedApplication(application, evalex=True)
def null_technical_500_response(request, exc_type, exc_value, tb):
raise exc_type, exc_value, tb
from django.views import debug
debug.technical_500_response = null_technical_500_response

215
fabfile.py vendored Normal file
View File

@ -0,0 +1,215 @@
from __future__ import with_statement
import os
from fabric.api import *
from dwfab.misc import growl as _growl
UPLOADS_DIR = 'media/uploads'
APPS_TO_SYNC = [
'auth',
'contenttypes',
'redirects',
'registration',
]
def prod():
'''Run on the production site.'''
env.env_name = 'production'
env.hosts = ['nick@beta.snipt.net:38038']
env.process_name = 'beta-snipt'
env.site_path = '/var/www/beta-snipt'
env.venv_path = '/home/nick/.virtualenvs/beta-snipt'
env.site_url = 'https://beta.snipt.net/'
env.uploads_path = env.site_path + '/' + UPLOADS_DIR
def _python(cmd):
return env.venv_path.rstrip('/') + '/bin/python ' + cmd
def _lpython(cmd):
return os.getenv('VIRTUAL_ENV').rstrip('/') + '/bin/python ' + cmd
def _pip(cmd):
return env.venv_path.rstrip('/') + '/bin/pip ' + cmd
def pull_uploads():
'''Copy the uploads from the site to your local machine.'''
require('uploads_path', provided_by=['prod'])
sudo('chmod -R a+r "%s"' % env.uploads_path)
rsync_command = r"""rsync -av -e 'ssh -p %s' %s@%s:%s %s""" % (
env.port,
env.user, env.host,
env.uploads_path.rstrip('/') + '/',
UPLOADS_DIR.rstrip('/')
)
print local(rsync_command, capture=False)
def pull_data():
'''Copy the data from the site to your local machine.'''
require('site_path', provided_by=['prod'])
require('venv_path', provided_by=['prod'])
for app in APPS_TO_SYNC:
local(_lpython('manage.py reset --noinput %s' % app))
local(_lpython('manage.py migrate --fake %s' % app))
for app in APPS_TO_SYNC:
with cd(env.site_path):
sudo(_python('manage.py dumpdata --format=json --indent=2 --natural ' +
'%s > snipt-fixtures-%s.json' % (app, app)))
get('%s/snipt-fixtures-%s.json' % (env.site_path, app), 'snipt-fixtures-%s.json' % app)
local(_lpython('manage.py loaddata snipt-fixtures-%s.json' % app))
def pull_all():
'''Copy the uploads and data from the site to your local machine.'''
pull_uploads()
pull_data()
_growl('Snipt: Pull Complete', 'The database and uploads have been refreshed.')
def reindex():
require('site_path', provided_by=['prod'])
with cd(env.site_path):
sudo(_python('manage.py rebuild_index --noinput'))
def syncdb():
'''Run syncdb.'''
require('site_path', provided_by=['prod'])
require('venv_path', provided_by=['prod'])
with cd(env.site_path):
sudo(_python('manage.py syncdb'))
def migrate():
'''Run any needed migrations.'''
require('site_path', provided_by=['prod'])
require('venv_path', provided_by=['prod'])
with cd(env.site_path):
sudo(_python('manage.py migrate'))
def requirements():
'''Copy local requirements.txt to the site and install requirements.'''
require('site_path', provided_by=['prod'])
require('venv_path', provided_by=['prod'])
with cd(env.site_path):
put('requirements.txt', 'requirements.txt')
run(_pip('install -r requirements.txt'))
run('hg revert requirements.txt')
def retag():
'''Check which revision the site is at and update the local tag.
Useful if someone else has deployed (which makes your production/staging local
tag incorrect.
'''
require('site_path', provided_by=['prod'])
require('env_name', provided_by=['prod'])
with cd(env.site_path):
current = run('hg id --rev . --quiet').strip(' \n+')
local('hg tag --local --force %s --rev %s' % (env.env_name, current))
def deploy(rev='.'):
'''Deploy your current revision to the site.
You can also specify a different revision to deploy by passing an argument:
fab stag deploy:1a2cc06d
You can use your local revision numbers as arguments -- the full hash will be
looked up and used.
'''
require('site_path', provided_by=['prod'])
rev = local('hg id --rev %s --quiet' % rev).strip(' \n+')
local('hg push --rev %s' % rev)
with cd(env.site_path):
run('hg tag --local --force previous')
run('hg pull --rev %s' % rev)
run('hg update --rev %s' % rev)
retag()
syncdb()
migrate()
restart()
check()
def deploy_template(rev='.'):
'''Deploy your current revision to the site, without restarting the server.
You can also specify a different revision to deploy by passing an argument:
fab stag deploy:1a2cc06d
You can use your local revision numbers as arguments -- the full hash will be
looked up and used.
'''
require('site_path', provided_by=['prod'])
rev = local('hg id --rev %s --quiet' % rev).strip(' \n+')
local('hg push --rev %s' % rev)
with cd(env.site_path):
run('hg tag --local --force previous')
run('hg pull --rev %s' % rev)
run('hg update --rev %s' % rev)
retag()
check()
def rollback():
'''Roll the site back to the version it was at before the last deployment.
Things may break if migrations were made between the versions. TODO: Fix this.
'''
require('site_path', provided_by=['prod'])
with cd(env.site_path):
run('hg update previous')
retag()
restart()
check()
def check():
'''Check that the home page of the site returns an HTTP 200.
If it does, a normal growl message is sent.
If it does not, a warning is issued and a sticky growl message is sent.
'''
require('site_url', provided_by=['prod'])
if not '200 OK' in run('curl --silent -I "%s"' % env.site_url):
warn("Something is wrong (we didn't get a 200 response)!")
_growl('Snipt: DEPLOYMENT ERROR', 'Something went wrong. Please investigate.', sticky=True)
else:
_growl('Snipt: Deployment Complete', 'Deployment finished, site is working.')
def restart():
'''Restart the site's gunicorn server.'''
require('site_path', provided_by=['prod'])
require('process_name', provided_by=['prod'])
sudo('supervisorctl restart %s' % env.process_name)
with cd(env.site_path):
sudo('chmod a+r media/css/*.css')

3
gk Executable file
View File

@ -0,0 +1,3 @@
#/usr/bin/env bash
kill `cat .gunicorn.pid`

3
gs Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
gunicorn -c gunicorn.conf.py debug_wsgi:application

21
gunicorn.conf.py Normal file
View File

@ -0,0 +1,21 @@
bind = "unix:/tmp/gunicorn.snipt.sock"
daemon = True # Whether work in the background
debug = False # Some extra logging
logfile = ".gunicorn.log" # Name of the log file
loglevel = "info" # The level at which to log
pidfile = ".gunicorn.pid" # Path to a PID file
workers = 1 # Number of workers to initialize
umask = 0 # Umask to set when daemonizing
user = None # Change process owner to user
group = None # Change process group to group
proc_name = "gunicorn-snipt" # Change the process name
tmp_upload_dir = None # Set path used to store temporary uploads
def post_fork(server, worker):
server.log.info("Worker spawned (pid: %s)" % worker.pid)
import local_settings, monitor
if local_settings.DEBUG:
server.log.info("Starting change monitor.")
monitor.start(interval=1.0)

21
gunicorn.server.conf.py Normal file
View File

@ -0,0 +1,21 @@
bind = "unix:/tmp/gunicorn.snipt.sock"
daemon = False # Whether work in the background
debug = False # Some extra logging
logfile = ".gunicorn.log" # Name of the log file
loglevel = "info" # The level at which to log
pidfile = ".gunicorn.pid" # Path to a PID file
workers = 9 # Number of workers to initialize
umask = 0 # Umask to set when daemonizing
user = None # Change process owner to user
group = None # Change process group to group
proc_name = "gunicorn-snipt" # Change the process name
tmp_upload_dir = None # Set path used to store temporary uploads
def post_fork(server, worker):
server.log.info("Worker spawned (pid: %s)" % worker.pid)
import local_settings, monitor
if local_settings.DEBUG:
server.log.info("Starting change monitor.")
monitor.start(interval=1.0)

View File

@ -0,0 +1,20 @@
# Copy this file to local_settings.py and change it as needed.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
EMAIL_BACKEND = 'postmark.backends.PostmarkBackend'
POSTMARK_API_KEY = ''
DATABASES = {
'default': {
'ENGINE': 'sqlite3',
'NAME': '/path/to/db/',
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
}
}
VIRTUALENV_PATH = '/path/to/virtualenv/'

11
manage.py Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env python
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
except ImportError:
import sys
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(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":
execute_manager(settings)

48
media/css/reset.css Normal file
View File

@ -0,0 +1,48 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

81
media/css/style.less Normal file
View File

@ -0,0 +1,81 @@
// Fonts
@Helvetica: 'Helvetica Neue', Helvetica, Arial, 'Liberation Sans', FreeSans, sans-serif;
// Focus
*:focus {
.box-shadow(0, 0, 10px, #47B7F2);
}
// Mixins
.border-radius(@radius: 5px) {
-webkit-background-clip: padding-box;
-webkit-border-radius: @radius;
-moz-background-clip: padding-box;
-moz-border-radius: @radius;
border-radius: @radius;
background-clip: padding-box;
}
.box-shadow(@horizontal: 0px, @vertical: 1px, @blur: 2px, @color: #CCC) {
-webkit-box-shadow: @horizontal @vertical @blur @color;
-moz-box-shadow: @horizontal @vertical @blur @color;
box-shadow: @horizontal @vertical @blur @color;
}
.inset-box-shadow(@horizontal: 0px, @vertical: 1px, @blur: 2px, @color: #CCC) {
-webkit-box-shadow: inset @horizontal @vertical @blur @color;
-moz-box-shadow: inset @horizontal @vertical @blur @color;
box-shadow: inset @horizontal @vertical @blur @color;
}
.multi-color-border(@top, @sides, @bottom) {
border-top: 1px solid @top;
border-left: 1px solid @sides;
border-right: 1px solid @sides;
border-bottom: 1px solid @bottom;
}
.multi-border-radius(@topLeft: 5px, @topRight: 5px, @bottomRight: 5px, @bottomLeft: 5px) {
-webkit-border-top-left-radius: @topLeft;
-webkit-border-top-right-radius: @topRight;
-webkit-border-bottom-right-radius: @bottomRight;
-webkit-border-bottom-left-radius: @bottomLeft;
-moz-border-radius-topleft: @topLeft;
-moz-border-radius-topright: @topRight;
-moz-border-radius-bottomright: @bottomRight;
-moz-border-radius-bottomleft: @bottomLeft;
border-top-left-radius: @topLeft;
border-top-right-radius: @topRight;
border-bottom-right-radius: @bottomRight;
border-bottom-left-radius: @bottomLeft;
}
.vertical-gradient(@start: #000, @stop: #FFF) { background: (@start + @stop) / 2;
background: -webkit-gradient(linear, left top, left bottom, from(@start), to(@stop));
background: -moz-linear-gradient(center top, @start 0%, @stop 100%);
background: -moz-gradient(center top, @start 0%, @stop 100%);
}
.vertical-gradient-with-image(@start: #000, @stop: #FFF, @image) {
background: (@start + @stop) / 2 @image;
background: @image, -webkit-gradient(linear, left top, left bottom, from(@start), to(@stop));
background: @image, -moz-linear-gradient(center top, @start 0%, @stop 100%);
background: @image, -moz-gradient(center top, @start 0%, @stop 100%);
}
// Page
html, body {
background-color: #FFF;
}
body {
color: #666;
font: normal 14px @Helvetica;
overflow-y: scroll;
text-rendering: optimizeLegibility;
}
// Utils
.group:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.hidden {
display: none;
}

18
media/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

16
media/js/less.js Normal file

File diff suppressed because one or more lines are too long

1
media/js/script.js Normal file
View File

@ -0,0 +1 @@
$(function() {});

113
monitor.py Normal file
View File

@ -0,0 +1,113 @@
import os
import sys
import time
import signal
import threading
import atexit
import Queue
_interval = 1.0
_times = {}
_files = []
_running = False
_queue = Queue.Queue()
_lock = threading.Lock()
def _restart(path):
_queue.put(True)
prefix = 'monitor (pid=%d):' % os.getpid()
print >> sys.stderr, '%s Change detected to \'%s\'.' % (prefix, path)
print >> sys.stderr, '%s Triggering process restart.' % prefix
os.kill(os.getpid(), signal.SIGINT)
def _modified(path):
try:
# If path doesn't denote a file and were previously
# tracking it, then it has been removed or the file type
# has changed so force a restart. If not previously
# tracking the file then we can ignore it as probably
# pseudo reference such as when file extracted from a
# collection of modules contained in a zip file.
if not os.path.isfile(path):
return path in _times
# Check for when file last modified.
mtime = os.stat(path).st_mtime
if path not in _times:
_times[path] = mtime
# Force restart when modification time has changed, even
# if time now older, as that could indicate older file
# has been restored.
if mtime != _times[path]:
return True
except:
# If any exception occured, likely that file has been
# been removed just before stat(), so force a restart.
return True
return False
def _monitor():
while 1:
# Check modification times on all files in sys.modules.
for module in sys.modules.values():
if not hasattr(module, '__file__'):
continue
path = getattr(module, '__file__')
if not path:
continue
if os.path.splitext(path)[1] in ['.pyc', '.pyo', '.pyd']:
path = path[:-1]
if _modified(path):
return _restart(path)
# Check modification times on files which have
# specifically been registered for monitoring.
for path in _files:
if _modified(path):
return _restart(path)
# Go to sleep for specified interval.
try:
return _queue.get(timeout=_interval)
except:
pass
_thread = threading.Thread(target=_monitor)
_thread.setDaemon(True)
def _exiting():
try:
_queue.put(True)
except:
pass
_thread.join()
atexit.register(_exiting)
def track(path):
if not path in _files:
_files.append(path)
def start(interval=1.0):
global _interval
if interval < _interval:
_interval = interval
global _running
_lock.acquire()
if not _running:
prefix = 'monitor (pid=%d):' % os.getpid()
print >> sys.stderr, '%s Starting change monitor.' % prefix
_running = True
_thread.start()
_lock.release()

24
requirements.txt Normal file
View File

@ -0,0 +1,24 @@
# Django
-e git://github.com/django/django.git@1.3.X#egg=django
-e git://github.com/dziegler/django-css.git#egg=django-css
-e git://github.com/django-extensions/django-extensions.git#egg=django_extensions
-e hg+http://bitbucket.org/offline/django-annoying#egg=django-annoying
-e git://github.com/alex/django-templatetag-sugar.git#egg=templatetag_sugar
-e hg+http://bitbucket.org/dwaiter/django-bcrypt#egg=django-bcrypt
-e hg+ssh://hg@bitbucket.org/nicksergeant/django-registration#egg=django-registration
# Admin
-e svn+http://django-grappelli.googlecode.com/svn/trunk#egg=django-grappelli
-e hg+http://bitbucket.org/fetzig/grappelli-admin-tools#egg=admin_tools
# Deployment
-e git://github.com/bitprophet/fabric.git#egg=fabric
-e git+http://github.com/benoitc/gunicorn.git#egg=gunicorn
-e hg+ssh://hg@bitbucket.org/nicksergeant/dwfab#egg=dwfab
BeautifulSoup
django-postmark
johnny-cache
python-memcached
South
Werkzeug

90
settings.py Normal file
View File

@ -0,0 +1,90 @@
import os.path
BASE_PATH = os.path.dirname(__file__)
ADMINS = (
('Nick Sergeant', 'nick@snipt.net'),
)
MANAGERS = ADMINS
INTERNAL_IPS = ('127.0.0.1',)
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/login/'
LOGOUT_URL = '/logout/'
TIME_ZONE = 'America/New_York'
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
USE_I18N = True
MEDIA_ROOT = os.path.join(BASE_PATH, 'media')
MEDIA_URL = '/media/'
ADMIN_MEDIA_PREFIX = '/media/admin/'
SECRET_KEY = 'm5w4e9^9r69f!6b9qio%)_p%a*1d(waqki+r_g11=qijh=#wuk'
SESSION_COOKIE_AGE = 31556926
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.request',
'grappelli.context_processors.admin_template_path',
)
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfResponseMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.contrib.redirects.middleware.RedirectFallbackMiddleware',
)
ROOT_URLCONF = 'urls'
TEMPLATE_DIRS = os.path.join(BASE_PATH, 'templates')
INSTALLED_APPS = (
'grappelli',
'admin_tools.theming',
'admin_tools.menu',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.humanize',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.redirects',
'compressor',
'django_bcrypt',
'south',
)
# CSS compression
COMPRESS_OUTPUT_DIR = "cache"
COMPILER_FORMATS = {
'.less': {
'binary_path':'lessc',
'arguments': '*.less *.css'
},
}
# Grappelli
GRAPPELLI_ADMIN_TITLE = '<a href="/">Snipt</a>'
# Local settings and debug
from local_settings import *
if DEBUG:
INSTALLED_APPS += ('django_extensions',)

3
symlink-dependencies.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
ln -s "$VIRTUAL_ENV/src/django-grappelli/grappelli/media" "media/admin"

7
templates/404.html Normal file
View File

@ -0,0 +1,7 @@
{% extends "base.html" %}
{% block page-title %}Page Not Found - {{ block.super }}{% endblock %}
{% block content %}
404: Page Not Found
{% endblock %}

8
templates/500.html Normal file
View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block page-title %}Server Error - {{ block.super }}{% endblock %}
{% block content %}
500: Server Error
{% endblock %}

62
templates/base.html Normal file
View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en" class="{% block html-class %}{% endblock %}">
<head>
<title>{% block page-title %}Snipt{% endblock %}</title>
<meta charset="utf-8" />
<meta name="description" content="Long-term memory for coders. Share and store code snippets." />
{% load compress %}
{% compress css %}
<link rel="stylesheet" href="/media/css/reset.css" />
{% if not debug %}
<link rel="stylesheet" href="/media/css/style.less" />
{% endif %}
{% endcompress %}
{% if debug %}
<link rel="stylesheet/less" href="/media/css/style.less" />
{% endif %}
{% compress js %}
<script type="text/javascript" src="/media/js/jquery.js"></script>
<script type="text/javascript" src="/media/js/script.js"></script>
{% if debug %}
<script type="text/javascript" src="/media/js/less.js"></script>
{% endif %}
{% endcompress %}
<!--[if IE]>
<style type="text/css">
.group {
display: block;
zoom: 1;
}
</style>
<![endif]-->
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body class="{% block body-class %}{% endblock %}">
{% block content %}{% endblock %}
{% if not debug %}
<script type="text/javascript">
//<![CDATA[
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-514462-44']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga);
})();
//]]>
</script>
{% endif %}
</body>
</html>

5
templates/home.html Normal file
View File

@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
Home
{% endblock %}

19
urls.py Normal file
View File

@ -0,0 +1,19 @@
from django.views.generic.simple import direct_to_template
from django.conf.urls.defaults import *
from django.contrib import admin
admin.autodiscover()
from views import home
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
url(r'^admin_tools/', include('admin_tools.urls')),
url(r'^grappelli/', include('grappelli.urls')),
url(r'^404/$', direct_to_template, {'template': '404.html'}),
url(r'^500/$', direct_to_template, {'template': '500.html'}),
url(r'^$', home),
)

5
views.py Normal file
View File

@ -0,0 +1,5 @@
from annoying.decorators import render_to
@render_to('home.html')
def home(request):
return {}