-
Notifications
You must be signed in to change notification settings - Fork 309
Eating elephant, first step (events, db schema) #2006
Changes from all commits
f0de1a7
f3c0ecc
fd6357c
c35be7e
3da974b
ba6181a
d36e6e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import heapq | ||
import psycopg2 | ||
from gittip import wireup | ||
|
||
def main(): | ||
db = wireup.db() | ||
with db.get_cursor() as c: | ||
|
||
claimed = c.all(""" | ||
SELECT claimed_time as ts, id, 'claim' as action | ||
FROM participants | ||
WHERE claimed_time IS NOT NULL | ||
ORDER BY claimed_time | ||
""") | ||
usernames = c.all(""" | ||
SELECT claimed_time + interval '0.01 s' as ts, id, 'set' as action, username | ||
FROM participants | ||
WHERE claimed_time IS NOT NULL | ||
ORDER BY claimed_time | ||
""") | ||
api_keys = c.all(""" | ||
SELECT a.mtime as ts, p.id as id, 'set' as action, a.api_key | ||
FROM api_keys a | ||
JOIN participants p | ||
ON a.participant = p.username | ||
ORDER BY ts | ||
""") | ||
goals = c.all(""" | ||
SELECT g.mtime as ts, p.id as id, 'set' as action, g.goal::text | ||
FROM goals g | ||
JOIN participants p | ||
ON g.participant = p.username | ||
ORDER BY ts | ||
""") | ||
|
||
for event in heapq.merge(claimed, usernames, api_keys, goals): | ||
payload = dict(action=event.action, id=event.id) | ||
if event.action == 'set': | ||
payload['values'] = { event._fields[-1]: event[-1] } | ||
c.run(""" | ||
INSERT INTO events (ts, type, payload) | ||
VALUES (%s, %s, %s) | ||
""", (event.ts, 'participant', psycopg2.extras.Json(payload))) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
BEGIN; | ||
DROP TABLE IF EXISTS events; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Really? Why would the events table ever exist already? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, well. Left over from continuous testing. Editing the file and rerunning it with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, that's fine for development (I've been known to do the same). For a PR we should remove this line though, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a work with no real value because the file is going away anyway after the merge. |
||
CREATE TABLE events | ||
( id serial PRIMARY KEY | ||
, ts timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP | ||
, type text NOT NULL | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be better to use |
||
, payload json | ||
); | ||
|
||
CREATE INDEX events_ts ON events(ts ASC); | ||
CREATE INDEX events_type ON events(type); | ||
|
||
/* run branch.py before this | ||
DROP RULE log_api_key_changes ON participants; | ||
DROP RULE log_goal_changes ON participants; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have four rules on the participants table right now:
This PR replaces (2) and (3) with calls to the new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is correct. Now that we are somewhat unclogged I'll look into adding those over the weekend. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool. Be sure to give me bite-sized PRs, eh? :-) |
||
DROP TABLE goals, api_keys; | ||
DROP SEQUENCE api_keys_id_seq, goals_id_seq; | ||
*/ | ||
|
||
END; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,8 @@ | |
NoSelfTipping, | ||
BadAmount, | ||
) | ||
|
||
from gittip.models import add_event | ||
from gittip.models._mixin_team import MixinTeam | ||
from gittip.models.account_elsewhere import AccountElsewhere | ||
from gittip.utils import canonicalize | ||
|
@@ -185,7 +187,9 @@ def update_number(self, number): | |
def recreate_api_key(self): | ||
api_key = str(uuid.uuid4()) | ||
SQL = "UPDATE participants SET api_key=%s WHERE username=%s" | ||
self.db.run(SQL, (api_key, self.username)) | ||
with self.db.get_cursor() as c: | ||
add_event(c, 'participant', dict(action='set', id=self.id, values=dict(api_key=api_key))) | ||
c.run(SQL, (api_key, self.username)) | ||
return api_key | ||
|
||
|
||
|
@@ -207,16 +211,18 @@ def resolve_unclaimed(self): | |
return '/on/%s/%s/' % (rec.platform, rec.user_name) | ||
|
||
def set_as_claimed(self): | ||
claimed_time = self.db.one("""\ | ||
with self.db.get_cursor() as c: | ||
add_event(c, 'participant', dict(id=self.id, action='claim')) | ||
claimed_time = c.one("""\ | ||
|
||
UPDATE participants | ||
SET claimed_time=CURRENT_TIMESTAMP | ||
WHERE username=%s | ||
AND claimed_time IS NULL | ||
RETURNING claimed_time | ||
UPDATE participants | ||
SET claimed_time=CURRENT_TIMESTAMP | ||
WHERE username=%s | ||
AND claimed_time IS NULL | ||
RETURNING claimed_time | ||
|
||
""", (self.username,)) | ||
self.set_attributes(claimed_time=claimed_time) | ||
""", (self.username,)) | ||
self.set_attributes(claimed_time=claimed_time) | ||
|
||
|
||
|
||
|
@@ -292,13 +298,14 @@ def change_username(self, suggested): | |
if suggested != self.username: | ||
try: | ||
# Will raise IntegrityError if the desired username is taken. | ||
actual = self.db.one( "UPDATE participants " | ||
"SET username=%s, username_lower=%s " | ||
"WHERE username=%s " | ||
"RETURNING username, username_lower" | ||
, (suggested, lowercased, self.username) | ||
, back_as=tuple | ||
) | ||
with self.db.get_cursor(back_as=tuple) as c: | ||
add_event(c, 'participant', dict(id=self.id, action='set', values=dict(username=suggested))) | ||
actual = c.one( "UPDATE participants " | ||
"SET username=%s, username_lower=%s " | ||
"WHERE username=%s " | ||
"RETURNING username, username_lower" | ||
, (suggested, lowercased, self.username) | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you intentionally remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not only is not needed, it is not allowed. The way it works is that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, now I see that. Makes sense. |
||
except IntegrityError: | ||
raise UsernameAlreadyTaken(suggested) | ||
|
||
|
@@ -310,9 +317,12 @@ def change_username(self, suggested): | |
|
||
def update_goal(self, goal): | ||
typecheck(goal, (Decimal, None)) | ||
self.db.run( "UPDATE participants SET goal=%s WHERE username=%s" | ||
, (goal, self.username) | ||
) | ||
with self.db.get_cursor() as c: | ||
tmp = goal if goal is None else unicode(goal) | ||
add_event(c, 'participant', dict(id=self.id, action='set', values=dict(goal=tmp))) | ||
c.one( "UPDATE participants SET goal=%s WHERE username=%s RETURNING id" | ||
, (goal, self.username) | ||
) | ||
self.set_attributes(goal=goal) | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,9 @@ <h2>Navigation</h2> | |
{% if participant.show_as_team(user) %} | ||
<a href="/{{ participant.username }}/members/"><button{% if 'members' == current_page %} class="selected"{% endif %}>Members</button></a> | ||
{% endif %} | ||
{% if user.ADMIN %} | ||
<a href="/{{ participant.username }}/events/"><button{% if 'events' == current_page %} class="selected"{% endif %}>Events</button></a> | ||
{% endif %} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the user themselves not be able to see this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to merge it and the events view is not ready yet for regular user consumption. In the future, yes, it is for the user to see. |
||
</div> | ||
{% elif participant.show_as_team(user) %} | ||
<div class="nav level-2"> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
from decimal import Decimal | ||
|
||
from aspen import Response, log | ||
from aspen.utils import to_age | ||
from gittip.utils import get_participant, get_avatar_url | ||
|
||
db = website.db | ||
|
||
[-----------------------------------------------------------------------------] | ||
|
||
participant = get_participant(request, restrict=True) | ||
hero = "Events" | ||
title = "%s - %s" % (participant.username, hero) | ||
locked = False | ||
|
||
SQL = """ | ||
SELECT * FROM events WHERE type = 'participant' AND payload->>'id' = %s ORDER BY ts ASC | ||
""" | ||
|
||
events = db.all(SQL, (unicode(participant.id),)) | ||
|
||
[-----------------------------------------------------------------------------] | ||
{% extends "templates/profile.html" %} | ||
{% block page %} | ||
|
||
<style> | ||
td { padding: 4px; } | ||
</style> | ||
<table id="events" class="centered"> | ||
|
||
{% for e in events %} | ||
<tr> | ||
<td>{{ e.ts }}</td> | ||
<td>{{ e.type }}</td> | ||
<td>{{ e.payload }}</td> | ||
|
||
</tr> | ||
{% endfor %} | ||
|
||
</table> | ||
|
||
{% endblock %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. I've never used that before. :-)