Skip to content

Commit 9408d5c

Browse files
committed
Flesh out Subscription model methods
Added `select`, `update`, and `delete` methods to `Subscription` to handle all CRUD operations. Changed the signatures on `from_attachment`, `from_changeset`, and `from_comment` to let the `user`, `role`, and `notify` values be overridden. Added `for_attachment` and `for_changeset` methods to shortcut queries for those resource types. Added `attachment_deleted`, `attachment_reparented`, and `changeset_modified` event handlers.
1 parent d11d5ef commit 9408d5c

2 files changed

Lines changed: 154 additions & 30 deletions

File tree

code_comments/subscription.py

Lines changed: 148 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,77 @@ def __init__(self, env, data=None):
2626
self.__dict__ = data
2727
self.env = env
2828

29+
@classmethod
30+
def select(cls, env, args={}):
31+
select = 'SELECT * FROM code_comments_subscriptions'
32+
if len(args) > 0:
33+
select += ' WHERE '
34+
criteria = []
35+
for key, value in args.iteritems():
36+
template = '{0}={1}'
37+
if isinstance(value, str):
38+
template = '{0}=\'{1}\''
39+
criteria.append(template.format(key, value))
40+
select += ' AND '.join(criteria)
41+
cursor = env.get_read_db().cursor()
42+
cursor.execute(select)
43+
for row in cursor:
44+
yield cls._from_row(env, row)
45+
2946
def insert(self, db=None):
3047
"""
31-
Insert a new subscription.
48+
Insert a new subscription. Returns bool to indicate success.
49+
"""
50+
if self.id > 0:
51+
# Already has an id, don't insert
52+
return False
53+
else:
54+
@self.env.with_transaction()
55+
def do_insert(db):
56+
cursor = db.cursor()
57+
insert = ("INSERT INTO code_comments_subscriptions "
58+
"(user, role, type, path, repos, rev, notify) "
59+
"VALUES (%s, %s, %s, %s, %s, %s, %s)")
60+
values = (self.user, self.role, self.type, self.path,
61+
self.repos, self.rev, self.notify)
62+
cursor.execute(insert, values)
63+
self.id = db.get_last_id(cursor, 'code_comments_subscriptions')
64+
return True
65+
66+
def update(self, db=None):
67+
"""
68+
Update an existing subscription. Returns bool to indicate success.
69+
"""
70+
if self.id == 0:
71+
# Doesn't have a valid id, don't update
72+
return False
73+
else:
74+
@self.env.with_transaction()
75+
def do_update(db):
76+
cursor = db.cursor()
77+
update = ("UPDATE code_comments_subscriptions SET "
78+
"user=%s, role=%s, type=%s, path=%s, repos=%s, "
79+
"rev=%s, notify=%s WHERE id=%s")
80+
values = (self.user, self.role, self.type, self.path,
81+
self.repos, self.rev, self.notify, self.id)
82+
try:
83+
cursor.execute(update, values)
84+
except db.IntegrityError:
85+
self.env.log.warning("Subscription update failed.")
86+
return False
87+
return True
88+
89+
def delete(self, db=None):
90+
"""
91+
Delete an existing subscription.
3292
"""
33-
@self.env.with_transaction(db)
34-
def do_insert(db):
35-
cursor = db.cursor()
36-
insert = ("INSERT INTO code_comments_subscriptions "
37-
"(user, role, type, path, repos, rev, notify) "
38-
"VALUES (%s, %s, %s, %s, %s, %s, %s)")
39-
values = (self.user, self.role, self.type, self.path, self.repos,
40-
self.rev, self.notify)
41-
cursor.execute(insert, values)
42-
self.id = db.get_last_id(cursor, 'code_comments_subscriptions')
93+
if self.id > 0:
94+
@self.env.with_transaction()
95+
def do_delete(db):
96+
cursor = db.cursor()
97+
delete = ("DELETE FROM code_comments_subscriptions WHERE "
98+
"id=%s")
99+
cursor.execute(delete, (self.id,))
43100

