diff --git a/.gitignore b/.gitignore index 5538d3f..b92df6c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ -*.egg-info *.pyc *.pyo -.DS_Store -.epio-app -dist -docs/_build -env -production.cfg +*.swp +*.un~ +*.db +static_root/ \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index aadd9e3..0000000 --- a/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (c) 2011, Ori Livneh - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . diff --git a/README.md b/README.md deleted file mode 100644 index 922ffcb..0000000 --- a/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Marcel - -Marcel is a free and simple web application for co-ordinating mutual aid. It is -built on top of the Flask web framework and Redis. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/epio.ini b/epio.ini index 0dc07d3..68c3266 100644 --- a/epio.ini +++ b/epio.ini @@ -1,18 +1,22 @@ # epio.ini for permabank +[django] +append_settings = false +base = . + [wsgi] requirements = requirements.txt -entrypoint = permabank:app - -[static] -/static = permabank/static [services] +postgres = true redis = true [redis] memory = 16 +[static] +/static = static_root +/media = ../data + [env] EPIO = true -PERMABANK_SETTINGS = ../production.cfg diff --git a/epio_commands/__init__.py b/epio_commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/epio_commands/management/__init__.py b/epio_commands/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/epio_commands/management/commands/__init__.py b/epio_commands/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/epio_commands/management/commands/epio_flush_cache.py b/epio_commands/management/commands/epio_flush_cache.py new file mode 100644 index 0000000..0936e5a --- /dev/null +++ b/epio_commands/management/commands/epio_flush_cache.py @@ -0,0 +1,10 @@ +from django.core.cache import cache + +from django.core.management.base import NoArgsCommand + +class Command(NoArgsCommand): + help = 'Flushes the cache.' + + def handle_noargs(self, **options): + cache.clear() + print "Cache flushed." diff --git a/epio_commands/management/commands/epio_flush_redis.py b/epio_commands/management/commands/epio_flush_redis.py new file mode 100644 index 0000000..fc2e789 --- /dev/null +++ b/epio_commands/management/commands/epio_flush_redis.py @@ -0,0 +1,12 @@ +import redis +from bundle_config import config + +from django.core.management.base import NoArgsCommand + +class Command(NoArgsCommand): + help = 'Flushes all keys in redis.' + + def handle_noargs(self, **options): + r = redis.Redis(host=config['redis']['host'], port=int(config['redis']['port']), password=config['redis']['password']) + r.flushall() + print "All redis keys flushed." diff --git a/epio_commands/models.py b/epio_commands/models.py new file mode 100644 index 0000000..e69de29 diff --git a/fabfile.py b/fabfile.py new file mode 100644 index 0000000..e0a3301 --- /dev/null +++ b/fabfile.py @@ -0,0 +1,21 @@ +from fabric.api import local, env + +def production(): + env['epioapp'] = # production epio instance name + +def staging(): + env['epioapp'] = # staging epio instance + +def epio(commandstring): + local("epio {0} -a {1}".format( + commandstring, + env['epioapp'])) + +def deploy(): + """ An example deploy workflow """ + local("./manage.py collectstatic") + epio('upload') + epio('django syncdb') + epio('django migrate') + epio('django epio_flush_cache') + diff --git a/install_deps b/install_deps new file mode 100644 index 0000000..e483da0 --- /dev/null +++ b/install_deps @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +pip install -r requirements.txt diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..3e4eedc --- /dev/null +++ b/manage.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +import imp +try: + imp.find_module('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" % __file__) + sys.exit(1) + +import settings + +if __name__ == "__main__": + execute_manager(settings) diff --git a/permabank.db b/permabank.db new file mode 100644 index 0000000..3c8fed9 Binary files /dev/null and b/permabank.db differ diff --git a/permabank.sh b/permabank.sh index 379cffd..5f29f64 100644 --- a/permabank.sh +++ b/permabank.sh @@ -1,11 +1,6 @@ #!/bin/env bash # functions and aliases -# resets the app -function resetapp () { - python -c "import permabank;print permabank.reset() is None" -} - # starts a redis-cli session on epio-hosted instance function epio-redis () { epio run_command python -a permabank -- -c "\"from bundle_config import config;from subprocess import call;r=config['redis'];call(['redis-cli','-h',r['host'],'-p',r['port'],'-a',r['password']])\"" diff --git a/permabank/__init__.py b/permabank/__init__.py deleted file mode 100644 index 0a549f3..0000000 --- a/permabank/__init__.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- -""" - Permabank - --------- - - :copyright: (c) 2011 By Ori Livneh - :license: GPLv3, see LICENSE for more details. -""" -from flask import Flask -from flaskext.babel import Babel, format_datetime -from flaskext.openid import OpenID -from openidredis import RedisStore -from redis import Redis, RedisError - -from permabank.timesince import timesince - - -# settings -CSRF_ENABLED = True -DEBUG = True -SECRET_KEY = 'development key' -REDIS = { - 'host': 'localhost', - 'port': 6379, - 'password': None, - 'db': 0 -} - -# Initialize app. Defaults are loaded from the namespace of this module. If the -# PERMABANK_SETTINGS environment variable is set, import settings from whatever -# file it points to. -app = Flask(__name__) -app.config.from_object(__name__) -app.config.from_envvar('PERMABANK_SETTINGS', silent=True) - -# Configure a Redis connection instance -redis = Redis(**app.config['REDIS']) -redis.error = RedisError # for convenience's sake; use in try/except - -# Set up flask-openid to use Redis as its datastore -redis_store_factory = lambda: RedisStore(key_prefix='permabank:oid', conn=redis) -oid = OpenID(app, store_factory=redis_store_factory) - -app.config['OPENID_PROVIDERS'] = { - 'google': 'https://www.google.com/accounts/o8/id', - 'yahoo': 'https://yahoo.com/', -} - -# Set up flask-babel and register dependent template filters -babel = Babel(app) -app.jinja_env.filters['format_datetime'] = format_datetime -app.jinja_env.filters['timesince'] = timesince - - -def reset_app(): - """ Reset Permabank by deleting all associated keys """ - keys = redis.keys('permabank:*') - if keys: - redis.delete(*keys) - -# Views must be loaded after the application object is created: -# See http://flask.pocoo.org/docs/patterns/packages/ -import permabank.views diff --git a/permabank/forms.py b/permabank/forms.py deleted file mode 100644 index 2fbbba5..0000000 --- a/permabank/forms.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" - HTML form definitions for Marcel - - :copyright: (c) 2011 By Ori Livneh - :license: GPLv3, see LICENSE for more details. -""" -from flaskext.wtf import ( - Form, - SelectField, - TextAreaField, - TextField, - validators -) - - -class EntryForm(Form): - """ Represents an HTML form for a Marcel entry """ - entry_type = SelectField( - label='Type of Entry', - choices=[('request', 'I have a request'), - ('offer', 'I have something to offer')] - ) - - summary = TextField( - label='Summary', - validators=[validators.Length(max=140), validators.Required()], - description="Headline for your entry, in 140 characters or fewer" - ) - - details = TextAreaField( - label='Details', - validators=[validators.Length(max=1000), validators.Required()], - description="1000 characters max." - ) - - contact_info = TextField( - label='Contact Information', - validators=[validators.Length(max=140), validators.Required()], - description="A phone number, email address, location, etc." - ) diff --git a/permabank/gravatars.py b/permabank/gravatars.py deleted file mode 100644 index f5b6272..0000000 --- a/permabank/gravatars.py +++ /dev/null @@ -1,13 +0,0 @@ -from urllib import urlencode -from hashlib import md5 - -from marcel import app - -def get_gravatar_url(email, default=None, size=40): - """ Generates a gravatar URL for a given email address """ - if default is None: - default = app.config['DEFAULT_GRAVATAR_URL'] - email = email.lower() - digest = md5(email).hexdigest() - q = urlencode({'d': default, 's': size}) - return "http://www.gravatar.com/avatar/%s?%s" % (digest, q) diff --git a/permabank/models.py b/permabank/models.py deleted file mode 100644 index 35bd631..0000000 --- a/permabank/models.py +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 -*- -""" - Mapping of Python objects to Redis datatypes for Permabank - - :copyright: (c) 2011 By Ori Livneh - :license: GPLv3, see LICENSE for more details. -""" -from datetime import datetime -from uuid import uuid5, NAMESPACE_URL - -import dateutil.parser -from flask import session - -from permabank import redis - - -class User(object): - """ Represents a Permabank user / author """ - def __init__(self, uuid=None, openid=None): - """ Creates a User instance; one of `uuid` or `openid` is required """ - if uuid: - self.uuid = uuid - elif openid: - self.uuid = uuid5(NAMESPACE_URL, openid) - else: - raise TypeError("Either a uuid or an openid is required") - self.key = "permabank:user:%s" % self.uuid - - def from_session(self): - """ Gets the current user based on the value of `openid` in the session - context """ - return User(openid=session['openid']) - - def exists(self): - """ Check whether or not User exists in Redis """ - return redis.exists(self.key) - - def get(self): - """ Retrieve a User from Redis """ - return redis.hgetall(self.key) - - def set(self, **kwargs): - """ Store User attributes in Redis """ - redis.hmset(self.key, {key: val for key, val in kwargs.items() if val}) - - -class EntryManager(object): - """ - The EntryManager class manages both requests and offers. - """ - def __init__(self, type): - """ Instantiates a manager for `type` entry type """ - self.type = type - - def get(self, uid): - """ Retrieves record `uid` from Redis """ - item = redis.hgetall("permabank:%s:%s" % (self.type, uid)) - item['type'] = self.type - item['pubdate'] = dateutil.parser.parse(item['pubdate']) - return item - - def all(self): - """ Gets all records of type self.type """ - keys = redis.lrange(name="permabank:%s" % self.type, start=0, end=-1) - items = [self.get(uid) for uid in keys] - return items - - def add(self, user, summary, details, contact_info, pubdate=None): - """ Adds a new entry """ - if pubdate is None: - pubdate = datetime.now().isoformat() - # TODO(Ori): Should we batch these into a single transaction? - uid = redis.incr("permabank:%s:next_uid" % self.type) - redis.rpush("permabank:%s" % self.type, uid) - redis.hmset("permabank:%s:%s" % (self.type, uid), { - 'user': user.uuid, - 'summary': summary, - 'details': details, - 'contact_info': contact_info, - 'pubdate': pubdate - }) - return uid - -# Initialize managers for the two types of entries we have: -requests = EntryManager("request") -offers = EntryManager("offer") diff --git a/permabank/static/openid.png b/permabank/static/openid.png deleted file mode 100644 index b57e66d..0000000 Binary files a/permabank/static/openid.png and /dev/null differ diff --git a/permabank/static/permabank.js b/permabank/static/permabank.js deleted file mode 100644 index 8359ed7..0000000 --- a/permabank/static/permabank.js +++ /dev/null @@ -1,16 +0,0 @@ -// Javascript code for Marcel. Requires jQuery >= 1.4.3 -// -// :copyright: (c) 2011 by Ori Livneh -// :license: GPLv3, see LICENSE for more details. -(function ($) { - $(document).ready(function() { - // Fade out any alerts after a delay - $('.flash-message').delay(1000).fadeOut('slow'); - - // Expand/contract entries by clicking on the headline - $('article header').click(function() { - $('article header').not(this).next().slideUp(); - $(this).next().slideToggle(); - }); - }); -}(jQuery)); diff --git a/permabank/static/style.css b/permabank/static/style.css deleted file mode 100644 index 5a201d8..0000000 --- a/permabank/static/style.css +++ /dev/null @@ -1,116 +0,0 @@ -/* vim: set ts=2 sw=2: */ - -h1 { - font-family: 'Ubuntu', sans-serif; - font-weight: 400; -} - -div#container > header > h1 { - font-size: 72px; - line-height: 52px; - color: #f38630; -} - -body { - font-family: "Lucida Grande", "Lucida Sans Unicode", "Trebuchet MS", Helvetica, Arial, Verdana, sans-serif; - font-size: 13px; - margin: 0; - padding: 0; -} - -div.entry_body { - display: none; -} - -div#container { - margin: 48px auto; - padding: 0; - width: 740px; -} - -section h1 { - font-size: 48px; -} - -article.entry { - padding: 8px; - margin: 16px 0; - font-size: 24px; - font-weight: nro -} - -article header { - cursor: pointer; -} - -article.entry h1 { - margin: 0; - color: #4c4a52; - font-weight: 800; - font-size: 24px; - line-height: 24px; -} - -article.request { - background: #0e2f59; - color: #cec7d1; -} - -article.request h1 { - color: #fff; -} - -article.offer div.entry_body p { - color: #293137; -} - -article.request div.entry_body p { - color: #f2c288; -} - -article.offer { - background: #eaecec; - color: #86a0b7; -} -article header h1 { - font-size: 16px; -} - -p[class^="flash"] { - background: none repeat scroll 0 0 #fffccf; - border: 1px solid #fffccf; - border-radius: 0 0 5px 5px; - color: #000000; - left: 50%; - margin: 0 0 0 -200px; - padding: 5px; - position: fixed; - text-align: center; - top: 0; - width: 400px; - z-index: 1000; - font-size: 17px; -} - -label small { - display: block; -} - -input.openid { - background: url(openid.png) no-repeat 4px center; - padding-left: 26px; -} - -input, textarea, select { - border: 1px solid black; - padding: 2px; - background: white; - font-family: 'Georgia', serif; - font-size: 17px; - color: #004b6b; -} - -textarea { - width: 99%; -} - diff --git a/permabank/stopwords.py b/permabank/stopwords.py deleted file mode 100644 index 425a238..0000000 --- a/permabank/stopwords.py +++ /dev/null @@ -1,15 +0,0 @@ -stopwords = ['a', 'about', 'above', 'after', 'again', 'against', 'all', - 'am', 'an', 'and', 'any', 'are', 'as', 'at', 'be', 'because', 'been', - 'before', 'being', 'below', 'between', 'both', 'but', 'by', 'can', - 'did', 'do', 'does', 'doing', 'don', 'down', 'during', 'each', 'few', - 'for', 'from', 'further', 'had', 'has', 'have', 'having', 'he', 'her', - 'here', 'hers', 'herself', 'him', 'himself', 'his', 'how', 'i', 'if', - 'in', 'into', 'is', 'it', 'its', 'itself', 'just', 'me', 'more', - 'most', 'my', 'myself', 'no', 'nor', 'not', 'now', 'of', 'off', 'on', - 'once', 'only', 'or', 'other', 'our', 'ours', 'ourselves', 'out', - 'over', 'own', 's', 'same', 'she', 'should', 'so', 'some', 'such', - 't', 'than', 'that', 'the', 'their', 'theirs', 'them', 'themselves', - 'then', 'there', 'these', 'they', 'this', 'those', 'through', 'to', - 'too', 'under', 'until', 'up', 'very', 'was', 'we', 'were', 'what', - 'when', 'where', 'which', 'while', 'who', 'whom', 'why', 'will', - 'with', 'you', 'your', 'yours', 'yourself', 'yourselves'] diff --git a/permabank/templates/_formhelpers.html b/permabank/templates/_formhelpers.html deleted file mode 100644 index ef3cddf..0000000 --- a/permabank/templates/_formhelpers.html +++ /dev/null @@ -1,12 +0,0 @@ -{# Via http://flask.pocoo.org/docs/patterns/wtforms/ #} -{# By Armin Ronacher #} -{% macro render_field(field) %} -
{{ field.label }} -
{{ field(**kwargs)|safe }} - {% if field.errors %} - - {% endif %} -
-{% endmacro %} diff --git a/permabank/templates/entry.html b/permabank/templates/entry.html deleted file mode 100644 index c69d2af..0000000 --- a/permabank/templates/entry.html +++ /dev/null @@ -1,13 +0,0 @@ -{# vim: set ts=2 sw=2 filetype=htmljinja: #} -
-
-

{{ entry.summary|lower }}

- -
-
-

{{ entry.details }}

-
-
{{ entry.contact_info }}
-
-
-
diff --git a/permabank/templates/layout.html b/permabank/templates/layout.html deleted file mode 100644 index 2a4f92c..0000000 --- a/permabank/templates/layout.html +++ /dev/null @@ -1,27 +0,0 @@ -{# vim: set ts=2 sw=2 filetype=htmljinja: #} -{# Basic skeleton for all pages on Permabank #} - - - - Permabank - - - - -
-
-

Permabank

-

Permabank is a free and simple web application for co-ordinating mutual aid. Source on GitHub.

-
- {% block body %}{% endblock %} -
- - - - - diff --git a/permabank/templates/login.html b/permabank/templates/login.html deleted file mode 100644 index f120229..0000000 --- a/permabank/templates/login.html +++ /dev/null @@ -1,13 +0,0 @@ -{# vim: set ts=2 sw=2 filetype=htmljinja: #} -{# OpenID login form for embedding in templates #} -
- {% if error %}

Error: {{ error }}

{% endif %} -

To add offers or requests, you need to log in.

-

You may log in using an existing account with Google or - Yahoo!

-

Alternately, provide an OpenID URL in the box below.

- - -

(What is an OpenID? How do I get one?)

- -
diff --git a/permabank/templates/redis_debug.html b/permabank/templates/redis_debug.html deleted file mode 100644 index 9c24d6e..0000000 --- a/permabank/templates/redis_debug.html +++ /dev/null @@ -1,14 +0,0 @@ -{# vim: set ts=2 sw=2 filetype=htmljinja: #} -{% extends "layout.html" %} -{% block body %} - - - -
Datum - Value - {% for key, value in info.items() -%} -
{{ key }} - {{ value }} - {%- endfor %} -
-{% endblock %} diff --git a/permabank/templates/show_entries.html b/permabank/templates/show_entries.html deleted file mode 100644 index c00c9af..0000000 --- a/permabank/templates/show_entries.html +++ /dev/null @@ -1,37 +0,0 @@ -{# vim: set ts=2 sw=2 filetype=htmljinja: #} -{% extends "layout.html" %} -{% block body %} -
-

Requests

- {% for entry in requests -%} - {% include "entry.html" %} - {% else %} -

No requests. Everything is hunky-dory.

- {%- endfor %} -
-
-

Offers

- {% for entry in offers -%} - {% include "entry.html" %} - {% else %} -

No offers. Why not step up?

- {%- endfor %} -
-
- {% if authenticated %} -

Add an entry

- {% from "_formhelpers.html" import render_field %} -
- {{ form.csrf }} - {{ render_field(form.entry_type) }} - {{ render_field(form.summary) }} - {{ render_field(form.details) }} - {{ render_field(form.contact_info) }} - -
- {% else %} -

Sign in

- {% include "login.html" %} - {% endif %} -
-{% endblock %} diff --git a/permabank/timesince.py b/permabank/timesince.py deleted file mode 100644 index 77d93d3..0000000 --- a/permabank/timesince.py +++ /dev/null @@ -1,103 +0,0 @@ -""" - A port of Django's django.utils.timesince to Flask-Babel - Ported by Ori Livneh - - Copyright (c) Django Software Foundation and individual contributors. - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of Django nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" -import datetime - -from flaskext.babel import gettext, ngettext, to_user_timezone - - -def timesince(d, now=None): - """ - Takes two datetime objects and returns the time between d and now - as a nicely formatted string, e.g. "10 minutes". If d occurs after now, - then "0 minutes" is returned. - - Units used are years, months, weeks, days, hours, and minutes. - Seconds and microseconds are ignored. Up to two adjacent units will be - displayed. For example, "2 weeks, 3 days" and "1 year, 3 months" are - possible outputs, but "2 weeks, 3 hours" and "1 year, 5 days" are not. - - Adapted from http://blog.natbat.co.uk/archive/2003/Jun/14/time_since - """ - chunks = ( - (60 * 60 * 24 * 365, lambda n: ngettext('year', 'years', n)), - (60 * 60 * 24 * 30, lambda n: ngettext('month', 'months', n)), - (60 * 60 * 24 * 7, lambda n: ngettext('week', 'weeks', n)), - (60 * 60 * 24, lambda n: ngettext('day', 'days', n)), - (60 * 60, lambda n: ngettext('hour', 'hours', n)), - (60, lambda n: ngettext('minute', 'minutes', n)) - ) - # Convert datetime.date to datetime.datetime for comparison. - if not isinstance(d, datetime.datetime): - d = datetime.datetime(d.year, d.month, d.day) - if now and not isinstance(now, datetime.datetime): - now = datetime.datetime(now.year, now.month, now.day) - - if not now: - if d.tzinfo: - now = to_user_timezone(datetime.datetime.now()) - else: - now = datetime.datetime.now() - - # ignore microsecond part of 'd' since we removed it from 'now' - delta = now - (d - datetime.timedelta(0, 0, d.microsecond)) - since = delta.days * 24 * 60 * 60 + delta.seconds - if since <= 0: - # d is in the future compared to now, stop processing. - return u'0 ' + gettext('minutes') - for i, (seconds, name) in enumerate(chunks): - count = since // seconds - if count != 0: - break - s = gettext('%(number)d %(type)s', number=count, type=name(count)) - if i + 1 < len(chunks): - # Now get the second item - seconds2, name2 = chunks[i + 1] - count2 = (since - (seconds * count)) // seconds2 - if count2 != 0: - s += gettext(', %(number)d %(type)s', number=count2, - type=name2(count2)) - return s - - -def timeuntil(d, now=None): - """ - Like timesince, but returns a string measuring the time until - the given time. - """ - if not now: - if getattr(d, 'tzinfo', None): - now = to_user_timezone(datetime.datetime.now()) - now = datetime.datetime.now(LocalTimezone(d)) - else: - now = datetime.datetime.now() - return timesince(now, d) diff --git a/permabank/views.py b/permabank/views.py deleted file mode 100644 index d5f34cd..0000000 --- a/permabank/views.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -""" - Permabank views - - :copyright: (c) 2011 By Ori Livneh - :license: GPLv3, see LICENSE for more details. -""" - -from flask import ( - abort, - flash, - g, - redirect, - render_template, - request, - session, - url_for -) - -from flask.views import MethodView - -from permabank import app, oid -from permabank.forms import EntryForm -from permabank.models import User, offers, requests, EntryManager - - -# -# Contexts -# - -# Make the authentication status available in template contexts -@app.context_processor -def inject_authentication_status(): - return dict(authenticated=hasattr(g.user, 'uuid')) - -# Embed the current user (if any) in the request context -@app.before_request -def lookup_current_user(): - g.user = None - if 'openid' in session: - g.user = User(openid=session['openid']) - - -# -# Login Views -# - -@app.route('/login', methods=['GET', 'POST']) -@oid.loginhandler -def login(): - if g.user is not None: - return redirect(oid.get_next_url()) - providers = app.config['OPENID_PROVIDERS'] - openid = None - if request.method == 'GET' and 'provider' in request.args: - openid = providers.get(request.args.get('provider')) - elif request.method == 'POST': - openid = request.form.get('openid') - if openid: - return oid.try_login(openid, ask_for=['email']) - return render_template('login.html', - next=oid.get_next_url(), - error=oid.fetch_error()) - - -@app.route('/logout') -def logout(): - session.pop('openid', None) - flash('You were signed out') - return redirect(oid.get_next_url()) - - -@oid.after_login -def after_login(resp): - session['openid'] = resp.identity_url - user = User(openid=resp.identity_url) - if not user.exists(): - user.set(identity_url=resp.identity_url, email=resp.email) - flash('Successfully signed in') - g.user = user - return redirect(oid.get_next_url()) - - -# -# Entry Views -# - -class EntryAPI(MethodView): - def get(self): - form = EntryForm() - return render_template('show_entries.html', - form=form, - offers=offers.all(), - requests=requests.all()) - - def post(self): - form = EntryForm() - if form.validate(): - flash("Success") - entry_manager = EntryManager(form.entry_type.data) - entry_manager.add( - user=g.user, - summary=form.summary.data, - details=form.details.data, - contact_info=form.contact_info.data - ) - else: - flash("Error") - return render_template('show_entries.html', - form=form, - offers=offers.all(), - requests=requests.all()) - - -app.add_url_rule('/', view_func=EntryAPI.as_view('entries')) diff --git a/requirements.txt b/requirements.txt index 2df708e..03e24a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,8 @@ -Flask -Flask-OpenID --e git+https://github.com/bbangert/openid-redis.git#egg=openid-redis -Flask-WTF -Flask-Markdown -Flask-Babel +Django>=1.3,<1.4 +epio +Unipath +django-redis-cache +fabric +django-grappelli +jinja2 +django-profiles diff --git a/runpermabank b/runpermabank deleted file mode 100755 index d038629..0000000 --- a/runpermabank +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -while true; do - python runserver.py -done diff --git a/runserver.py b/runserver.py deleted file mode 100644 index 6b7886b..0000000 --- a/runserver.py +++ /dev/null @@ -1,3 +0,0 @@ -# runs development server -from permabank import app -app.run(debug=True) diff --git a/search.py b/search.py deleted file mode 100644 index c1632cd..0000000 --- a/search.py +++ /dev/null @@ -1,17 +0,0 @@ -import re - -from nltk.corpus import stopwords -from nltk.tokenize import word_tokenize -from nltk.stem import LancasterStemmer - -stemmer = LancasterStemmer() -stopwords = {word for word in stopwords.words('english')} - -def get_stems(query): - """ Tokenize and stem query """ - query = re.sub('[^\w]', ' ', query) - query = word_tokenize(query) - filtered_words = [stemmer.stem(word) for word in query if word not in stopwords] - return filtered_words - -#def search( diff --git a/settings/__init__.py b/settings/__init__.py new file mode 100644 index 0000000..bbe4d07 --- /dev/null +++ b/settings/__init__.py @@ -0,0 +1,10 @@ +from __future__ import absolute_import +import os + +if 'EPIO' in os.environ: + from .epio import * +else: + try: + from .local import * + except ImportError: + from .base import * diff --git a/settings/base.py b/settings/base.py new file mode 100644 index 0000000..46a26b0 --- /dev/null +++ b/settings/base.py @@ -0,0 +1,91 @@ +from unipath import FSPath as Path + +PROJECT_DIR = Path(__file__).absolute().ancestor(2) + +DEBUG = False +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + ('Ori Livneh', 'ori.livneh@gmail.com'), +) + +MANAGERS = ADMINS + +# needed by django-debug-toolbar +INTERNAL_IPS = ('127.0.0.1',) + +# l10n / i18n +TIME_ZONE = 'America/New_York' +LANGUAGE_CODE = 'en-us' +SITE_ID = 1 +USE_I18N = True +USE_L10N = True + +MEDIA_ROOT = PROJECT_DIR.child('media') +MEDIA_URL = '/media/' + +STATIC_ROOT = PROJECT_DIR.child('static_root') +STATIC_URL = '/static/' +STATICFILES_DIRS = ( + str(PROJECT_DIR.child('static')), +) +ADMIN_MEDIA_PREFIX = STATIC_URL + 'grappelli/' +# ADMIN_MEDIA_PREFIX = STATIC_URL + 'admin/' + +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', +) + +SECRET_KEY = '0h&#_1@jg_o@4u6hl6rghwj^$+^qu^ads4@f^&%0%juz1rmh1t' + +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +) + +ROOT_URLCONF = 'urls' + +TEMPLATE_DIRS = ( + PROJECT_DIR.child('templates'), +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'grappelli', + 'profiles', + 'epio_commands', + 'django.contrib.admin', + 'django.contrib.admindocs', +) + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'mail_admins': { + 'level': 'ERROR', + 'class': 'django.utils.log.AdminEmailHandler' + } + }, + 'loggers': { + 'django.request': { + 'handlers': ['mail_admins'], + 'level': 'ERROR', + 'propagate': True, + }, + } +} diff --git a/settings/epio.py b/settings/epio.py new file mode 100644 index 0000000..496652a --- /dev/null +++ b/settings/epio.py @@ -0,0 +1,28 @@ +from __future__ import absolute_import +from .base import * + +from bundle_config import config +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': config['postgres']['database'], + 'USER': config['postgres']['username'], + 'PASSWORD': config['postgres']['password'], + 'HOST': config['postgres']['host'], + } +} + +CACHES = { + 'default': { + 'BACKEND': 'redis_cache.RedisCache', + 'LOCATION': '{host}:{port}'.format( + host=config['redis']['host'], + port=config['redis']['port']), + 'OPTIONS': { + 'PASSWORD': config['redis']['password'], + }, + 'VERSION': config['core']['version'], + }, +} +MEDIA_ROOT = config['core']['data_directory'] + diff --git a/settings/local.py b/settings/local.py new file mode 100644 index 0000000..4d8edc7 --- /dev/null +++ b/settings/local.py @@ -0,0 +1,26 @@ +""" + Permabank settings for local development +""" +from __future__ import absolute_import +from .base import * + + +# turn on debug mode +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +# use sqlite as data store +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': PROJECT_DIR.child('permabank.db'), + } +} + +# use redis as cache backend +CACHES = { + 'default': { + 'BACKEND': 'redis_cache.RedisCache', + 'LOCATION': 'localhost:6379', + }, +} diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..04c467b --- /dev/null +++ b/urls.py @@ -0,0 +1,14 @@ +from django.conf.urls.defaults import patterns, include, url +from django.contrib import admin + + +admin.autodiscover() + +urlpatterns = patterns('', + # Examples: + # url(r'^$', 'epio_skel.views.home', name='home'), + # url(r'^epio_skel/', include('epio_skel.foo.urls')), + + url(r'^admin/doc/', include('django.contrib.admindocs.urls')), + url(r'^admin/', include(admin.site.urls)), +)