cp etc/hyph_en_US.dic .env/lib/python2.7/site-packages/hyphen/
+
## Features
### Pages
- Zips of the haikus, report
- Source on GitHub
+### Models
+
+- User
+- Haiku
+- Like
+- Text
+- Comment
+
+
+## Debugging Notes
+
+When running `ipython` under `virtualenv`, you can activate the environment from within the interpreter:
+
+````python
+execfile('.env/bin/activate_this.py', dict(__file__='.env/bin/activate_this.py'))
+````
+
+Which resolves problems with the env's `site-packages` dir not getting picked up recursively.
+
## Notes
- Did you know that the Financial Crisis Inquiry Report increased the US Gross National Haiku Quotient by 1.8%, the largest single increase every affected by a congressional report?
- Split out Haiku-finder into its own package? That'd be neat.
+
+
request, session, g, redirect, url_for,
abort, render_template, flash, )
from flaskext.sqlalchemy import SQLAlchemy
+from flaskext.bcrypt import Bcrypt
+from flaskext.oauth import OAuth
app = Flask('crisishaiku')
### Config
-# Load from any ALL_CAPS variables in this file
-app.config.from_object('crisishaiku')
# Load from our base config file
-app.config.from_pyfile('etc/flask.cfg')
+app.config.from_pyfile('../etc/flask.cfg')
# Load any overrides from the file specified in the env var FLASK_SETTINGS
app.config.from_envvar('FLASK_SETTINGS', silent=True)
db = SQLAlchemy(app)
+crypt = Bcrypt(app)
+# pw_hash = crypt.generate_password_hash('hunter2')
+# crypt.check_password_hash(pw_hash, 'hunter2') # returns True
+oauth = OAuth()
+twitter = oauth.remote_app('twitter',
+ base_url = 'https://api.twitter.com/1/',
+ request_token_url = 'https://api.twitter.com/oauth/request_token',
+ access_token_url = 'https://api.twitter.com/oauth/access_token',
+ authorize_url = 'https://api.twitter.com/oauth/authenticate', # /autorize if we wanted to read/write
+ consumer_key = app.config['TWITTER_CONSUMER_KEY'],
+ consumer_secret = app.config['TWITTER_CONSUMER_SECRET']
+)
+
+
+# import crisishaiku.models
+# import crisishaiku.routes
+++ /dev/null
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
+++ /dev/null
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-import sys, codecs, locale
-import argparse
-from path import path
-
-
-__all__ = ('FileType', 'PathType', 'DirectoryType', 'PathTypeError',)
-
-
-class PathTypeError(TypeError):
- """ TypeError that provides `path` and `type` attributes tracking expectations. """
-
- def __init__(self, message, filepath, pathtype):
- super(PathTypeError, self).__init__(message, filepath, pathtype)
- self.message = message
- self.path = filepath
- self.type = pathtype
-
-
-
-class FileType(argparse.FileType):
- """Factory for creating file object types
-
- Instances of FileType are typically passed as type= arguments to the
- ArgumentParser add_argument() method.
-
- Keyword Arguments:
- - mode='r' -- A string indicating how the file is to be opened. Accepts the
- same values as the builtin open() function.
- - encoding=None -- The file's encoding. None is treated as per the `codecs`
- module (as bytes).
- - errors='strict' -- Error handling as defined in the `codecs` module:
- 'strict', 'ignore', 'replace', 'xmlcharrefreplace', 'backslashreplace'
- - bufsize=-1 -- The file's desired buffer size. Accepts the same values as
- the builtin open() function.
- """
-
- def __init__(self, mode='r', encoding=None, errors='strict', bufsize=-1):
- self._mode = mode
- self._encoding = encoding
- self._errors = errors
- self._bufsize = bufsize
-
- def __call__(self, f):
- mode = self._mode
- enc = self._encoding
-
- # the special path "-" means sys.std{in,out}
- if f == '-':
- if 'r' in mode:
- f = '/dev/stdin'
- enc = enc or sys.stdin.encoding or locale.getpreferredencoding().lower()
- elif 'w' in mode:
- f = '/dev/stdout'
- enc = enc or sys.stdout.encoding or locale.getpreferredencoding().lower()
- else:
- msg = _('argument "-" with mode %r') % mode
- raise ValueError(msg)
-
- # all other paths are used as ... paths
- try:
- return codecs.open( f, mode=mode, encoding=enc or None,
- errors=self._errors, buffering=self._bufsize )
- except IOError as e:
- message = _("can't open '%s': %s")
- raise ArgumentTypeError(message % (f, e))
-
- def __repr__(self):
- args = self._mode, self._encoding, self._errors, self._bufsize
- args_str = ', '.join(repr(arg) for arg in args if arg != -1)
- return '%s(%s)' % (type(self).__name__, args_str)
-
-
-
-class PathType(object):
- """ Factory for validating a path and wrapping it as a `path`.
-
- Keyword Arguments:
- - base=u'' -- Base path to resolve the passed path from.
- - mustExist=False -- Validate directory exists, raising OSError otherwise.
- - expand=True -- Expand the path.
- - abspath=False -- Resolve the absolute path.
- """
- base = u''
- mustExist = True
- expand = True
- abspath = False
-
-
- def __init__(self, base=u'', mustExist=True, expand=True, abspath=False):
- self.base = path(base)
- self.mustExist = mustExist
- self.expand = expand
- self.abspath = abspath
-
-
- def checkExists(self, p):
- if self.mustExist and not p.exists():
- raise OSError(2, 'No such file or directory', p)
- return p
-
- def __call__(self, p):
- p = self.base/p
- if self.expand:
- p = p.expand()
- if self.abspath():
- p = p.abspath()
- return self.checkExists(p)
-
-
- def __repr__(self):
- return "%s(%s)" % ( type(self).__name__,
- ', '.join( '%s=%r' % (k,v) for k,v in self.__dict__.items() if not k[0] == '_' ) )
-
-
-
-class DirectoryType(PathType):
- """ Factory for validating a directory path and wrapping it as a `path`.
- """
- mkdirs = True
-
-
- def __init__(self, base=u'', mkdirs=True, mustExist=False, expand=True, abspath=False):
- """ Factory for validating a directory path and wrapping it as a `path`. If a given
- path is not a directory, TypeError is raised.
-
- Keyword Arguments:
- - base=u'' -- Base path to resolve the passed path from.
- - mkdirs=True -- If directory does not exist, make it and all intermediary
- directories.
- - mustExist=False -- Validate directory exists, raising OSError otherwise.
- - expand=True -- Expand the path.
- - abspath=False -- Resolve the absolute path.
- """
- super(DirectoryType, self).__init__(base, mustExist, expand, abspath)
- self.mkdirs = mkdirs
-
-
- def checkExists(self, p):
- if self.mkdirs and not p.exists():
- p.makedirs()
- if p.exists() and not p.isdir():
- raise PathTypeError('Path is not a directory', p, self)
- return super(PathType, self).checkExists(p)
-
-
-
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from sqlalchemy import (
+ Table, Column, ForeignKey,
+ Boolean, Enum, Binary, PickleType,
+ Integer, SmallInteger, BigInteger, Float,
+ String, Text, Unicode, UnicodeText,
+ DateTime, Date, Time,
+ func, )
+from sqlalchemy.orm import relationship, backref, aliased
+from sqlalchemy.types import TypeDecorator, VARCHAR
+from sqlalchemy.ext.associationproxy import association_proxy
+import anyjson as json
+
+from crisishaiku import db
+
+
+
+class JSONData(TypeDecorator):
+ "Type representing serialized JSON data."
+ impl = VARCHAR
+
+ def process_bind_param(self, value, dialect):
+ if value is not None:
+ value = json.dumps(value)
+ return value
+
+ def process_result_value(self, value, dialect):
+ if value is not None:
+ value = json.loads(value)
+ return value
+
+class TimestampMixin(object):
+ ctime = Column(DateTime, default=func.now())
+ mtime = Column(DateTime, default=func.now())
+
+
+# join tables
+
+# likes = db.Table('likes',
+# Column('user_id', Integer, ForeignKey('user.id')),
+# Column('target_id', Integer, ForeignKey('target.id')),
+# Column('ctime', DateTime, default=func.now()),
+# )
+
+# tags2haikus = db.Table('tags',
+# Column('tag_id', Integer, ForeignKey('tag.id')),
+# Column('haiku_id', Integer, ForeignKey('haiku.id'))
+# )
+
+
+class Target(db.Model):
+ "A polymorphic pointer to a Comment, Haiku, or Section."
+ id = Column(Integer, primary_key=True)
+
+class Section(db.Model):
+ "A chunk of the Report. May represent a chapter/section node, or a paragraph of text."
+ id = Column(Integer, primary_key=True)
+ title = Column(String(200))
+ text = Column(Text())
+ parent = relationship('Section', backref=backref('children', lazy='dynamic'))
+
+class Haiku(db.Model):
+ id = Column(Integer, primary_key=True)
+ text = Column(Text())
+ context = relationship('Section', backref=backref('haikus' lazy='dynamic'))
+ start = Column(Integer) # character index into the section where this haiku begins
+
+class Like(db.Model):
+ id = Column(Integer, primary_key=True)
+ user = relationship('User', backref=backref('likes', lazy='dynamic'))
+ target = relationship('Target')
+ ctime = Column(DateTime, default=func.now())
+
+class Comment(TimestampMixin, db.Model):
+ id = Column(Integer, primary_key=True)
+ target = relationship('Target')
+ author = relationship('User', backref=backref('comments', lazy='dynamic'))
+ text = Column(Text())
+ approved = Column(Boolean) # spam filter approval
+ deleted = Column(Boolean)
+
+class Tag(db.Model):
+ id = Column(Integer, primary_key=True)
+ name = Column(String(120), unique=True)
+ haikus = relationship('Haiku', secondary=tags2haikus, backref=backref('tags', lazy='dynamic'))
+
+# user_tags = db.Table('user_tags',
+# Column('user_id', Integer, ForeignKey('user.id')),
+# Column('tag_id', Integer, ForeignKey('tag.id')),
+# Column('haiku_id', Integer, ForeignKey('haiku.id')),
+# )
+
+class User(TimestampMixin, db.Model):
+ id = Column(Integer, primary_key=True)
+ username = Column(String(30), unique=True)
+ email = Column(String(120), unique=True)
+ pwhash = Column(String(60))
+
+ last_seen = Column(DateTime, default=func.now())
+ verified = Column(Boolean)
+ banned = Column(Boolean)
+ deleted = Column(Boolean)
+
+ # OAuth & OpenID accounts
+ accounts = Column(JSONData())
+
+ # We can store the verification crap elsewhere
+ # verification_token = Column(String(60))
+
+ # comments
+ # likes
+ # tags = relationship('UserTag')
+
+
+ def __init__(self, username, email):
+ self.username = username
+ self.email = email
+
+ def __repr__(self):
+ return '<User %r, %r>' % (self.username, self.email)
+
--- /dev/null
+from flask import (
+ request, session, url_for, redirect, abort,
+ render_template, g, flash, send_from_directory, )
+from crisishaiku import app, db, crypt, twitter, models
+from path import path
+
+STATIC = path(app.root_path)/'static'
+
+
+@app.route('/favicon.ico')
+def favicon():
+ return send_from_directory(STATIC, 'favicon.ico', mimetype='image/vnd.microsoft.icon')
+
+
+@app.route('/login')
+def login():
+ return twitter.authorize(callback=url_for('oauth_authorized',
+ next=request.args.get('next') or request.referrer or None))
+
+
+@app.route('/oauth-authorized')
+@twitter.authorized_handler
+def oauth_authorized(res):
+ next_url = request.args.get('next') or url_for('index')
+ if res is None:
+ flash(u'You denied the request to sign in.')
+ return redirect(next_url)
+
+ session['twitter_token'] = (
+ res['oauth_token'],
+ res['oauth_token_secret']
+ )
+ session['twitter_user'] = res['screen_name']
+
+ flash('You were signed in as %s' % res['screen_name'])
+ return redirect(next_url)
+
SQLALCHEMY_DATABASE_URI = 'sqlite:///var/haikus.db'
+TWITTER_CONSUMER_KEY = 'tQww8eCQbbtF12hHPTJA',
+TWITTER_CONSUMER_SECRET = 'OE9AIRTkxWTBJU88PJjqGI4soC6k0pUOtrRur0Q8d4w',
install_requires = [
# 'PyHyphen >= 1.0beta1',
+ 'path >= 2.2',
'bunch >= 1.0',
'jsonlib2 >= 1.5.2',
'anyjson >= 0.3.1',
'PyYAML >= 3.10',
+ 'py-bcrypt >= 0.2',
+ 'SQLAlchemy >= 0.7.4',
'Flask >= 0.8',
'Flask-SQLAlchemy >= 0.15',
+ 'Flask-Bcrypt >= 0.5.2',
+ 'Flask-Admin >= 0.3.0',
+ 'Flask-OAuth >= 0.11',
+ 'Flask-Openid >= 1.0.1',
'pyjade >= 0.6',
'pystache >= 0.3.1',