nuke old hotfix

This commit is contained in:
Ricky Elrod 2013-08-12 07:44:12 +00:00
parent 0a7105213a
commit 80a53a17fb
2 changed files with 0 additions and 907 deletions

View file

@ -1,897 +0,0 @@
import random
import transaction
import types
import sqlalchemy as sa
import velruse
import json as _json
import StringIO
import qrcode as qrcode_module
import docutils.examples
from datetime import datetime
from mako.template import Template as t
from pyramid.view import (
view_config,
forbidden_view_config,
)
from pyramid.response import Response
from pyramid.httpexceptions import (
HTTPFound,
HTTPGone,
HTTPNotFound,
HTTPForbidden,
)
from pyramid.security import (
authenticated_userid,
effective_principals,
remember,
forget,
)
from pyramid.settings import asbool
from tahrir_api.dbapi import TahrirDatabase
import tahrir_api.model as m
from tahrir.utils import strip_tags, generate_badge_yaml
import widgets
from moksha.wsgi.widgets.api import get_moksha_socket, LiveWidget
# Optional. Emit messages to the fedmsg bus.
fedmsg = None
try:
import fedmsg
except ImportError:
pass
@view_config(route_name='admin', renderer='admin.mak', permission='admin')
def admin(request):
settings = request.registry.settings
# TODO: Check if I even need this anymore... leaving for now.
request.session['came_from'] = request.route_url('admin')
if authenticated_userid(request):
awarded_assertions = request.db.get_assertions_by_email(
authenticated_userid(request))
else:
awarded_assertions = None
# Handle any admin actions. These are done through POSTS via the
# HTML forms on the admin panel.
if request.POST:
if request.POST.get('add-person'):
# Email is a required field on the HTML form.
# Add a Badge to the DB.
request.db.add_person(request.POST.get('person-email'),
nickname=request.POST.get(
'person-nickname'),
website=request.POST.get(
'person-website'),
bio=request.POST.get(
'person-bio'))
elif request.POST.get('add-badge'):
# Add a Badge to the DB.
request.db.add_badge(request.POST.get('badge-name'),
request.POST.get('badge-image'),
request.POST.get('badge-description'),
request.POST.get('badge-criteria'),
request.POST.get('badge-issuer'),
request.POST.get('badge-tags'))
elif request.POST.get('add-invitation'):
# Add an Invitation to the DB.
try:
created_on = datetime.strptime(
request.POST.get('invitation-created'),
'%Y-%m-%d %H:%M')
except ValueError:
created_on = None # Will default to datetime.now()
try:
expires_on = datetime.strptime(
request.POST.get('invitation-expires'),
'%Y-%m-%d %H:%M')
except ValueError:
expires_on = None # Will default to datettime.now()
request.db.add_invitation(
request.POST.get('invitation-badge-id'),
created_on=created_on,
expires_on=expires_on,
created_by=request.POST.get('invitation-issuer-id'))
elif request.POST.get('add-issuer'):
# Add an Issuer to the DB.
request.db.add_issuer(
request.POST.get('issuer-origin'),
request.POST.get('issuer-name'),
request.POST.get('issuer-org'),
request.POST.get('issuer-contact'))
elif request.POST.get('add-assertion'):
# Add an Assertion to the DB.
try:
issued_on = datetime.strptime(
request.POST.get('assertion-issued-on'),
'%Y-%m-%d %H:%M')
except ValueError:
issued_on = None # Will default to datetime.now()
request.db.add_assertion(
request.POST.get('assertion-badge-id'),
request.POST.get('assertion-person-email'),
issued_on)
if fedmsg and settings.get('tahrir.use_fedmsg', False):
person = request.db.get_person(
person_email=request.POST.get('assertion-person-email'))
badge = request.db.get_badge(
badge_id=request.POST.get('assertion-badge-id'))
fedmsg.publish(
modname="fedbadges", topic="badge.award",
msg=dict(
badge=dict(
name=badge.name,
description=badge.description,
image_url=badge.image,
),
user=dict(
username=person.nickname,
badges_user_id=person.id
),
))
return dict(
auth_principals=effective_principals(request),
awarded_assertions=awarded_assertions,
)
@view_config(route_name='home', renderer='index.mak')
def index(request):
n = 5 # n is the number of items displayed in each column.
if authenticated_userid(request):
awarded_assertions = request.db.get_assertions_by_email(
authenticated_userid(request))
else:
awarded_assertions = None
# set came_from so we can get back home after openid auth.
request.session['came_from'] = request.route_url('home')
persons_assertions = request.db.get_all_assertions().join(
m.Person).filter(
m.Person.opt_out == False)
from collections import defaultdict
top_persons = defaultdict(int) # person: assertion count
for item in persons_assertions:
top_persons[item.person] += 1
top_persons_sorted = sorted(sorted(top_persons,
key=lambda person: person.id),
key=top_persons.get,
reverse=True)
# Limit the sorted top persons to the top 10% and then take
# a random sample of 5 persons from that pool.
num_users_at_top = max(int(len(top_persons_sorted) * 0.1),
min(len(top_persons_sorted), 5))
# This is not actually a sample yet, but it's about to be...
top_persons_sample = top_persons_sorted[:num_users_at_top]
try:
top_persons_sample = random.sample(top_persons_sample, 5)
except ValueError:
# The sample is probably larger than the num of top users,
# so let's just take all the users in the top 10%, in a
# random order.
random.shuffle(top_persons_sample)
# Get latest awards.
latest_awards = persons_assertions.order_by(
sa.desc(m.Assertion.issued_on)).limit(n).all()
# Register our websocket handler callback
if asbool(request.registry.settings['tahrir.use_websockets']):
socket = make_websocket_handler(request.registry.settings)
socket.display()
return dict(
auth_principals=effective_principals(request),
latest_awards=latest_awards,
newest_persons=request.db.get_all_persons().filter(
m.Person.opt_out == False).order_by(
sa.desc(m.Person.created_on)).limit(n).all(),
top_persons=top_persons,
top_persons_sample=top_persons_sample,
awarded_assertions=awarded_assertions,
moksha_socket=get_moksha_socket(request.registry.settings),
)
@view_config(context=types.FunctionType, permission='admin')
def action(context, request):
# Do the action
context()
return HTTPFound(location=request.route_url('home'))
@view_config(context=m.Invitation, name='claim')
def invitation_claim(request):
""" Action that awards a person a badge after scanning a qrcode. """
settings = request.registry.settings
if request.context.expires_on < datetime.now():
return HTTPGone("That invitation is expired.")
if not authenticated_userid(request):
request.session['came_from'] = request.resource_url(
request.context, 'claim')
return HTTPFound(location=request.route_url('login'))
person = request.db.get_person(person_email=authenticated_userid(request))
# Check to see if the user already has the badge.
if request.context.badge in [a.badge for a in person.assertions]:
# TODO: Flash a message explaining that they already have the badge
return HTTPFound(location=request.route_url('home'))
result = request.db.add_assertion(request.context.badge_id,
person.email,
datetime.now())
if fedmsg and settings.get('tahrir.use_fedmsg', False):
badge = request.context.badge
fedmsg.publish(
modname="fedbadges", topic="badge.award",
msg=dict(
badge=dict(
name=badge.name,
description=badge.description,
image_url=badge.image,
),
user=dict(
username=person.nickname,
badges_user_id=person.id
),
))
# TODO -- return them to a page that auto-exports their badges.
# TODO -- flash and tell them they got the badge
return HTTPFound(location=request.route_url('home'))
@view_config(context=m.Invitation, name='qrcode')
def invitation_qrcode(request):
""" Returns a raw dummy qrcode through to the user. """
if request.context.expires_on < datetime.now():
return HTTPGone("That invitation is expired.")
target = request.resource_url(request.context, 'claim')
img = qrcode_module.make(target)
stringstream = StringIO.StringIO()
img.save(stringstream)
return Response(
body=stringstream.getvalue(),
content_type='image/png',
)
@view_config(route_name='leaderboard', renderer='leaderboard.mak')
def leaderboard(request):
""" Render a top users view. """
if authenticated_userid(request):
awarded_assertions = request.db.get_assertions_by_email(
authenticated_userid(request))
else:
awarded_assertions = None
# Get top persons.
persons_assertions = request.db.get_all_assertions().join(m.Person)
from collections import defaultdict
top_persons = defaultdict(int) # person: assertion count
for item in persons_assertions:
top_persons[item.person] += 1
# top_persons and top_persons_sorted contain all persons, ordered
top_persons_sorted = sorted(sorted(top_persons,
key=lambda person: person.id),
key=top_persons.get,
reverse=True)
# Get total user count.
user_count = len(top_persons)
if authenticated_userid(request):
# Get rank.
try:
rank = top_persons_sorted.index(request.db.get_person(
person_email=authenticated_userid(
request))) + 1
except ValueError:
rank = 0
# Get percentile.
try:
percentile = (float(rank) / float(user_count)) * 100
except ZeroDivisionError:
percentile = 0
# Get a list of nearby competetors (5 users above the current
# user and 5 users ranked below).
competitors = top_persons_sorted[max(rank - 3, 0):\
min(rank + 2, len(top_persons_sorted))]
else:
rank = None
percentile = None
competitors = None
return dict(
auth_principals=effective_principals(request),
awarded_assertions=awarded_assertions,
top_persons=top_persons,
top_persons_sorted=top_persons_sorted,
rank=rank,
user_count=user_count,
percentile=percentile,
competitors=competitors,
)
@view_config(route_name='explore', renderer='explore.mak')
def explore(request):
# Check if a search has been done, and if so, show
# search results.
search_results = dict() # name: link
if request.POST:
if request.POST.get('badge-search'):
# badge-query is a required field on the template form.
badge_query = request.POST.get('badge-query')
matching_results = request.db.get_all_badges().filter(
sa.func.lower(m.Badge.name).like('%' + badge_query
+ '%') |
sa.func.lower(m.Badge.description).like('%' +
badge_query +
'%') |
sa.func.lower(m.Badge.tags).like('%' +
badge_query
+ '%')).all()
for r in matching_results:
search_results[r.name] = request.route_url('badge',
id=r.name.lower().replace(' ', '-'))
elif request.POST.get('person-search'):
# person-query is a required field on the template form.
person_query = request.POST.get('person-query')
matching_results = request.db.get_all_persons().filter(
((m.Person.nickname.like('%' + person_query
+ '%')) |
(m.Person.bio.like('%' + person_query
+ '%'))) &
(m.Person.opt_out == False)).all()
for r in matching_results:
search_results[r.nickname] = request.route_url(
'user', id=r.nickname)
elif request.POST.get('tag-search'):
# tag-query is a required field on the template form.
tag_query = request.POST.get('tag-query')
if request.POST.get('tag-match-all'):
return HTTPFound(location=request.route_url(
'tags', tags=tag_query, match='all'))
else:
return HTTPFound(location=request.route_url(
'tags', tags=tag_query, match='any'))
# Get awarded assertions.
if authenticated_userid(request):
awarded_assertions = request.db.get_assertions_by_email(
authenticated_userid(request))
else:
awarded_assertions = None
# Get some random badges (for discovery).
try:
random_badges = random.sample(request.db.get_all_badges().all(), 5)
except ValueError: # the sample is probably larger than the population
random_badges = request.db.get_all_badges().all()
# Get some random persons (for discovery).
try:
random_persons = random.sample(request.db.get_all_persons().filter(
m.Person.opt_out == False).all(), 5)
except ValueError: # the sample is probably larger than the population
random_persons = request.db.get_all_persons().filter(
m.Person.opt_out == False).all()
return dict(
auth_principals=effective_principals(request),
awarded_assertions=awarded_assertions,
random_badges=random_badges,
random_persons=random_persons,
search_results=search_results,
)
@view_config(route_name='badge', renderer='badge.mak')
def badge(request):
"""Render badge info page."""
# Get the badge to render info about.
badge_id = request.matchdict.get('id')
badge = request.db.get_badge(badge_id)
# if the badge isn't found, raise a 404
if not badge:
raise HTTPNotFound("No such badge %r" % badge_id)
# Get awarded assertions.
if authenticated_userid(request):
awarded_assertions = request.db.get_assertions_by_email(
authenticated_userid(request))
else:
awarded_assertions = None
# Get badge statistics.
# TODO: Perhaps abstract these statistics methods away somewhere?
try:
times_awarded = len(request.db.get_assertions_by_badge(badge_id))
last_awarded = request.db.get_all_assertions().filter(
sa.func.lower(m.Assertion.badge_id) == \
sa.func.lower(badge_id)).order_by(
sa.desc(m.Assertion.issued_on)).limit(1).one()
last_awarded_person = request.db.get_person(
id=last_awarded.person_id)
first_awarded = request.db.get_all_assertions().filter(
sa.func.lower(m.Assertion.badge_id) == \
sa.func.lower(badge_id)).order_by(
sa.asc(m.Assertion.issued_on)).limit(1).one()
first_awarded_person = request.db.get_person(
id=first_awarded.person_id)
percent_earned = float(times_awarded) / \
float(len(request.db.get_all_persons().all()))
except sa.orm.exc.NoResultFound: # This badge has never been awarded.
times_awarded = 0
last_awarded = None
last_awarded_person = None
first_awarded = None
first_awarded_person = None
percent_earned = 0
# Percent of people who have earned this badge
# Get badge description HTML from RST.
# Note: this html_body function wraps the output
# in a <divclass="document">.
badge_description_html = docutils.examples.html_body(badge.description)
return dict(
badge=badge,
badge_description_html=badge_description_html,
auth_principals=effective_principals(request),
awarded_assertions=awarded_assertions,
times_awarded=times_awarded,
last_awarded=last_awarded,
last_awarded_person=last_awarded_person,
first_awarded=first_awarded,
first_awarded_person=first_awarded_person,
percent_earned=percent_earned,
)
def _badge_json_generator(request, badge_id, badge):
try:
times_awarded = len(request.db.get_assertions_by_badge(badge_id))
last_awarded = request.db.get_all_assertions().filter(
sa.func.lower(m.Assertion.badge_id) == \
sa.func.lower(badge_id)).order_by(
sa.desc(m.Assertion.issued_on)).limit(1).one()
last_awarded_person = request.db.get_person(
id=last_awarded.person_id)
first_awarded = request.db.get_all_assertions().filter(
sa.func.lower(m.Assertion.badge_id) == \
sa.func.lower(badge_id)).order_by(
sa.asc(m.Assertion.issued_on)).limit(1).one()
first_awarded_person = request.db.get_person(
id=first_awarded.person_id)
percent_earned = float(times_awarded) / \
float(len(request.db.get_all_persons().all()))
except sa.orm.exc.NoResultFound: # This badge has never been awarded.
times_awarded = 0
last_awarded = None
last_awarded_person = None
first_awarded = None
first_awarded_person = None
percent_earned = 0
if last_awarded:
last_awarded = float(last_awarded.issued_on.strftime('%s'))
if last_awarded_person:
last_awarded_person = last_awarded_person.nickname
if first_awarded:
first_awarded = float(first_awarded.issued_on.strftime('%s'))
if first_awarded_person:
first_awarded_person = first_awarded_person.nickname
if percent_earned:
percent_earned *= 100
return {
'id': badge.id,
'name': badge.name,
'description': badge.description,
'times_awarded': times_awarded,
'last_awarded': last_awarded,
'last_awarded_person': last_awarded_person,
'first_awarded': first_awarded,
'first_awarded_person': first_awarded_person,
'percent_earned': percent_earned,
'image': badge.image
}
@view_config(route_name='badge_json', renderer='json')
def badge_json(request):
"""Render badge JSON dump."""
# Get the badge to render info about.
badge_id = request.matchdict.get('id')
badge = request.db.get_badge(badge_id)
# if the badge isn't found, raise a 404
if not badge:
request.response.status = '404 Not Found'
return {"error": "No such badge exists."}
return _badge_json_generator(request, badge_id, badge)
@view_config(route_name='user', renderer='user.mak')
def user(request):
"""Render user info page."""
# Grab a boolean out of the config
settings = request.registry.settings
allow_changenick = asbool(settings.get('tahrir.allow_changenick', True))
# Get awarded assertions.
if authenticated_userid(request):
awarded_assertions = request.db.get_assertions_by_email(
authenticated_userid(request))
else:
awarded_assertions = None
# So, here they can use their 'id' or their 'nickname'.
# We'll try nickname first since we want to encourage that (or whatever)
# and fall back to id if that fails. If both fail, raise a 404.
user_id = request.matchdict.get('id')
user = request.db.get_person(nickname=user_id)
if not user:
try:
# We cast user_id to an integer so that Postgres doesn't
# get upset about comparing what is potentially a string
# to an integer column.
user = request.db.get_person(id=int(user_id))
except TypeError:
raise HTTPNotFound("No such user %r" % user_id)
# If we still haven't found anything, just give up.
if not user:
raise HTTPNotFound("No such user %r" % user_id)
if user.opt_out == True and user.email != authenticated_userid(request):
raise HTTPNotFound("User %r has opted out." % user_id)
if request.POST:
# Authz check
if authenticated_userid(request) != user.email:
raise HTTPForbidden("Unauthorized")
person = request.db.get_all_persons().filter_by(
email=authenticated_userid(request)).one()
if request.POST.get('change-nickname') and allow_changenick:
new_nick = request.POST.get('new-nickname')
person.nickname = new_nick
# The user's nickname has changed, so let's go to the new URL.
return HTTPFound(location=request.route_url('user', id=new_nick))
elif request.POST.get('deactivate-account'):
person.opt_out = True
elif request.POST.get('reactivate-account'):
person.opt_out = False
# Get user badges.
user_badges = [a.badge for a in user.assertions]
# Sort user badges by id.
user_badges = sorted(user_badges, key=lambda badge: badge.id)
# Get total number of unique badges in the system.
count_total_badges = len(request.db.get_all_badges().all())
# Get percentage of badges earned.
try:
percent_earned = (float(len(user_badges)) / \
float(count_total_badges)) * 100
except ZeroDivisionError:
percent_earned = 0
# Get invitations the user has created.
invitations = [i for i in request.db.get_invitations(user.id)\
if i.expires_on > datetime.now()]
return dict(
user=user,
user_badges=user_badges,
invitations=invitations,
percent_earned=percent_earned,
auth_principals=effective_principals(request),
awarded_assertions=awarded_assertions,
allow_changenick=allow_changenick,
)
@view_config(route_name='user_json', renderer='json')
def user_json(request):
"""Render user info JSON dump."""
# So, here they can use their 'id' or their 'nickname'.
# We'll try nickname first since we want to encourage that (or whatever)
# and fall back to id if that fails. If both fail, raise a 404.
user_id = request.matchdict.get('id')
user = request.db.get_person(nickname=user_id)
if not user:
try:
# We cast user_id to an integer so that Postgres doesn't
# get upset about comparing what is potentially a string
# to an integer column.
user = request.db.get_person(id=int(user_id))
except TypeError:
request.response.status = '404 Not Found'
return {"error": "No such user exists."}
# If we still haven't found anything, just give up.
if not user:
request.response.status = '404 Not Found'
return {"error": "No such user exists."}
if user.opt_out == True and user.email != authenticated_userid(request):
request.response.status = '404 Not Found'
return {"error": "User has opted out."}
awarded_assertions = request.db.get_assertions_by_email(user.email)
# Get user badges.
user_badges = [a.badge for a in user.assertions]
# Sort user badges by id.
user_badges = sorted(user_badges, key=lambda badge: badge.id)
# Get total number of unique badges in the system.
count_total_badges = len(request.db.get_all_badges().all())
# Get percentage of badges earned.
try:
percent_earned = (float(len(user_badges)) / \
float(count_total_badges)) * 100
except ZeroDivisionError:
percent_earned = 0
assertions = []
for assertion in awarded_assertions:
assertions.append(
dict(
{'issued': float(assertion.issued_on.strftime('%s'))}.items() + \
_badge_json_generator(request, assertion.badge.id, assertion.badge).items()))
return {
'user': user.nickname,
'avatar': user.avatar_url(int(request.GET.get('size', 100))),
'percent_earned': percent_earned,
'assertions': assertions
}
@view_config(route_name='builder', renderer='builder.mak')
def builder(request):
if authenticated_userid(request):
awarded_assertions = request.db.get_assertions_by_email(
authenticated_userid(request))
else:
awarded_assertions = None
# set came_from so we can get back home after openid auth.
request.session['came_from'] = request.route_url('builder')
# get default creator field
default_creator = None
user = request.db.get_person(person_email=authenticated_userid(request))
if user:
default_creator = user.nickname or user.email
badge_yaml = None
if request.POST:
badge_yaml = generate_badge_yaml(request.POST)
return dict(
auth_principals=effective_principals(request),
awarded_assertions=awarded_assertions,
default_creator=default_creator,
badge_yaml=badge_yaml,
)
@view_config(route_name='tags', renderer='tags.mak')
def tags(request):
"""Render tag page."""
# Get awarded assertions.
if authenticated_userid(request):
awarded_assertions = request.db.get_assertions_by_email(
authenticated_userid(request))
else:
awarded_assertions = None
# Get badges matching tag.
tags = [t.strip() for t in request.matchdict.get('tags').split(',')]
if request.matchdict.get('match') == 'all':
badges = request.db.get_badges_from_tags(tags, match_all=True)
else:
badges = request.db.get_badges_from_tags(tags, match_all=False)
return dict(
tags=tags,
badges=badges,
auth_principals=effective_principals(request),
awarded_assertions=awarded_assertions,
)
@view_config(context=unicode)
def html(context, request):
return Response(context)
@view_config(context=m.Assertion, renderer='json')
def json(context, request):
return context.__json__()
@view_config(context='pyramid.httpexceptions.HTTPNotFound', renderer='404.mak')
def _404(request):
request.response.status = 404
return dict()
@view_config(context='pyramid.httpexceptions.HTTPServerError',
renderer='500.mak')
def _500(request):
request.response.status = 500
return dict()
@view_config(route_name='login', renderer='login.mak')
@forbidden_view_config(renderer='login.mak')
def login(request):
settings = request.registry.settings
ident = "openid_identifier=" + settings.get('tahrir.openid_identifier')
url = velruse.login_url(request, 'openid') + "?" + ident
return HTTPFound(location=url)
@view_config(context='velruse.AuthenticationComplete')
def login_complete_view(request):
context = request.context
settings = request.registry.settings
nickname = context.profile['preferredUsername']
if asbool(settings.get('tahrir.use_openid_email')) \
and context.profile['emails']:
email = context.profile['emails'][0]
else:
ident = settings.get('tahrir.openid_identifier')
domain = '.'.join(ident.split('.')[-2:])
if domain.endswith('/'):
domain = domain[:-1]
email = nickname + "@" + domain
# Keep adding underscores until we get a default nickname
# that isn't already used.
while request.db.get_person(nickname=nickname):
nickname += '_'
if not request.db.get_person(person_email=email):
request.db.add_person(email=email, nickname=nickname)
headers = remember(request, email)
response = HTTPFound(location=request.session.get('came_from', '/'))
response.headerlist.extend(headers)
return response
@view_config(context='velruse.AuthenticationDenied', renderer='json')
def login_denied_view(request):
# HAAACK -- if login fails, just try again.
return HTTPFound(location=request.route_url('login'))
@view_config(route_name='logout')
def logout(request):
headers = forget(request)
return HTTPFound(location=request.resource_url(request.context),
headers=headers)
@view_config(route_name='assertion_widget',
renderer='assertion_widget.mak')
def assertion_widget(request):
person_id = request.matchdict.get('person')
badge_id = request.matchdict.get('badge')
user = request.db.get_person(id=person_id)
if not user:
raise HTTPNotFound("No such person %r" % person_id)
def get_assertion():
for assertion in user.assertions:
if assertion.badge.id == badge_id:
return assertion
raise HTTPNotFound("User does not have that badge")
assertion = get_assertion()
return dict(assertion=assertion)
def make_websocket_handler(settings):
""" Add a js snippet that listens over websockets to fedmsg.
It animates the "latest awards" pane on the frontpage.
"""
class WebsocketHandler(LiveWidget):
topic = settings.get("tahrir.websocket.topic")
onmessage = """
(function(json){
// TODO -- put the DOM manipulation stuff here.
var user = json.msg.user.badges_user_id;
var badge = json.msg.badge.badge_id;
$.ajax({
url: "%s/_w/assertion/" + user + "/" + badge,
dataType: "html",
success: function (html) {
$("#latest-awards").prepend(html);
$("#latest-awards > div:first-child").hide();
$("#latest-awards > div:first-child").slideDown("slow");
$("#latest-awards > div:last-child").slideUp('slow', complete=function() {
$("#latest-awards > div:last-child").remove();
});
}
});
})(json);
""" % settings['tahrir.base_url']
backend = "websocket"
# Don't actually produce anything when you call .display() on this widget.
inline_engine_name = "mako"
template = ""
return WebsocketHandler

View file

@ -43,16 +43,6 @@
notify:
- restart apache
- name: hotfix - fix badges search 500 error with postgres
copy: >
src=tahrir/views.py
dest=/usr/lib/python2.6/site-packages/tahrir/views.py
owner=root group=root mode=0644
tags:
- hotfix
notify:
- restart apache
- name: hotfix - allow velruse to do stateless openid
copy: >
src=openid.py