Adds server script; fixes some model ... misunderstands.
authordsc <david.schoonover@gmail.com>
Sun, 25 Dec 2011 06:29:14 +0000 (22:29 -0800)
committerdsc <david.schoonover@gmail.com>
Sun, 25 Dec 2011 06:29:14 +0000 (22:29 -0800)
README.md
bin/serve.py [new file with mode: 0755]
crisishaiku/__init__.py
crisishaiku/__main__.py [new file with mode: 0755]
crisishaiku/models.py
crisishaiku/routes.py [deleted file]
crisishaiku/views.py
setup.py

index 0099bb7..ebc7c4b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -15,6 +15,8 @@ Then install the egg:
     pip install -e .
 
 
+## Dev Install
+
 **OPTIONAL**
 
 This isn't needed to hack on the site.
@@ -29,10 +31,31 @@ about the missing dictionaries.
 
     pip install PyHyphen
 
-Finally, let's fix the missing dict (which I dug up from somewhere).
+If you try to use the package, it'll yell at you in an uncouth fashion:
+
+    >>> import hyphen
+    >>> hy = hyphen.Hyphenator('en_US')
+    Traceback (most recent call last):
+        ...
+    IOError: Cannot load hyphen dictionary.
+
+Bitches. Okay, let's fix the missing dict. Since it's not available on
+OpenOffice.org any more, I had to dig up a copy from a random sourceforge
+project -- it's `etc/hyph_en_US.dic`. The package expects it in its source
+root:
 
     cp etc/hyph_en_US.dic .env/lib/python2.7/site-packages/hyphen/
 
+Done! Now everything should work fine.
+
+    >>> import hyphen
+    >>> hy = hyphen.Hyphenator('en_US')
+    >>> hy.syllables(u'autopoietic')
+    [u'au', u'topoi', u'et', u'ic']
+    >>> hy.syllables(u'ion')
+    []
+
+It works! (okok. It's not perfect, but it's good enough.)
 
 
 ## Features
@@ -40,56 +63,63 @@ Finally, let's fix the missing dict (which I dug up from somewhere).
 ### Pages
 
 - **Home**
-    - Best Of / Staff Picks
-    - Popular
-    - Mentioned on Fb/Twitter (etc)
-    - Longest Chains?
-- **Haiku Page**
-    - Unique URL per haiku (plus short URL)
-    - Tags, context, favs, ratings
+    - Best Of / Staff Picks (this is `haikus-fav.txt`)
+    - Popular (calc'd using likes, mentions, views)
+    - Longest Chains
+    - Mentions (on fb/twitter)
+
+- **Haiku Pages**
+    - Haiku text + report context, link to location in report
+    - Unique URL (+short URL)
+    - Like, Tag
     - Comments ("share your story"?)
-    - Sharing (tweet this, share on fb, AddThis)
+    - Share (AddThis, tweet this, share on Fb)
     - Mentions (on twitter/fb)
-- **Report**
-    - Split out by-chapter
-    - Haikus highlighted inline in report, link to Haiku Page
-    - Per-line comments?
+
+- **The Report**
+    - TOC w/ page per chapter/section; can deep link to paragraphs
+    - Haikus highlighted inline (w/ link to Haiku Page)
+    - Comments, Likes per- paragraph/section/chapter
+    - Share (AddThis, tweet this, share on Fb)
+
 - **Search**
-    - Fulltext of haikus
-    - Fulltext of report (by chapter)
-    - By tag
+    - Fulltext of all Haikus
+    - Fulltext of Report (by chapter & paragraph)
+    - Browse by Tag
+
 - **Users**
-    - Favorites, Rate, Comment, Tag
-    - Signup required to mutate (w/ connect via FB, Twitter, GitHub, Google)
+    - OAuth: Fb, Twitter, GitHub
+    - OpenID: Google
+    - Likes, Comments, Tags
+
 - **Download**
     - 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:
+### ipython and virtualenv
+
+`ipython` under `virtualenv` does not seem to recursively add the non-zipped packages
+in `.env/lib/python2.7/site-packages` (whereas the normal python repl does). Thankfully,
+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.
+You can do this automatically with a little extra work on startup.
 
-To do this automatically in ipython, I do a little work on startup. In my `ipython_config.py` file,
-in `c.TerminalIPythonApp.exec_lines` I have (among many other things) an import for
-a module `dsc_ipython` where I keep all my ipython specific code, including:
+I have done this work.
+
+In my `ipython_config.py` file, I have `c.TerminalIPythonApp.exec_lines` import
+a module where I keep all my ipython specific code (`dsc_ipython`), including:
 
 ````python
+### Automatically Find and Activate VirtualEnv (if present)
+
 import os, sys
 from os.path import isfile, join, relpath, abspath
 
@@ -128,15 +158,17 @@ def activate_virtual_env(base=None, dirnames=None, override=False):
 
 # automatically check on startup, using defaults
 activate_virtual_env()
-
 ````
 
+I also register this as the Magic command `%activate_virtual_env` (which took forever
+to figure out how to do in ipython 0.11 because the docs are totally inadequate, even if
+ipython is one of the best things since bees).
 
 
 
 ## 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.
+- Split out Haiku-finder into its own PyPi package? That'd be neat.
 
 
diff --git a/bin/serve.py b/bin/serve.py
new file mode 100755 (executable)
index 0000000..d96a0b8
--- /dev/null
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from crisishaiku import app
+app.run(debug=True)
index 507b9f5..15a2a30 100644 (file)
@@ -2,7 +2,6 @@
 # -*- coding: utf-8 -*-
 """ crisishaiku.com -- a website dedicated to the poetry of financial disaster
 """
-
 __version__ = '0.0.1'
 VERSION = tuple(map(int, __version__.split('.')))
 
@@ -23,23 +22,23 @@ 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)
 
