Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

added email kudos receiver, more design changes and added a network v…

…isualization of kudos
  • Loading branch information...
commit 9176d55e6a72106c7339f6ce1881b19981ac9b5a 1 parent b5a5bbc
@progrium progrium authored
Showing with 156 additions and 67 deletions.
  1. +19 −0 mail.py
  2. +43 −3 main.py
  3. +1 −1  templates/base.html
  4. +93 −63 templates/main.html
View
19 mail.py
@@ -0,0 +1,19 @@
+from google.appengine.api import mail
+from google.appengine.ext import deferred
+
+FROM = 'robot@hackerdojo-kudos.appspotmail.com'
+
+def send_kudos_email(kudos, giver, to):
+ body = ["You have been praised with %s kudos" % kudos.amount]
+ if kudos.reason:
+ body.append(" for:\n\n%s\n\n" % kudos.reason)
+ else:
+ body.append(".\n\n")
+ body.append("The Dojo community is better from your efforts.\n\n")
+ body.append("http://kudos.hackerdojo.com/kudos/%s" % kudos.key().id())
+ mail.send_mail(
+ sender="%s <%s>" % (giver.fullname(), FROM),
+ to="%s <%s>" % (to.fullname(), to.user.email()),
+ reply_to="%s <%s>" % (giver.fullname(), giver.user.email()),
+ subject="[Kudos] Thank you from the community",
+ body=''.join(body))
View
46 main.py
@@ -11,6 +11,7 @@
from google.appengine.api import users
from google.appengine.api.labs import taskqueue
+import mail
from shared.api import domain
MONTHLY_POINTS = 10
@@ -28,8 +29,9 @@ class UserWorker(webapp.RequestHandler):
def post(self):
username = self.request.get('username')
month_ttl = 3600*24*28
- user = domain('/users/%s' % username, month_ttl)
- memcache.set('/users/%s:fullname' % username, "%s %s" % (user['first_name'], user['last_name']), month_ttl)
+ user = domain('/users/%s' % username)
+ if len(user):
+ memcache.set('/users/%s:fullname' % username, "%s %s" % (user['first_name'], user['last_name']), month_ttl)
def username(user):
return user.nickname().split('@')[0] if user else None
@@ -125,6 +127,8 @@ def get(self):
# monthly leader board
receive_leaders = Profile.top_receivers_this_month()
give_leaders = Profile.top_givers_this_month()
+ this_month = datetime.datetime.now().strftime('%B')
+
self.response.out.write(template.render('templates/main.html', locals()))
def post(self):
@@ -139,7 +143,7 @@ def post(self):
if kudos_to_give < 0:
kudos_to_give = 0
# If profile doesn't exist it will be created, no matter if user exists (which is fine)
- to_profile = Profile.get_by_user(users.User(self.request.get('user_to') + '@hackerdojo.com'))
+ to_profile = Profile.get_by_user(users.User(self.request.get('user_to') + '@hackerdojo.com'))
to_profile.received_total += kudos_to_give
to_profile.received_this_month += kudos_to_give
to_profile.put()
@@ -154,6 +158,7 @@ def post(self):
from_profile.gave_this_month += kudos_to_give
from_profile.gave_total += kudos_to_give
from_profile.put()
+ mail.send_kudos_email(kudos, from_profile, to_profile)
self.redirect('/kudos/%s' % kudos.key().id())
class CertificateHandler(webapp.RequestHandler):
@@ -174,10 +179,45 @@ def post(self):
profile.refresh()
self.response.out.write("Finished.")
+class GraphHandler(webapp.RequestHandler):
+ def get(self):
+ graph = {'nodes': [], 'links': []}
+
+ kudos_links = {}
+ nodes = set()
+ for kudos in Kudos.all():
+ source = kudos.user_from.email()
+ target = kudos.user_to.email()
+ nodes.add(source)
+ nodes.add(target)
+ key = '%s-%s' % (source, target)
+ if not key in kudos_links:
+ kudos_links[key] = [source, target, 0]
+ kudos_links[key][2] += kudos.amount
+
+ email_index = {}
+ index = 0
+ for profile in Profile.all():
+ if profile.user.email() in nodes:
+ graph['nodes'].append({'nodeName': profile.fullname(), 'group': 1})
+ email_index[profile.user.email()] = index
+ index += 1
+
+ for link in kudos_links.values():
+ try:
+ graph['links'].append({
+ 'source': email_index[link[0]],
+ 'target': email_index[link[1]],
+ 'value': link[2]})
+ except KeyError:
+ continue
+ self.response.out.write("var kudos = %s;" % simplejson.dumps(graph))
+
def main():
application = webapp.WSGIApplication([
('/', MainHandler),
('/kudos/(\d+)', CertificateHandler),
+ ('/graph.js', GraphHandler),
('/refresh', RefreshHandler),
('/worker/user', UserWorker), ], debug=True)
util.run_wsgi_app(application)
View
2  templates/base.html
@@ -5,7 +5,7 @@
<link href="/static/js/jquery.autocomplete.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="/static/js/jquery.min.js"></script>
<script type="text/javascript" src="/static/js/jquery.autocomplete.pack.js"></script>
- <link href='http://fonts.googleapis.com/css?family=PT+Sans' rel='stylesheet' type='text/css'>
+ {% block head %}{% endblock %}
</head>
<body>
<div id="top">
View
156 templates/main.html
@@ -1,4 +1,9 @@
{% extends 'base.html' %}
+{% block head %}
+<script type="text/javascript" src="http://vis.stanford.edu/protovis/protovis-r3.2.js"></script>
+<script type="text/javascript" src="http://vis.stanford.edu/protovis/ex/miserables.js"></script>
+<script type="text/javascript" src="http://kudos.hackerdojo.com/graph.js"></script>
+{% endblock %}
{% block content %}
<script type="text/javascript">
var names = {{names}};
@@ -17,77 +22,102 @@
<div id="primary" style="padding-top: -10px;">
-{% if user %}
-<div style="width: 42%; float: right; border-left: 2px solid #eee; height: 170px; padding-left: 20px;">
- <h2>Received kudos</h2>
- <table style="margin-top: 5px; margin-left: 20px;">
- <tr><td style="font-size: 38px; font-weight: bold; text-align: right;">{{profile.received_this_month}}</td><td>kudos received this month</td></tr>
- <tr><td style="font-size: 24px; font-weight: bold; text-align: right;">{{profile.received_total}}</td><td>kudos received ever</td></tr>
- </table>
-</div>
+ {% if user %}
+ <div style="width: 42%; float: right; border-left: 2px solid #eee; height: 170px; padding-left: 20px;">
+ <h2>Received kudos</h2>
+ <table style="margin-top: 5px; margin-left: 20px;">
+ <tr><td style="font-size: 38px; font-weight: bold; text-align: right;">{{profile.received_this_month}}</td><td>kudos received this month</td></tr>
+ <tr><td style="font-size: 24px; font-weight: bold; text-align: right;">{{profile.received_total}}</td><td>kudos received ever</td></tr>
+ </table>
+ </div>
+
+
+ <div style="width: 48%;">
+
+ <div style="float: right; font-size: x-large; margin-left: 30px; margin-top: 0px;" title="Kudos to give refreshes every month">
+ <span style="color: #cd355c;">{{points_remaining}}</span>{{points_used}}
+ </div>
+ <h2>Give kudos</h2>
+
+ {% ifequal profile.to_give 0 %}
+ <p style="margin-left: 20px; margin-bottom: 100px;">
+ <strong>You've run out of kudos to give!</strong><br />
+ <span style="font-size: smaller; padding-left: 2px;">I guess you'll have to wait until next month...</span></p>
+ {% else %}
+ <form action="/" method="post" onsubmit="return !$('#submit')[0].disabled">
+ <table style="width: 100%;">
+ <tr>
+ <td width="70%" class="label">Who gets kudos:</td>
+ <td class="label">How much:</td>
+ </tr>
+ <tr>
+ <td>
+ <input type="text" onkeyup="updateUser()" onblur="updateUser()" id="user_name" style="font-size: 14px; width: 100%;"/>
+ <input type="hidden" name="user_to" id="user_to" />
+ </td>
+ <td>
+ <select name="points" style="font-size: 14px;">
+ {% for points in point_options %}<option value="{{points.0}}">{{points.1}}</option>{% endfor %}
+ </select>
+ </td>
+ </tr>
+ <tr><td colspan="2" class="label" style="font-size: ;">What for:</td></tr>
+ <tr><td colspan="2"><input type="text" name="reason" style="width: 80%; font-size: 14px;"/> <input type="submit" disabled="disabled" id="submit" value="Give em" /></td></tr>
+ </table>
+ </form>
+ {% endifequal %}
+ </div>
+
+ {% endif %}
+
+
+ <div style="float: right; border-left: 2px solid #eee; padding-left: 10px;">
+ <h2 style="padding-left: 10px;">Total kudos graph</h2>
+ <script type="text/javascript+protovis">
+ var w = 400,
+ h = 300;
-<div style="width: 48%;">
+ var vis = new pv.Panel()
+ .width(w)
+ .height(h)
+ .fillStyle("white")
+ .event("mousedown", pv.Behavior.pan())
+ .event("mousewheel", pv.Behavior.zoom())
+ .transform(new pv.Transform().scale(1.75).translate(-80, -75));
-<div style="float: right; font-size: x-large; margin-left: 30px; margin-top: 0px;" title="Kudos to give refreshes every month">
- <span style="color: #cd355c;">{{points_remaining}}</span>{{points_used}}
-</div>
-<h2>Give kudos</h2>
+ var force = vis.add(pv.Layout.Force)
+ .nodes(kudos.nodes)
+ .links(kudos.links);
-{% ifequal profile.to_give 0 %}
-<p style="margin-left: 20px; margin-bottom: 100px;">
- <strong>You've run out of kudos to give!</strong><br />
- <span style="font-size: smaller; padding-left: 2px;">I guess you'll have to wait until next month...</span></p>
-{% else %}
-<form action="/" method="post" onsubmit="return !$('#submit')[0].disabled">
- <table style="width: 100%;">
- <tr>
- <td width="70%" class="label">Who gets kudos:</td>
- <td class="label">How much:</td>
- </tr>
- <tr>
- <td>
- <input type="text" onkeyup="updateUser()" onblur="updateUser()" id="user_name" style="font-size: 14px; width: 100%;"/>
- <input type="hidden" name="user_to" id="user_to" />
- </td>
- <td>
- <select name="points" style="font-size: 14px;">
- {% for points in point_options %}<option value="{{points.0}}">{{points.1}}</option>{% endfor %}
- </select>
- </td>
- </tr>
- <tr><td colspan="2" class="label" style="font-size: ;">What for:</td></tr>
- <tr><td colspan="2"><input type="text" name="reason" style="width: 80%; font-size: 14px;"/> <input type="submit" disabled="disabled" id="submit" value="Give em" /></td></tr>
- </table>
-</form>
-{% endifequal %}
-</div>
+ force.springLength = 40;
+ force.springConstant = 1;
+ force.link.add(pv.Line);
-{% endif %}
-
+ force.node.add(pv.Dot)
+ .size(function(d) (d.linkDegree + 4) * Math.pow(this.scale, -1.5))
+ .fillStyle(function(d) "#cd355c")
+ .strokeStyle(function() this.fillStyle().darker())
+ .lineWidth(1)
+ .title(function(d) d.nodeName)
+ .event("mousedown", pv.Behavior.drag())
+ .event("drag", force);
-
-<h2>This month</h2>
-<div style="overflow: auto; width: 100%;">
-<div style="float: left; margin-right: 80px;">
-<h3 style="margin-left: 20px; margin-bottom: 0px;">Most Received</h3>
-<table>
-{% for p in receive_leaders %}
-<tr><td style="font-size: 24px; font-weight: bold; text-align: right;">{{p.received_this_month}}</td><td>{{p.fullname}}</td></tr>
-{% endfor %}
-</table>
-</div>
+ vis.render();
-<div style="float: left;">
-<h3 style="margin-left: 20px; margin-bottom: 0px;">Most Gave</h3>
-<table>
-{% for p in give_leaders %}
-<tr><td style="font-size: 24px; font-weight: bold; text-align: right;">{{p.gave_this_month}}</td><td>{{p.fullname}}</td></tr>
-{% endfor %}
-</table>
-</div>
-</div>
+ </script>
+ </div>
+
+ <h2>{{this_month}} most kudos</h2>
+ <div style="overflow: auto; width: 40%; margin-top: 10px; margin-left: 20px;">
+ <table>
+ {% for p in receive_leaders %}
+ <tr><td style="font-size: 24px; font-weight: bold; text-align: right;">{{p.received_this_month}}</td><td>{{p.fullname}}</td></tr>
+ {% endfor %}
+ </table>
+ </div>
+ <div style="clear: both;"></div>
</div>
<script type="text/javascript">
Please sign in to comment.
Something went wrong with that request. Please try again.