44101
@classmethod
45102
def _from_row(cls, env, row):
@@ -48,7 +105,7 @@ def _from_row(cls, env, row):
48105
"""
49106
try:
50107
subscription = cls(env)
51-
subscription.id = row[0]
108+
subscription.id = int(row[0])
52109
subscription.user = row[1]
53110
subscription.role = row[2]
54111
subscription.type = row[3]
@@ -87,7 +144,8 @@ def _from_dict(cls, env, dict_):
87144
return subscription
88145

89146
@classmethod
90-
def from_attachment(cls, env, attachment):
147+
def from_attachment(cls, env, attachment, user=None, role='author',
148+
notify='always'):
91149
"""
92150
Creates a subscription from an Attachment object.
93151
"""
@@ -96,42 +154,44 @@ def from_attachment(cls, env, attachment):
96154
attachment.filename)
97155

98156
sub = {
99-
'user': attachment.author,
100-
'role': 'author',
157+
'user': user or attachment.author,
158+
'role': role,
101159
'type': 'attachment',
102160
'path': _path,
103161
'repos': '',
104162
'rev': '',
105-
'notify': 'always',
163+
'notify': notify,
106164
}
107165
return cls._from_dict(env, sub)
108166

109167
@classmethod
110-
def from_changeset(cls, env, changeset):
168+
def from_changeset(cls, env, changeset, user=None, role='author',
169+
notify='always'):
111170
"""
112171
Creates a subscription from a Changeset object.
113172
"""
114173
sub = {
115-
'user': changeset.author,
116-
'role': 'author',
174+
'user': user or changeset.author,
175+
'role': role,
117176
'type': 'changeset',
118177
'path': '',
119178
'repos': changeset.repos.reponame,
120179
'rev': changeset.rev,
121-
'notify': 'always',
180+
'notify': notify,
122181
}
123182
return cls._from_dict(env, sub)
124183

125184
@classmethod
126-
def from_comment(cls, env, comment):
185+
def from_comment(cls, env, comment, user=None, role='commenter',
186+
notify='always'):
127187
"""
128188
Creates a subscription from a Comment object.
129189
"""
130190
sub = {
131-
'user': comment.author,
132-
'role': 'commenter',
191+
'user': user or comment.author,
192+
'role': user,
133193
'type': comment.type,
134-
'notify': 'always'
194+
'notify': notify,
135195
}
136196

137197
# Munge attachments
@@ -160,6 +220,34 @@ def from_comment(cls, env, comment):
160220

161221
return cls._from_dict(env, sub)
162222

223+
@classmethod
224+
def for_attachment(cls, env, attachment, path=None):
225+
"""
226+
Returns all subscriptions for an attachment. The path can be
227+
overridden.
228+
"""
229+
path_template = "/{0}/{1}/{2}"
230+
_path = path or path_template.format(attachment.parent_realm,
231+
attachment.parent_id,
232+
attachment.filename)
233+
args = {
234+
'type': 'attachment',
235+
'path': _path,
236+
}
237+
return cls.select(env, args)
238+
239+
@classmethod
240+
def for_changeset(cls, env, changeset):
241+
"""
242+
Returns all subscriptions for an changeset.
243+
"""
244+
args = {
245+
'type': 'changeset',
246+
'repos': changeset.repos.reponame,
247+
'rev': changeset.rev,
248+
}
249+
return cls.select(env, args)
250+
163251

164252
class SubscriptionAdmin(Component):
165253
"""
@@ -217,11 +305,47 @@ class SubscriptionListeners(Component):
217305
def attachment_added(self, attachment):
218306
Subscription.from_attachment(self.env, attachment)
219307

308+
def attachment_deleted(self, attachment):
309+
for subscription in Subscription.for_attachment(self.env, attachment):
310+
subscription.delete()
311+
312+
def attachment_reparented(self, attachment, old_parent_realm,
313+
old_parent_id):
314+
path_template = "/{0}/{1}/{2}"
315+
old_path = path_template.format(old_parent_realm,
316+
old_parent_id,
317+
attachment.filename)
318+
new_path = path_template.format(attachment.parent_realm,
319+
attachment.parent_id,
320+
attachment.filename)
321+
322+
for subscription in Subscription.for_attachment(self.env, attachment,
323+
old_path):
324+
subscription.path = new_path
325+
subscription.update()
326+
220327
# IRepositoryChangeListener methods
221328

222329
def changeset_added(self, repos, changeset):
223330
Subscription.from_changeset(self.env, changeset)
224331

332+
def changeset_modified(self, repos, changeset, old_changeset):
333+
# Handle existing subscriptions
334+
if changeset.author != old_changeset.author:
335+
for subscription in Subscription.for_changeset(self.env,
336+
changeset):
337+
if (subscription.user == old_changeset.author and
338+
subscription.role == 'author'):
339+
subscription.role = 'subscriber'
340+
341+
if subscription.user == changeset.author:
342+
subscription.role = 'author'
343+
344+
subscription.update()
345+
346+
# Create a new author subscription
347+
Subscription.from_changeset(self.env, changeset)
348+
225349
# ICodeCommentChangeListener methods
226350

227351
def comment_created(self, comment):

todo.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,17 @@ Need to figure out how to handle blanket subscriptions.
2424

2525
- [x] model
2626
- [x] create
27-
- [ ] update
28-
- [ ] delete
29-
- [ ] query
27+
- [x] update
28+
- [x] delete
29+
- [x] query
3030
- [x] db
3131
- [ ] api
3232

3333
## Listeners
3434

3535
- [x] comments_created - create a subscription for the comment author
3636
- [x] attachment_added - create a subscription for the attachment author
37-
- [ ] attachment_deleted - remove all subscriptions for the attachment (should we also remove all comments?)
38-
- [ ] attachment_reparented - update all subscriptions for the attachment
37+
- [x] attachment_deleted - remove all subscriptions for the attachment (should we also remove all comments?)
38+
- [x] attachment_reparented - update all subscriptions for the attachment
3939
- [x] changeset_added - create a subscription for the changeset author
40-
- [ ] changeset_modified - update the subscription (author?)
40+
- [x] changeset_modified - update the subscription (author?)

0 commit comments

Comments
 (0)