+# render .jade files using pyjade
+app.jinja_env.add_extension('pyjade.ext.jinja.PyJadeExtension')
+
 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
+    authorize_url     = 'https://api.twitter.com/oauth/authenticate', # /authorize if we want read/write to their stream
     consumer_key      = app.config['TWITTER_CONSUMER_KEY'],
-    consumer_secret   = app.config['TWITTER_CONSUMER_SECRET']
+    consumer_secret   = app.config['TWITTER_CONSUMER_SECRET'],
 )
 
 
-# import crisishaiku.models
-# import crisishaiku.routes
+import crisishaiku.models
+import crisishaiku.views
 
diff --git a/crisishaiku/__main__.py b/crisishaiku/__main__.py
new file mode 100755 (executable)
index 0000000..d96a0b8
--- /dev/null
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from crisishaiku import app
+app.run(debug=True)
index 23bce30..f8e0067 100644 (file)
@@ -15,8 +15,12 @@ import anyjson as json
 
 from crisishaiku import db
 
+# pw_hash = crypt.generate_password_hash('hunter2')
+# crypt.check_password_hash(pw_hash, 'hunter2') # returns True
 
 
+### Helpers
+
 class JSONData(TypeDecorator):
     "Type representing serialized JSON data."
     impl = VARCHAR
@@ -36,22 +40,19 @@ class TimestampMixin(object):
     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')),
-# )
-
-# tags2haikus = db.Table('tags',
-#     Column('tag_id',    Integer, ForeignKey('tag.id')),
-#     Column('haiku_id',  Integer, ForeignKey('haiku.id'))
-# )
 
+### Model Classes
 
 class Target(db.Model):
     "A polymorphic pointer to a Comment, Haiku, or Section."
-    id        = Column(Integer, primary_key=True)
+    id = Column(Integer, primary_key=True)
+    section  = relationship('Section')
+    haiku    = relationship('Haiku')
+    comment  = relationship('Comment')
+    
+    @property
+    def value(self):
+        return self.section or self.haiku or self.comment
 
 
 class Section(db.Model):
@@ -59,40 +60,53 @@ class Section(db.Model):
     id        = Column(Integer, primary_key=True)
     title     = Column(String(200))
     text      = Column(Text())
