review/web_ui.py @ 69015b8a626f

web: add a site_root option for deployments
author Steve Losh <steve@stevelosh.com>
date Sun, 13 Jun 2010 12:31:04 -0400
parents 1262a21153ac
children b39562553ee2
from __future__ import with_statement

"""The review extension's web UI."""

import sys, os
from hashlib import md5

from mercurial import commands, templatefilters
from mercurial.node import short
from mercurial.util import email

import api

def unbundle():
    package_path = os.path.split(os.path.realpath(__file__))[0]
    template_path = os.path.join(package_path, 'web_templates')
    media_path = os.path.join(package_path, 'web_media')
    top_path = os.path.split(package_path)[0]
    bundled_path = os.path.join(top_path, 'bundled')
    flask_path = os.path.join(bundled_path, 'flask')
    jinja2_path = os.path.join(bundled_path, 'jinja2')
    werkzeug_path = os.path.join(bundled_path, 'werkzeug')
    simplejson_path = os.path.join(bundled_path, 'simplejson')

    sys.path.insert(0, flask_path)
    sys.path.insert(0, werkzeug_path)
    sys.path.insert(0, jinja2_path)
    sys.path.insert(0, simplejson_path)

unbundle()

from flask import Flask
from flask import abort, redirect, render_template, request
app = Flask(__name__)

LOG_PAGE_LEN = 1000000

def _item_gravatar(item):
    return 'http://www.gravatar.com/avatar/%s/' % md5(email(item.author)).hexdigest()

def _line_type(line):
    return 'rem' if line[0] == '-' else 'add' if line[0] == '+' else 'con'

def _categorize_signoffs(signoffs):
    return { 'yes': len(filter(lambda s: s.opinion == 'yes', signoffs)),
             'no': len(filter(lambda s: s.opinion == 'no', signoffs)),
             'neutral': len(filter(lambda s: s.opinion == '', signoffs)),}
utils = {
    'node_short': short,
    'basename': os.path.basename,
    'md5': md5,
    'email': email,
    'templatefilters': templatefilters,
    'len': len,
    'item_gravatar': _item_gravatar,
    'line_type': _line_type,
    'categorize_signoffs': _categorize_signoffs,
    'map': map,
    'str': str,
}

def _render(template, **kwargs):
    return render_template(template, read_only=app.read_only,
        allow_anon=app.allow_anon, utils=utils, datastore=app.datastore, **kwargs)


@app.route('/')
def index():
    rev_max = app.datastore.target['tip'].rev()
    rev_min = rev_max - LOG_PAGE_LEN if rev_max >= LOG_PAGE_LEN else 0
    rcsets = [app.datastore[r] for r in xrange(rev_max, rev_min, -1)]
    return _render('index.html', title='', rcsets=rcsets)


def _handle_signoff(revhash):
    signoff = request.form.get('signoff', None)

    if signoff not in ['yes', 'no', 'neutral']:
        abort(400)

    if signoff == 'neutral':
        signoff = ''

    body = request.form.get('new-signoff-body', '')
    rcset = app.datastore[revhash]
    rcset.add_signoff(body, signoff, force=True)

    return redirect("%s/changeset/%s/" % (app.site_root, revhash))

def _handle_comment(revhash):
    filename = request.form.get('filename', '')
    lines = str(request.form.get('lines', ''))
    if lines:
        lines = filter(None, [l.strip() for l in lines.split(',')])
    body = request.form['new-comment-body']
    
    if body:
        rcset = app.datastore[revhash]
        rcset.add_comment(body, filename, lines)
    
    return redirect("%s/changeset/%s/" % (app.site_root, revhash))

@app.route('/changeset/<revhash>/', methods=['GET', 'POST'])
def changeset(revhash):
    if request.method == 'POST':
        signoff = request.form.get('signoff', None)
        if signoff and not app.read_only:
            return _handle_signoff(revhash)
        elif not app.read_only or app.allow_anon:
            return _handle_comment(revhash)
    
    rcset = app.datastore[revhash]
    rev = rcset.target[revhash]
    
    cu_signoffs = rcset.signoffs_for_current_user()
    cu_signoff = cu_signoffs[0] if cu_signoffs else None
    
    return _render('changeset.html',
        title='%s:%s' % (rev.rev(), short(rev.node())),
        rcset=rcset, rev=rev, cu_signoff=cu_signoff
    )


@app.route('/pull/', methods=['POST'])
def pull():
    if not app.read_only:
        path = request.form['path']
        commands.pull(app.datastore.repo.ui, app.datastore.repo, path, update=True)
    return redirect('%s/' % app.site_root)

@app.route('/push/', methods=['POST'])
def push():
    if not app.read_only:
        path = request.form['path']
        commands.push(app.datastore.repo.ui, app.datastore.repo, path)
    return redirect('%s/' % app.site_root)


def load_interface(ui, repo, read_only=False, allow_anon=False,
        open=False, address='127.0.0.1', port=8080):
    if open:
        import webbrowser
        webbrowser.open('http://localhost:%d/' % port)
        
    app.read_only = read_only
    app.debug = ui.debugflag
    app.allow_anon = allow_anon
    app.site_root = ''

    if app.allow_anon:
        ui.setconfig('ui', 'username', 'Anonymous <anonymous@example.com>')

    app.datastore = api.ReviewDatastore(ui, repo)

    if app.debug:
        from flaskext.lesscss import lesscss
        lesscss(app)
    app.run(host=address, port=port)