-    parent    = relationship('Section', backref=backref('children', lazy='dynamic'))
+    parent_id = Column(Integer, ForeignKey('section.id'))
+    children  = relationship('Section', lazy='dynamic', backref=backref('parent',  lazy='dynamic'))
+    haikus    = relationship('Haiku',   lazy='dynamic', backref=backref('context', 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
+    id         = Column(Integer, primary_key=True)
+    text       = Column(Text())
+    start      = Column(Integer) # character index into the section where this haiku begins
+    context_id = Column(Integer, ForeignKey('section.id'))
 
 
+# likes = db.Table('likes',
+#     Column('user_id',    Integer, ForeignKey('user.id')),
+#     Column('target_id',  Integer, ForeignKey('target.id')),
+# )
+
 class Like(db.Model):
     id        = Column(Integer, primary_key=True)
-    user      = relationship('User', backref=backref('likes', lazy='dynamic'))
-    target    = relationship('Target')
+    target    = relationship('Target', lazy='dynamic')
+    user_id   = Column(Integer, ForeignKey('user.id'))
     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'))
+    target    = relationship('Target', lazy='dynamic')
+    author_id = Column(Integer, ForeignKey('user.id'))
     text      = Column(Text())
-    approved  = Column(Boolean) # spam filter approval
-    deleted   = Column(Boolean)
+    approved  = Column(Boolean, default=False) # spam filter approval
+    deleted   = Column(Boolean, default=False)
 
 
+haiku_tags = db.Table('haiku_tags',
+    Column('tag_id',    Integer, ForeignKey('tag.id')),
+    Column('haiku_id',  Integer, ForeignKey('haiku.id')),
+)
+
 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'))
+    haikus    = relationship('Haiku', secondary=haiku_tags, lazy='dynamic', backref=backref('tags', lazy='dynamic'))
+
 
 # user_tags = db.Table('user_tags',
+#     Column('tag', String(120), unique=True),
 #     Column('user_id',   Integer, ForeignKey('user.id')),
-#     Column('tag_id',    Integer, ForeignKey('tag.id')),
 #     Column('haiku_id',  Integer, ForeignKey('haiku.id')),
 # )
 
@@ -104,9 +118,9 @@ class User(TimestampMixin, db.Model):
     pwhash    = Column(String(60))
     
     last_seen = Column(DateTime, default=func.now())
-    verified  = Column(Boolean)
-    banned    = Column(Boolean)
-    deleted   = Column(Boolean)
+    verified  = Column(Boolean, default=False)
+    banned    = Column(Boolean, default=False)
+    deleted   = Column(Boolean, default=False)
     
     # OAuth & OpenID accounts
     accounts = Column(JSONData())
@@ -114,10 +128,11 @@ class User(TimestampMixin, db.Model):
     # We can store the verification crap elsewhere
     # verification_token = Column(String(60))
     
-    # comments
-    # likes    = relationship('Target', secondary=likes, backref=backref('tags', lazy='dynamic'))
+    likes    = relationship('Like',    lazy='dynamic', backref=backref('user',   lazy='dynamic'))
+    comments = relationship('Comment', lazy='dynamic', backref=backref('author', lazy='dynamic'))
     
-    # tags = relationship('UserTag')
+    # disable per-user tag tracking
+    ## tags = relationship('UserTag', backref=backref('user', lazy='dynamic'))
     
     
     def __init__(self, username, email):
diff --git a/crisishaiku/routes.py b/crisishaiku/routes.py
deleted file mode 100644 (file)
index 8886645..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-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)
-
index b92c26a..11706a1 100644 (file)
@@ -1,6 +1,47 @@
-from crisishaiku import app
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from flask import (
+    request, session, url_for, redirect, abort, 
+    render_template, g, flash, send_from_directory, )
+from path import path
+
+from crisishaiku import app, db, crypt, twitter, models
+
+STATIC = path(app.root_path)/'static'
+
+
 
 @app.route('/')
 def index():
     return 'Hello World!'
 
+
+@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)
+
index 6be6077..7ee4d6c 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -30,7 +30,7 @@ setup(
     
     install_requires = [
         # 'PyHyphen         >= 1.0beta1',
-        'path             >= 2.2',
+        'path.py          >= 2.2',
         'bunch            >= 1.0',
         'jsonlib2         >= 1.5.2',
         'anyjson          >= 0.3.1',