##// END OF EJS Templates
Add changeset status change into emails
marcink -
r2296:e5c0f201 codereview
parent child Browse files
Show More
@@ -1,416 +1,420 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden
29 from webob.exc import HTTPForbidden
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from pylons.decorators import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39
39
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import EmptyChangeset
43 from rhodecode.lib.utils import EmptyChangeset
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.lib.diffs import wrapped_diff
50 from rhodecode.lib.diffs import wrapped_diff
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 def _update_with_GET(params, GET):
55 def _update_with_GET(params, GET):
56 for k in ['diff1', 'diff2', 'diff']:
56 for k in ['diff1', 'diff2', 'diff']:
57 params[k] += GET.getall(k)
57 params[k] += GET.getall(k)
58
58
59
59
60 def anchor_url(revision, path, GET):
60 def anchor_url(revision, path, GET):
61 fid = h.FID(revision, path)
61 fid = h.FID(revision, path)
62 return h.url.current(anchor=fid, **dict(GET))
62 return h.url.current(anchor=fid, **dict(GET))
63
63
64
64
65 def get_ignore_ws(fid, GET):
65 def get_ignore_ws(fid, GET):
66 ig_ws_global = GET.get('ignorews')
66 ig_ws_global = GET.get('ignorews')
67 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
67 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
68 if ig_ws:
68 if ig_ws:
69 try:
69 try:
70 return int(ig_ws[0].split(':')[-1])
70 return int(ig_ws[0].split(':')[-1])
71 except:
71 except:
72 pass
72 pass
73 return ig_ws_global
73 return ig_ws_global
74
74
75
75
76 def _ignorews_url(GET, fileid=None):
76 def _ignorews_url(GET, fileid=None):
77 fileid = str(fileid) if fileid else None
77 fileid = str(fileid) if fileid else None
78 params = defaultdict(list)
78 params = defaultdict(list)
79 _update_with_GET(params, GET)
79 _update_with_GET(params, GET)
80 lbl = _('show white space')
80 lbl = _('show white space')
81 ig_ws = get_ignore_ws(fileid, GET)
81 ig_ws = get_ignore_ws(fileid, GET)
82 ln_ctx = get_line_ctx(fileid, GET)
82 ln_ctx = get_line_ctx(fileid, GET)
83 # global option
83 # global option
84 if fileid is None:
84 if fileid is None:
85 if ig_ws is None:
85 if ig_ws is None:
86 params['ignorews'] += [1]
86 params['ignorews'] += [1]
87 lbl = _('ignore white space')
87 lbl = _('ignore white space')
88 ctx_key = 'context'
88 ctx_key = 'context'
89 ctx_val = ln_ctx
89 ctx_val = ln_ctx
90 # per file options
90 # per file options
91 else:
91 else:
92 if ig_ws is None:
92 if ig_ws is None:
93 params[fileid] += ['WS:1']
93 params[fileid] += ['WS:1']
94 lbl = _('ignore white space')
94 lbl = _('ignore white space')
95
95
96 ctx_key = fileid
96 ctx_key = fileid
97 ctx_val = 'C:%s' % ln_ctx
97 ctx_val = 'C:%s' % ln_ctx
98 # if we have passed in ln_ctx pass it along to our params
98 # if we have passed in ln_ctx pass it along to our params
99 if ln_ctx:
99 if ln_ctx:
100 params[ctx_key] += [ctx_val]
100 params[ctx_key] += [ctx_val]
101
101
102 params['anchor'] = fileid
102 params['anchor'] = fileid
103 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
103 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
104 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
104 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
105
105
106
106
107 def get_line_ctx(fid, GET):
107 def get_line_ctx(fid, GET):
108 ln_ctx_global = GET.get('context')
108 ln_ctx_global = GET.get('context')
109 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
109 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
110
110
111 if ln_ctx:
111 if ln_ctx:
112 retval = ln_ctx[0].split(':')[-1]
112 retval = ln_ctx[0].split(':')[-1]
113 else:
113 else:
114 retval = ln_ctx_global
114 retval = ln_ctx_global
115
115
116 try:
116 try:
117 return int(retval)
117 return int(retval)
118 except:
118 except:
119 return
119 return
120
120
121
121
122 def _context_url(GET, fileid=None):
122 def _context_url(GET, fileid=None):
123 """
123 """
124 Generates url for context lines
124 Generates url for context lines
125
125
126 :param fileid:
126 :param fileid:
127 """
127 """
128
128
129 fileid = str(fileid) if fileid else None
129 fileid = str(fileid) if fileid else None
130 ig_ws = get_ignore_ws(fileid, GET)
130 ig_ws = get_ignore_ws(fileid, GET)
131 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
131 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
132
132
133 params = defaultdict(list)
133 params = defaultdict(list)
134 _update_with_GET(params, GET)
134 _update_with_GET(params, GET)
135
135
136 # global option
136 # global option
137 if fileid is None:
137 if fileid is None:
138 if ln_ctx > 0:
138 if ln_ctx > 0:
139 params['context'] += [ln_ctx]
139 params['context'] += [ln_ctx]
140
140
141 if ig_ws:
141 if ig_ws:
142 ig_ws_key = 'ignorews'
142 ig_ws_key = 'ignorews'
143 ig_ws_val = 1
143 ig_ws_val = 1
144
144
145 # per file option
145 # per file option
146 else:
146 else:
147 params[fileid] += ['C:%s' % ln_ctx]
147 params[fileid] += ['C:%s' % ln_ctx]
148 ig_ws_key = fileid
148 ig_ws_key = fileid
149 ig_ws_val = 'WS:%s' % 1
149 ig_ws_val = 'WS:%s' % 1
150
150
151 if ig_ws:
151 if ig_ws:
152 params[ig_ws_key] += [ig_ws_val]
152 params[ig_ws_key] += [ig_ws_val]
153
153
154 lbl = _('%s line context') % ln_ctx
154 lbl = _('%s line context') % ln_ctx
155
155
156 params['anchor'] = fileid
156 params['anchor'] = fileid
157 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
157 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
158 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
158 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
159
159
160
160
161 class ChangesetController(BaseRepoController):
161 class ChangesetController(BaseRepoController):
162
162
163 @LoginRequired()
163 @LoginRequired()
164 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
164 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
165 'repository.admin')
165 'repository.admin')
166 def __before__(self):
166 def __before__(self):
167 super(ChangesetController, self).__before__()
167 super(ChangesetController, self).__before__()
168 c.affected_files_cut_off = 60
168 c.affected_files_cut_off = 60
169
169
170 def index(self, revision):
170 def index(self, revision):
171
171
172 c.anchor_url = anchor_url
172 c.anchor_url = anchor_url
173 c.ignorews_url = _ignorews_url
173 c.ignorews_url = _ignorews_url
174 c.context_url = _context_url
174 c.context_url = _context_url
175 limit_off = request.GET.get('fulldiff')
175 limit_off = request.GET.get('fulldiff')
176 #get ranges of revisions if preset
176 #get ranges of revisions if preset
177 rev_range = revision.split('...')[:2]
177 rev_range = revision.split('...')[:2]
178 enable_comments = True
178 enable_comments = True
179 try:
179 try:
180 if len(rev_range) == 2:
180 if len(rev_range) == 2:
181 enable_comments = False
181 enable_comments = False
182 rev_start = rev_range[0]
182 rev_start = rev_range[0]
183 rev_end = rev_range[1]
183 rev_end = rev_range[1]
184 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
184 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
185 end=rev_end)
185 end=rev_end)
186 else:
186 else:
187 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
187 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
188
188
189 c.cs_ranges = list(rev_ranges)
189 c.cs_ranges = list(rev_ranges)
190 if not c.cs_ranges:
190 if not c.cs_ranges:
191 raise RepositoryError('Changeset range returned empty result')
191 raise RepositoryError('Changeset range returned empty result')
192
192
193 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
193 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
194 log.error(traceback.format_exc())
194 log.error(traceback.format_exc())
195 h.flash(str(e), category='warning')
195 h.flash(str(e), category='warning')
196 return redirect(url('home'))
196 return redirect(url('home'))
197
197
198 c.changes = OrderedDict()
198 c.changes = OrderedDict()
199
199
200 c.lines_added = 0 # count of lines added
200 c.lines_added = 0 # count of lines added
201 c.lines_deleted = 0 # count of lines removes
201 c.lines_deleted = 0 # count of lines removes
202
202
203 cumulative_diff = 0
203 cumulative_diff = 0
204 c.cut_off = False # defines if cut off limit is reached
204 c.cut_off = False # defines if cut off limit is reached
205 c.changeset_statuses = ChangesetStatus.STATUSES
205 c.changeset_statuses = ChangesetStatus.STATUSES
206 c.comments = []
206 c.comments = []
207 c.statuses = []
207 c.statuses = []
208 c.inline_comments = []
208 c.inline_comments = []
209 c.inline_cnt = 0
209 c.inline_cnt = 0
210 # Iterate over ranges (default changeset view is always one changeset)
210 # Iterate over ranges (default changeset view is always one changeset)
211 for changeset in c.cs_ranges:
211 for changeset in c.cs_ranges:
212
212
213 c.statuses.extend([ChangesetStatusModel()\
213 c.statuses.extend([ChangesetStatusModel()\
214 .get_status(c.rhodecode_db_repo.repo_id,
214 .get_status(c.rhodecode_db_repo.repo_id,
215 changeset.raw_id)])
215 changeset.raw_id)])
216
216
217 c.comments.extend(ChangesetCommentsModel()\
217 c.comments.extend(ChangesetCommentsModel()\
218 .get_comments(c.rhodecode_db_repo.repo_id,
218 .get_comments(c.rhodecode_db_repo.repo_id,
219 changeset.raw_id))
219 changeset.raw_id))
220 inlines = ChangesetCommentsModel()\
220 inlines = ChangesetCommentsModel()\
221 .get_inline_comments(c.rhodecode_db_repo.repo_id,
221 .get_inline_comments(c.rhodecode_db_repo.repo_id,
222 changeset.raw_id)
222 changeset.raw_id)
223 c.inline_comments.extend(inlines)
223 c.inline_comments.extend(inlines)
224 c.changes[changeset.raw_id] = []
224 c.changes[changeset.raw_id] = []
225 try:
225 try:
226 changeset_parent = changeset.parents[0]
226 changeset_parent = changeset.parents[0]
227 except IndexError:
227 except IndexError:
228 changeset_parent = None
228 changeset_parent = None
229
229
230 #==================================================================
230 #==================================================================
231 # ADDED FILES
231 # ADDED FILES
232 #==================================================================
232 #==================================================================
233 for node in changeset.added:
233 for node in changeset.added:
234 fid = h.FID(revision, node.path)
234 fid = h.FID(revision, node.path)
235 line_context_lcl = get_line_ctx(fid, request.GET)
235 line_context_lcl = get_line_ctx(fid, request.GET)
236 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
236 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
237 lim = self.cut_off_limit
237 lim = self.cut_off_limit
238 if cumulative_diff > self.cut_off_limit:
238 if cumulative_diff > self.cut_off_limit:
239 lim = -1 if limit_off is None else None
239 lim = -1 if limit_off is None else None
240 size, cs1, cs2, diff, st = wrapped_diff(
240 size, cs1, cs2, diff, st = wrapped_diff(
241 filenode_old=None,
241 filenode_old=None,
242 filenode_new=node,
242 filenode_new=node,
243 cut_off_limit=lim,
243 cut_off_limit=lim,
244 ignore_whitespace=ign_whitespace_lcl,
244 ignore_whitespace=ign_whitespace_lcl,
245 line_context=line_context_lcl,
245 line_context=line_context_lcl,
246 enable_comments=enable_comments
246 enable_comments=enable_comments
247 )
247 )
248 cumulative_diff += size
248 cumulative_diff += size
249 c.lines_added += st[0]
249 c.lines_added += st[0]
250 c.lines_deleted += st[1]
250 c.lines_deleted += st[1]
251 c.changes[changeset.raw_id].append(
251 c.changes[changeset.raw_id].append(
252 ('added', node, diff, cs1, cs2, st)
252 ('added', node, diff, cs1, cs2, st)
253 )
253 )
254
254
255 #==================================================================
255 #==================================================================
256 # CHANGED FILES
256 # CHANGED FILES
257 #==================================================================
257 #==================================================================
258 for node in changeset.changed:
258 for node in changeset.changed:
259 try:
259 try:
260 filenode_old = changeset_parent.get_node(node.path)
260 filenode_old = changeset_parent.get_node(node.path)
261 except ChangesetError:
261 except ChangesetError:
262 log.warning('Unable to fetch parent node for diff')
262 log.warning('Unable to fetch parent node for diff')
263 filenode_old = FileNode(node.path, '', EmptyChangeset())
263 filenode_old = FileNode(node.path, '', EmptyChangeset())
264
264
265 fid = h.FID(revision, node.path)
265 fid = h.FID(revision, node.path)
266 line_context_lcl = get_line_ctx(fid, request.GET)
266 line_context_lcl = get_line_ctx(fid, request.GET)
267 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
267 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
268 lim = self.cut_off_limit
268 lim = self.cut_off_limit
269 if cumulative_diff > self.cut_off_limit:
269 if cumulative_diff > self.cut_off_limit:
270 lim = -1 if limit_off is None else None
270 lim = -1 if limit_off is None else None
271 size, cs1, cs2, diff, st = wrapped_diff(
271 size, cs1, cs2, diff, st = wrapped_diff(
272 filenode_old=filenode_old,
272 filenode_old=filenode_old,
273 filenode_new=node,
273 filenode_new=node,
274 cut_off_limit=lim,
274 cut_off_limit=lim,
275 ignore_whitespace=ign_whitespace_lcl,
275 ignore_whitespace=ign_whitespace_lcl,
276 line_context=line_context_lcl,
276 line_context=line_context_lcl,
277 enable_comments=enable_comments
277 enable_comments=enable_comments
278 )
278 )
279 cumulative_diff += size
279 cumulative_diff += size
280 c.lines_added += st[0]
280 c.lines_added += st[0]
281 c.lines_deleted += st[1]
281 c.lines_deleted += st[1]
282 c.changes[changeset.raw_id].append(
282 c.changes[changeset.raw_id].append(
283 ('changed', node, diff, cs1, cs2, st)
283 ('changed', node, diff, cs1, cs2, st)
284 )
284 )
285 #==================================================================
285 #==================================================================
286 # REMOVED FILES
286 # REMOVED FILES
287 #==================================================================
287 #==================================================================
288 for node in changeset.removed:
288 for node in changeset.removed:
289 c.changes[changeset.raw_id].append(
289 c.changes[changeset.raw_id].append(
290 ('removed', node, None, None, None, (0, 0))
290 ('removed', node, None, None, None, (0, 0))
291 )
291 )
292
292
293 # count inline comments
293 # count inline comments
294 for path, lines in c.inline_comments:
294 for path, lines in c.inline_comments:
295 for comments in lines.values():
295 for comments in lines.values():
296 c.inline_cnt += len(comments)
296 c.inline_cnt += len(comments)
297
297
298 if len(c.cs_ranges) == 1:
298 if len(c.cs_ranges) == 1:
299 c.changeset = c.cs_ranges[0]
299 c.changeset = c.cs_ranges[0]
300 c.changes = c.changes[c.changeset.raw_id]
300 c.changes = c.changes[c.changeset.raw_id]
301
301
302 return render('changeset/changeset.html')
302 return render('changeset/changeset.html')
303 else:
303 else:
304 return render('changeset/changeset_range.html')
304 return render('changeset/changeset_range.html')
305
305
306 def raw_changeset(self, revision):
306 def raw_changeset(self, revision):
307
307
308 method = request.GET.get('diff', 'show')
308 method = request.GET.get('diff', 'show')
309 ignore_whitespace = request.GET.get('ignorews') == '1'
309 ignore_whitespace = request.GET.get('ignorews') == '1'
310 line_context = request.GET.get('context', 3)
310 line_context = request.GET.get('context', 3)
311 try:
311 try:
312 c.scm_type = c.rhodecode_repo.alias
312 c.scm_type = c.rhodecode_repo.alias
313 c.changeset = c.rhodecode_repo.get_changeset(revision)
313 c.changeset = c.rhodecode_repo.get_changeset(revision)
314 except RepositoryError:
314 except RepositoryError:
315 log.error(traceback.format_exc())
315 log.error(traceback.format_exc())
316 return redirect(url('home'))
316 return redirect(url('home'))
317 else:
317 else:
318 try:
318 try:
319 c.changeset_parent = c.changeset.parents[0]
319 c.changeset_parent = c.changeset.parents[0]
320 except IndexError:
320 except IndexError:
321 c.changeset_parent = None
321 c.changeset_parent = None
322 c.changes = []
322 c.changes = []
323
323
324 for node in c.changeset.added:
324 for node in c.changeset.added:
325 filenode_old = FileNode(node.path, '')
325 filenode_old = FileNode(node.path, '')
326 if filenode_old.is_binary or node.is_binary:
326 if filenode_old.is_binary or node.is_binary:
327 diff = _('binary file') + '\n'
327 diff = _('binary file') + '\n'
328 else:
328 else:
329 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
329 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
330 ignore_whitespace=ignore_whitespace,
330 ignore_whitespace=ignore_whitespace,
331 context=line_context)
331 context=line_context)
332 diff = diffs.DiffProcessor(f_gitdiff,
332 diff = diffs.DiffProcessor(f_gitdiff,
333 format='gitdiff').raw_diff()
333 format='gitdiff').raw_diff()
334
334
335 cs1 = None
335 cs1 = None
336 cs2 = node.changeset.raw_id
336 cs2 = node.changeset.raw_id
337 c.changes.append(('added', node, diff, cs1, cs2))
337 c.changes.append(('added', node, diff, cs1, cs2))
338
338
339 for node in c.changeset.changed:
339 for node in c.changeset.changed:
340 filenode_old = c.changeset_parent.get_node(node.path)
340 filenode_old = c.changeset_parent.get_node(node.path)
341 if filenode_old.is_binary or node.is_binary:
341 if filenode_old.is_binary or node.is_binary:
342 diff = _('binary file')
342 diff = _('binary file')
343 else:
343 else:
344 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
344 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
345 ignore_whitespace=ignore_whitespace,
345 ignore_whitespace=ignore_whitespace,
346 context=line_context)
346 context=line_context)
347 diff = diffs.DiffProcessor(f_gitdiff,
347 diff = diffs.DiffProcessor(f_gitdiff,
348 format='gitdiff').raw_diff()
348 format='gitdiff').raw_diff()
349
349
350 cs1 = filenode_old.changeset.raw_id
350 cs1 = filenode_old.changeset.raw_id
351 cs2 = node.changeset.raw_id
351 cs2 = node.changeset.raw_id
352 c.changes.append(('changed', node, diff, cs1, cs2))
352 c.changes.append(('changed', node, diff, cs1, cs2))
353
353
354 response.content_type = 'text/plain'
354 response.content_type = 'text/plain'
355
355
356 if method == 'download':
356 if method == 'download':
357 response.content_disposition = 'attachment; filename=%s.patch' \
357 response.content_disposition = 'attachment; filename=%s.patch' \
358 % revision
358 % revision
359
359
360 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
360 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
361 for x in c.changeset.parents])
361 for x in c.changeset.parents])
362
362
363 c.diffs = ''
363 c.diffs = ''
364 for x in c.changes:
364 for x in c.changes:
365 c.diffs += x[2]
365 c.diffs += x[2]
366
366
367 return render('changeset/raw_changeset.html')
367 return render('changeset/raw_changeset.html')
368
368
369 @jsonify
369 @jsonify
370 def comment(self, repo_name, revision):
370 def comment(self, repo_name, revision):
371 status = request.POST.get('changeset_status')
372 change_status = request.POST.get('change_changeset_status')
373
371 comm = ChangesetCommentsModel().create(
374 comm = ChangesetCommentsModel().create(
372 text=request.POST.get('text'),
375 text=request.POST.get('text'),
373 repo_id=c.rhodecode_db_repo.repo_id,
376 repo_id=c.rhodecode_db_repo.repo_id,
374 user_id=c.rhodecode_user.user_id,
377 user_id=c.rhodecode_user.user_id,
375 revision=revision,
378 revision=revision,
376 f_path=request.POST.get('f_path'),
379 f_path=request.POST.get('f_path'),
377 line_no=request.POST.get('line')
380 line_no=request.POST.get('line'),
381 status_change=(ChangesetStatus.get_status_lbl(status)
382 if status and change_status else None)
378 )
383 )
379
384
380 # get status if set !
385 # get status if set !
381 status = request.POST.get('changeset_status')
386 if status and change_status:
382 if status and request.POST.get('change_changeset_status'):
383 ChangesetStatusModel().set_status(
387 ChangesetStatusModel().set_status(
384 c.rhodecode_db_repo.repo_id,
388 c.rhodecode_db_repo.repo_id,
385 revision,
389 revision,
386 status,
390 status,
387 c.rhodecode_user.user_id,
391 c.rhodecode_user.user_id,
388 comm,
392 comm,
389 )
393 )
390
394
391 Session.commit()
395 Session.commit()
392 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
396 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
393 return redirect(h.url('changeset_home', repo_name=repo_name,
397 return redirect(h.url('changeset_home', repo_name=repo_name,
394 revision=revision))
398 revision=revision))
395
399
396 data = {
400 data = {
397 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
401 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
398 }
402 }
399 if comm:
403 if comm:
400 c.co = comm
404 c.co = comm
401 data.update(comm.get_dict())
405 data.update(comm.get_dict())
402 data.update({'rendered_text':
406 data.update({'rendered_text':
403 render('changeset/changeset_comment_block.html')})
407 render('changeset/changeset_comment_block.html')})
404
408
405 return data
409 return data
406
410
407 @jsonify
411 @jsonify
408 def delete_comment(self, repo_name, comment_id):
412 def delete_comment(self, repo_name, comment_id):
409 co = ChangesetComment.get(comment_id)
413 co = ChangesetComment.get(comment_id)
410 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
414 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
411 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
415 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
412 ChangesetCommentsModel().delete(comment=co)
416 ChangesetCommentsModel().delete(comment=co)
413 Session.commit()
417 Session.commit()
414 return True
418 return True
415 else:
419 else:
416 raise HTTPForbidden()
420 raise HTTPForbidden()
@@ -1,153 +1,156 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.comment
3 rhodecode.model.comment
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 comments model for RhodeCode
6 comments model for RhodeCode
7
7
8 :created_on: Nov 11, 2011
8 :created_on: Nov 11, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from sqlalchemy.util.compat import defaultdict
30 from sqlalchemy.util.compat import defaultdict
31
31
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
32 from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
36 from rhodecode.model.notification import NotificationModel
36 from rhodecode.model.notification import NotificationModel
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 class ChangesetCommentsModel(BaseModel):
41 class ChangesetCommentsModel(BaseModel):
42
42
43 def __get_changeset_comment(self, changeset_comment):
43 def __get_changeset_comment(self, changeset_comment):
44 return self._get_instance(ChangesetComment, changeset_comment)
44 return self._get_instance(ChangesetComment, changeset_comment)
45
45
46 def _extract_mentions(self, s):
46 def _extract_mentions(self, s):
47 user_objects = []
47 user_objects = []
48 for username in extract_mentioned_users(s):
48 for username in extract_mentioned_users(s):
49 user_obj = User.get_by_username(username, case_insensitive=True)
49 user_obj = User.get_by_username(username, case_insensitive=True)
50 if user_obj:
50 if user_obj:
51 user_objects.append(user_obj)
51 user_objects.append(user_obj)
52 return user_objects
52 return user_objects
53
53
54 def create(self, text, repo_id, user_id, revision, f_path=None,
54 def create(self, text, repo_id, user_id, revision, f_path=None,
55 line_no=None):
55 line_no=None, status_change=None):
56 """
56 """
57 Creates new comment for changeset
57 Creates new comment for changeset. IF status_change is not none
58 this comment is associated with a status change of changeset
58
59
59 :param text:
60 :param text:
60 :param repo_id:
61 :param repo_id:
61 :param user_id:
62 :param user_id:
62 :param revision:
63 :param revision:
63 :param f_path:
64 :param f_path:
64 :param line_no:
65 :param line_no:
66 :param status_change:
65 """
67 """
66
68
67 if text:
69 if text:
68 repo = Repository.get(repo_id)
70 repo = Repository.get(repo_id)
69 cs = repo.scm_instance.get_changeset(revision)
71 cs = repo.scm_instance.get_changeset(revision)
70 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
72 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
71 author_email = cs.author_email
73 author_email = cs.author_email
72 comment = ChangesetComment()
74 comment = ChangesetComment()
73 comment.repo = repo
75 comment.repo = repo
74 comment.user_id = user_id
76 comment.user_id = user_id
75 comment.revision = revision
77 comment.revision = revision
76 comment.text = text
78 comment.text = text
77 comment.f_path = f_path
79 comment.f_path = f_path
78 comment.line_no = line_no
80 comment.line_no = line_no
79
81
80 self.sa.add(comment)
82 self.sa.add(comment)
81 self.sa.flush()
83 self.sa.flush()
82 # make notification
84 # make notification
83 line = ''
85 line = ''
84 if line_no:
86 if line_no:
85 line = _('on line %s') % line_no
87 line = _('on line %s') % line_no
86 subj = safe_unicode(
88 subj = safe_unicode(
87 h.link_to('Re commit: %(commit_desc)s %(line)s' % \
89 h.link_to('Re commit: %(commit_desc)s %(line)s' % \
88 {'commit_desc': desc, 'line': line},
90 {'commit_desc': desc, 'line': line},
89 h.url('changeset_home', repo_name=repo.repo_name,
91 h.url('changeset_home', repo_name=repo.repo_name,
90 revision=revision,
92 revision=revision,
91 anchor='comment-%s' % comment.comment_id,
93 anchor='comment-%s' % comment.comment_id,
92 qualified=True,
94 qualified=True,
93 )
95 )
94 )
96 )
95 )
97 )
96
98
97 body = text
99 body = text
98
100
99 # get the current participants of this changeset
101 # get the current participants of this changeset
100 recipients = ChangesetComment.get_users(revision=revision)
102 recipients = ChangesetComment.get_users(revision=revision)
101
103
102 # add changeset author if it's in rhodecode system
104 # add changeset author if it's in rhodecode system
103 recipients += [User.get_by_email(author_email)]
105 recipients += [User.get_by_email(author_email)]
104
106
105 NotificationModel().create(
107 NotificationModel().create(
106 created_by=user_id, subject=subj, body=body,
108 created_by=user_id, subject=subj, body=body,
107 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
109 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT,
110 email_kwargs={'status_change': status_change}
108 )
111 )
109
112
110 mention_recipients = set(self._extract_mentions(body))\
113 mention_recipients = set(self._extract_mentions(body))\
111 .difference(recipients)
114 .difference(recipients)
112 if mention_recipients:
115 if mention_recipients:
113 subj = _('[Mention]') + ' ' + subj
116 subj = _('[Mention]') + ' ' + subj
114 NotificationModel().create(
117 NotificationModel().create(
115 created_by=user_id, subject=subj, body=body,
118 created_by=user_id, subject=subj, body=body,
116 recipients=mention_recipients,
119 recipients=mention_recipients,
117 type_=Notification.TYPE_CHANGESET_COMMENT
120 type_=Notification.TYPE_CHANGESET_COMMENT
118 )
121 )
119
122
120 return comment
123 return comment
121
124
122 def delete(self, comment):
125 def delete(self, comment):
123 """
126 """
124 Deletes given comment
127 Deletes given comment
125
128
126 :param comment_id:
129 :param comment_id:
127 """
130 """
128 comment = self.__get_changeset_comment(comment)
131 comment = self.__get_changeset_comment(comment)
129 self.sa.delete(comment)
132 self.sa.delete(comment)
130
133
131 return comment
134 return comment
132
135
133 def get_comments(self, repo_id, revision):
136 def get_comments(self, repo_id, revision):
134 return ChangesetComment.query()\
137 return ChangesetComment.query()\
135 .filter(ChangesetComment.repo_id == repo_id)\
138 .filter(ChangesetComment.repo_id == repo_id)\
136 .filter(ChangesetComment.revision == revision)\
139 .filter(ChangesetComment.revision == revision)\
137 .filter(ChangesetComment.line_no == None)\
140 .filter(ChangesetComment.line_no == None)\
138 .filter(ChangesetComment.f_path == None).all()
141 .filter(ChangesetComment.f_path == None).all()
139
142
140 def get_inline_comments(self, repo_id, revision):
143 def get_inline_comments(self, repo_id, revision):
141 comments = self.sa.query(ChangesetComment)\
144 comments = self.sa.query(ChangesetComment)\
142 .filter(ChangesetComment.repo_id == repo_id)\
145 .filter(ChangesetComment.repo_id == repo_id)\
143 .filter(ChangesetComment.revision == revision)\
146 .filter(ChangesetComment.revision == revision)\
144 .filter(ChangesetComment.line_no != None)\
147 .filter(ChangesetComment.line_no != None)\
145 .filter(ChangesetComment.f_path != None)\
148 .filter(ChangesetComment.f_path != None)\
146 .order_by(ChangesetComment.comment_id.asc())\
149 .order_by(ChangesetComment.comment_id.asc())\
147 .all()
150 .all()
148
151
149 paths = defaultdict(lambda: defaultdict(list))
152 paths = defaultdict(lambda: defaultdict(list))
150
153
151 for co in comments:
154 for co in comments:
152 paths[co.f_path][co.line_no].append(co)
155 paths[co.f_path][co.line_no].append(co)
153 return paths.items()
156 return paths.items()
@@ -1,1348 +1,1352 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from collections import defaultdict
30 from collections import defaultdict
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from beaker.cache import cache_region, region_invalidate
35 from beaker.cache import cache_region, region_invalidate
36
36
37 from pylons.i18n.translation import lazy_ugettext as _
37 from pylons.i18n.translation import lazy_ugettext as _
38
38
39 from rhodecode.lib.vcs import get_backend
39 from rhodecode.lib.vcs import get_backend
40 from rhodecode.lib.vcs.utils.helpers import get_scm
40 from rhodecode.lib.vcs.utils.helpers import get_scm
41 from rhodecode.lib.vcs.exceptions import VCSError
41 from rhodecode.lib.vcs.exceptions import VCSError
42 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 from rhodecode.lib.vcs.utils.lazy import LazyProperty
43
43
44 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
44 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
45 safe_unicode
45 safe_unicode
46 from rhodecode.lib.compat import json
46 from rhodecode.lib.compat import json
47 from rhodecode.lib.caching_query import FromCache
47 from rhodecode.lib.caching_query import FromCache
48
48
49 from rhodecode.model.meta import Base, Session
49 from rhodecode.model.meta import Base, Session
50 import hashlib
50 import hashlib
51
51
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55 #==============================================================================
55 #==============================================================================
56 # BASE CLASSES
56 # BASE CLASSES
57 #==============================================================================
57 #==============================================================================
58
58
59 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
60
60
61
61
62 class ModelSerializer(json.JSONEncoder):
62 class ModelSerializer(json.JSONEncoder):
63 """
63 """
64 Simple Serializer for JSON,
64 Simple Serializer for JSON,
65
65
66 usage::
66 usage::
67
67
68 to make object customized for serialization implement a __json__
68 to make object customized for serialization implement a __json__
69 method that will return a dict for serialization into json
69 method that will return a dict for serialization into json
70
70
71 example::
71 example::
72
72
73 class Task(object):
73 class Task(object):
74
74
75 def __init__(self, name, value):
75 def __init__(self, name, value):
76 self.name = name
76 self.name = name
77 self.value = value
77 self.value = value
78
78
79 def __json__(self):
79 def __json__(self):
80 return dict(name=self.name,
80 return dict(name=self.name,
81 value=self.value)
81 value=self.value)
82
82
83 """
83 """
84
84
85 def default(self, obj):
85 def default(self, obj):
86
86
87 if hasattr(obj, '__json__'):
87 if hasattr(obj, '__json__'):
88 return obj.__json__()
88 return obj.__json__()
89 else:
89 else:
90 return json.JSONEncoder.default(self, obj)
90 return json.JSONEncoder.default(self, obj)
91
91
92
92
93 class BaseModel(object):
93 class BaseModel(object):
94 """
94 """
95 Base Model for all classess
95 Base Model for all classess
96 """
96 """
97
97
98 @classmethod
98 @classmethod
99 def _get_keys(cls):
99 def _get_keys(cls):
100 """return column names for this model """
100 """return column names for this model """
101 return class_mapper(cls).c.keys()
101 return class_mapper(cls).c.keys()
102
102
103 def get_dict(self):
103 def get_dict(self):
104 """
104 """
105 return dict with keys and values corresponding
105 return dict with keys and values corresponding
106 to this model data """
106 to this model data """
107
107
108 d = {}
108 d = {}
109 for k in self._get_keys():
109 for k in self._get_keys():
110 d[k] = getattr(self, k)
110 d[k] = getattr(self, k)
111
111
112 # also use __json__() if present to get additional fields
112 # also use __json__() if present to get additional fields
113 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
113 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
114 d[k] = val
114 d[k] = val
115 return d
115 return d
116
116
117 def get_appstruct(self):
117 def get_appstruct(self):
118 """return list with keys and values tupples corresponding
118 """return list with keys and values tupples corresponding
119 to this model data """
119 to this model data """
120
120
121 l = []
121 l = []
122 for k in self._get_keys():
122 for k in self._get_keys():
123 l.append((k, getattr(self, k),))
123 l.append((k, getattr(self, k),))
124 return l
124 return l
125
125
126 def populate_obj(self, populate_dict):
126 def populate_obj(self, populate_dict):
127 """populate model with data from given populate_dict"""
127 """populate model with data from given populate_dict"""
128
128
129 for k in self._get_keys():
129 for k in self._get_keys():
130 if k in populate_dict:
130 if k in populate_dict:
131 setattr(self, k, populate_dict[k])
131 setattr(self, k, populate_dict[k])
132
132
133 @classmethod
133 @classmethod
134 def query(cls):
134 def query(cls):
135 return Session.query(cls)
135 return Session.query(cls)
136
136
137 @classmethod
137 @classmethod
138 def get(cls, id_):
138 def get(cls, id_):
139 if id_:
139 if id_:
140 return cls.query().get(id_)
140 return cls.query().get(id_)
141
141
142 @classmethod
142 @classmethod
143 def getAll(cls):
143 def getAll(cls):
144 return cls.query().all()
144 return cls.query().all()
145
145
146 @classmethod
146 @classmethod
147 def delete(cls, id_):
147 def delete(cls, id_):
148 obj = cls.query().get(id_)
148 obj = cls.query().get(id_)
149 Session.delete(obj)
149 Session.delete(obj)
150
150
151 def __repr__(self):
151 def __repr__(self):
152 if hasattr(self, '__unicode__'):
152 if hasattr(self, '__unicode__'):
153 # python repr needs to return str
153 # python repr needs to return str
154 return safe_str(self.__unicode__())
154 return safe_str(self.__unicode__())
155 return '<DB:%s>' % (self.__class__.__name__)
155 return '<DB:%s>' % (self.__class__.__name__)
156
156
157 class RhodeCodeSetting(Base, BaseModel):
157 class RhodeCodeSetting(Base, BaseModel):
158 __tablename__ = 'rhodecode_settings'
158 __tablename__ = 'rhodecode_settings'
159 __table_args__ = (
159 __table_args__ = (
160 UniqueConstraint('app_settings_name'),
160 UniqueConstraint('app_settings_name'),
161 {'extend_existing': True, 'mysql_engine': 'InnoDB',
161 {'extend_existing': True, 'mysql_engine': 'InnoDB',
162 'mysql_charset': 'utf8'}
162 'mysql_charset': 'utf8'}
163 )
163 )
164 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
164 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
165 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
165 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
166 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
166 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
167
167
168 def __init__(self, k='', v=''):
168 def __init__(self, k='', v=''):
169 self.app_settings_name = k
169 self.app_settings_name = k
170 self.app_settings_value = v
170 self.app_settings_value = v
171
171
172 @validates('_app_settings_value')
172 @validates('_app_settings_value')
173 def validate_settings_value(self, key, val):
173 def validate_settings_value(self, key, val):
174 assert type(val) == unicode
174 assert type(val) == unicode
175 return val
175 return val
176
176
177 @hybrid_property
177 @hybrid_property
178 def app_settings_value(self):
178 def app_settings_value(self):
179 v = self._app_settings_value
179 v = self._app_settings_value
180 if self.app_settings_name == 'ldap_active':
180 if self.app_settings_name == 'ldap_active':
181 v = str2bool(v)
181 v = str2bool(v)
182 return v
182 return v
183
183
184 @app_settings_value.setter
184 @app_settings_value.setter
185 def app_settings_value(self, val):
185 def app_settings_value(self, val):
186 """
186 """
187 Setter that will always make sure we use unicode in app_settings_value
187 Setter that will always make sure we use unicode in app_settings_value
188
188
189 :param val:
189 :param val:
190 """
190 """
191 self._app_settings_value = safe_unicode(val)
191 self._app_settings_value = safe_unicode(val)
192
192
193 def __unicode__(self):
193 def __unicode__(self):
194 return u"<%s('%s:%s')>" % (
194 return u"<%s('%s:%s')>" % (
195 self.__class__.__name__,
195 self.__class__.__name__,
196 self.app_settings_name, self.app_settings_value
196 self.app_settings_name, self.app_settings_value
197 )
197 )
198
198
199 @classmethod
199 @classmethod
200 def get_by_name(cls, ldap_key):
200 def get_by_name(cls, ldap_key):
201 return cls.query()\
201 return cls.query()\
202 .filter(cls.app_settings_name == ldap_key).scalar()
202 .filter(cls.app_settings_name == ldap_key).scalar()
203
203
204 @classmethod
204 @classmethod
205 def get_app_settings(cls, cache=False):
205 def get_app_settings(cls, cache=False):
206
206
207 ret = cls.query()
207 ret = cls.query()
208
208
209 if cache:
209 if cache:
210 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
210 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
211
211
212 if not ret:
212 if not ret:
213 raise Exception('Could not get application settings !')
213 raise Exception('Could not get application settings !')
214 settings = {}
214 settings = {}
215 for each in ret:
215 for each in ret:
216 settings['rhodecode_' + each.app_settings_name] = \
216 settings['rhodecode_' + each.app_settings_name] = \
217 each.app_settings_value
217 each.app_settings_value
218
218
219 return settings
219 return settings
220
220
221 @classmethod
221 @classmethod
222 def get_ldap_settings(cls, cache=False):
222 def get_ldap_settings(cls, cache=False):
223 ret = cls.query()\
223 ret = cls.query()\
224 .filter(cls.app_settings_name.startswith('ldap_')).all()
224 .filter(cls.app_settings_name.startswith('ldap_')).all()
225 fd = {}
225 fd = {}
226 for row in ret:
226 for row in ret:
227 fd.update({row.app_settings_name:row.app_settings_value})
227 fd.update({row.app_settings_name:row.app_settings_value})
228
228
229 return fd
229 return fd
230
230
231
231
232 class RhodeCodeUi(Base, BaseModel):
232 class RhodeCodeUi(Base, BaseModel):
233 __tablename__ = 'rhodecode_ui'
233 __tablename__ = 'rhodecode_ui'
234 __table_args__ = (
234 __table_args__ = (
235 UniqueConstraint('ui_key'),
235 UniqueConstraint('ui_key'),
236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
237 'mysql_charset': 'utf8'}
237 'mysql_charset': 'utf8'}
238 )
238 )
239
239
240 HOOK_UPDATE = 'changegroup.update'
240 HOOK_UPDATE = 'changegroup.update'
241 HOOK_REPO_SIZE = 'changegroup.repo_size'
241 HOOK_REPO_SIZE = 'changegroup.repo_size'
242 HOOK_PUSH = 'pretxnchangegroup.push_logger'
242 HOOK_PUSH = 'pretxnchangegroup.push_logger'
243 HOOK_PULL = 'preoutgoing.pull_logger'
243 HOOK_PULL = 'preoutgoing.pull_logger'
244
244
245 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
246 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
249 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
250
250
251 @classmethod
251 @classmethod
252 def get_by_key(cls, key):
252 def get_by_key(cls, key):
253 return cls.query().filter(cls.ui_key == key)
253 return cls.query().filter(cls.ui_key == key)
254
254
255 @classmethod
255 @classmethod
256 def get_builtin_hooks(cls):
256 def get_builtin_hooks(cls):
257 q = cls.query()
257 q = cls.query()
258 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
258 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
259 cls.HOOK_REPO_SIZE,
259 cls.HOOK_REPO_SIZE,
260 cls.HOOK_PUSH, cls.HOOK_PULL]))
260 cls.HOOK_PUSH, cls.HOOK_PULL]))
261 return q.all()
261 return q.all()
262
262
263 @classmethod
263 @classmethod
264 def get_custom_hooks(cls):
264 def get_custom_hooks(cls):
265 q = cls.query()
265 q = cls.query()
266 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
266 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
267 cls.HOOK_REPO_SIZE,
267 cls.HOOK_REPO_SIZE,
268 cls.HOOK_PUSH, cls.HOOK_PULL]))
268 cls.HOOK_PUSH, cls.HOOK_PULL]))
269 q = q.filter(cls.ui_section == 'hooks')
269 q = q.filter(cls.ui_section == 'hooks')
270 return q.all()
270 return q.all()
271
271
272 @classmethod
272 @classmethod
273 def create_or_update_hook(cls, key, val):
273 def create_or_update_hook(cls, key, val):
274 new_ui = cls.get_by_key(key).scalar() or cls()
274 new_ui = cls.get_by_key(key).scalar() or cls()
275 new_ui.ui_section = 'hooks'
275 new_ui.ui_section = 'hooks'
276 new_ui.ui_active = True
276 new_ui.ui_active = True
277 new_ui.ui_key = key
277 new_ui.ui_key = key
278 new_ui.ui_value = val
278 new_ui.ui_value = val
279
279
280 Session.add(new_ui)
280 Session.add(new_ui)
281
281
282
282
283 class User(Base, BaseModel):
283 class User(Base, BaseModel):
284 __tablename__ = 'users'
284 __tablename__ = 'users'
285 __table_args__ = (
285 __table_args__ = (
286 UniqueConstraint('username'), UniqueConstraint('email'),
286 UniqueConstraint('username'), UniqueConstraint('email'),
287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
288 'mysql_charset': 'utf8'}
288 'mysql_charset': 'utf8'}
289 )
289 )
290 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
290 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
291 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
291 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
292 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
292 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
293 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
293 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
294 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
294 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
295 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
297 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
297 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
298 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
299 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
301
301
302 user_log = relationship('UserLog', cascade='all')
302 user_log = relationship('UserLog', cascade='all')
303 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
303 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
304
304
305 repositories = relationship('Repository')
305 repositories = relationship('Repository')
306 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
306 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
307 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
307 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
308 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
308 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
309
309
310 group_member = relationship('UsersGroupMember', cascade='all')
310 group_member = relationship('UsersGroupMember', cascade='all')
311
311
312 notifications = relationship('UserNotification', cascade='all')
312 notifications = relationship('UserNotification', cascade='all')
313 # notifications assigned to this user
313 # notifications assigned to this user
314 user_created_notifications = relationship('Notification', cascade='all')
314 user_created_notifications = relationship('Notification', cascade='all')
315 # comments created by this user
315 # comments created by this user
316 user_comments = relationship('ChangesetComment', cascade='all')
316 user_comments = relationship('ChangesetComment', cascade='all')
317
317
318 @hybrid_property
318 @hybrid_property
319 def email(self):
319 def email(self):
320 return self._email
320 return self._email
321
321
322 @email.setter
322 @email.setter
323 def email(self, val):
323 def email(self, val):
324 self._email = val.lower() if val else None
324 self._email = val.lower() if val else None
325
325
326 @property
326 @property
327 def full_name(self):
327 def full_name(self):
328 return '%s %s' % (self.name, self.lastname)
328 return '%s %s' % (self.name, self.lastname)
329
329
330 @property
330 @property
331 def full_name_or_username(self):
331 def full_name_or_username(self):
332 return ('%s %s' % (self.name, self.lastname)
332 return ('%s %s' % (self.name, self.lastname)
333 if (self.name and self.lastname) else self.username)
333 if (self.name and self.lastname) else self.username)
334
334
335 @property
335 @property
336 def full_contact(self):
336 def full_contact(self):
337 return '%s %s <%s>' % (self.name, self.lastname, self.email)
337 return '%s %s <%s>' % (self.name, self.lastname, self.email)
338
338
339 @property
339 @property
340 def short_contact(self):
340 def short_contact(self):
341 return '%s %s' % (self.name, self.lastname)
341 return '%s %s' % (self.name, self.lastname)
342
342
343 @property
343 @property
344 def is_admin(self):
344 def is_admin(self):
345 return self.admin
345 return self.admin
346
346
347 def __unicode__(self):
347 def __unicode__(self):
348 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
348 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
349 self.user_id, self.username)
349 self.user_id, self.username)
350
350
351 @classmethod
351 @classmethod
352 def get_by_username(cls, username, case_insensitive=False, cache=False):
352 def get_by_username(cls, username, case_insensitive=False, cache=False):
353 if case_insensitive:
353 if case_insensitive:
354 q = cls.query().filter(cls.username.ilike(username))
354 q = cls.query().filter(cls.username.ilike(username))
355 else:
355 else:
356 q = cls.query().filter(cls.username == username)
356 q = cls.query().filter(cls.username == username)
357
357
358 if cache:
358 if cache:
359 q = q.options(FromCache(
359 q = q.options(FromCache(
360 "sql_cache_short",
360 "sql_cache_short",
361 "get_user_%s" % _hash_key(username)
361 "get_user_%s" % _hash_key(username)
362 )
362 )
363 )
363 )
364 return q.scalar()
364 return q.scalar()
365
365
366 @classmethod
366 @classmethod
367 def get_by_api_key(cls, api_key, cache=False):
367 def get_by_api_key(cls, api_key, cache=False):
368 q = cls.query().filter(cls.api_key == api_key)
368 q = cls.query().filter(cls.api_key == api_key)
369
369
370 if cache:
370 if cache:
371 q = q.options(FromCache("sql_cache_short",
371 q = q.options(FromCache("sql_cache_short",
372 "get_api_key_%s" % api_key))
372 "get_api_key_%s" % api_key))
373 return q.scalar()
373 return q.scalar()
374
374
375 @classmethod
375 @classmethod
376 def get_by_email(cls, email, case_insensitive=False, cache=False):
376 def get_by_email(cls, email, case_insensitive=False, cache=False):
377 if case_insensitive:
377 if case_insensitive:
378 q = cls.query().filter(cls.email.ilike(email))
378 q = cls.query().filter(cls.email.ilike(email))
379 else:
379 else:
380 q = cls.query().filter(cls.email == email)
380 q = cls.query().filter(cls.email == email)
381
381
382 if cache:
382 if cache:
383 q = q.options(FromCache("sql_cache_short",
383 q = q.options(FromCache("sql_cache_short",
384 "get_api_key_%s" % email))
384 "get_api_key_%s" % email))
385 return q.scalar()
385 return q.scalar()
386
386
387 def update_lastlogin(self):
387 def update_lastlogin(self):
388 """Update user lastlogin"""
388 """Update user lastlogin"""
389 self.last_login = datetime.datetime.now()
389 self.last_login = datetime.datetime.now()
390 Session.add(self)
390 Session.add(self)
391 log.debug('updated user %s lastlogin' % self.username)
391 log.debug('updated user %s lastlogin' % self.username)
392
392
393 def __json__(self):
393 def __json__(self):
394 return dict(
394 return dict(
395 user_id=self.user_id,
395 user_id=self.user_id,
396 first_name=self.name,
396 first_name=self.name,
397 last_name=self.lastname,
397 last_name=self.lastname,
398 email=self.email,
398 email=self.email,
399 full_name=self.full_name,
399 full_name=self.full_name,
400 full_name_or_username=self.full_name_or_username,
400 full_name_or_username=self.full_name_or_username,
401 short_contact=self.short_contact,
401 short_contact=self.short_contact,
402 full_contact=self.full_contact
402 full_contact=self.full_contact
403 )
403 )
404
404
405
405
406 class UserLog(Base, BaseModel):
406 class UserLog(Base, BaseModel):
407 __tablename__ = 'user_logs'
407 __tablename__ = 'user_logs'
408 __table_args__ = (
408 __table_args__ = (
409 {'extend_existing': True, 'mysql_engine': 'InnoDB',
409 {'extend_existing': True, 'mysql_engine': 'InnoDB',
410 'mysql_charset': 'utf8'},
410 'mysql_charset': 'utf8'},
411 )
411 )
412 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
412 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
413 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
413 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
414 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
414 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
415 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
415 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
416 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
416 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
417 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
417 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
418 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
418 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
419
419
420 @property
420 @property
421 def action_as_day(self):
421 def action_as_day(self):
422 return datetime.date(*self.action_date.timetuple()[:3])
422 return datetime.date(*self.action_date.timetuple()[:3])
423
423
424 user = relationship('User')
424 user = relationship('User')
425 repository = relationship('Repository', cascade='')
425 repository = relationship('Repository', cascade='')
426
426
427
427
428 class UsersGroup(Base, BaseModel):
428 class UsersGroup(Base, BaseModel):
429 __tablename__ = 'users_groups'
429 __tablename__ = 'users_groups'
430 __table_args__ = (
430 __table_args__ = (
431 {'extend_existing': True, 'mysql_engine': 'InnoDB',
431 {'extend_existing': True, 'mysql_engine': 'InnoDB',
432 'mysql_charset': 'utf8'},
432 'mysql_charset': 'utf8'},
433 )
433 )
434
434
435 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
435 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
436 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
436 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
437 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
437 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
438
438
439 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
439 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
440 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
440 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
441 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
441 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
442
442
443 def __unicode__(self):
443 def __unicode__(self):
444 return u'<userGroup(%s)>' % (self.users_group_name)
444 return u'<userGroup(%s)>' % (self.users_group_name)
445
445
446 @classmethod
446 @classmethod
447 def get_by_group_name(cls, group_name, cache=False,
447 def get_by_group_name(cls, group_name, cache=False,
448 case_insensitive=False):
448 case_insensitive=False):
449 if case_insensitive:
449 if case_insensitive:
450 q = cls.query().filter(cls.users_group_name.ilike(group_name))
450 q = cls.query().filter(cls.users_group_name.ilike(group_name))
451 else:
451 else:
452 q = cls.query().filter(cls.users_group_name == group_name)
452 q = cls.query().filter(cls.users_group_name == group_name)
453 if cache:
453 if cache:
454 q = q.options(FromCache(
454 q = q.options(FromCache(
455 "sql_cache_short",
455 "sql_cache_short",
456 "get_user_%s" % _hash_key(group_name)
456 "get_user_%s" % _hash_key(group_name)
457 )
457 )
458 )
458 )
459 return q.scalar()
459 return q.scalar()
460
460
461 @classmethod
461 @classmethod
462 def get(cls, users_group_id, cache=False):
462 def get(cls, users_group_id, cache=False):
463 users_group = cls.query()
463 users_group = cls.query()
464 if cache:
464 if cache:
465 users_group = users_group.options(FromCache("sql_cache_short",
465 users_group = users_group.options(FromCache("sql_cache_short",
466 "get_users_group_%s" % users_group_id))
466 "get_users_group_%s" % users_group_id))
467 return users_group.get(users_group_id)
467 return users_group.get(users_group_id)
468
468
469
469
470 class UsersGroupMember(Base, BaseModel):
470 class UsersGroupMember(Base, BaseModel):
471 __tablename__ = 'users_groups_members'
471 __tablename__ = 'users_groups_members'
472 __table_args__ = (
472 __table_args__ = (
473 {'extend_existing': True, 'mysql_engine': 'InnoDB',
473 {'extend_existing': True, 'mysql_engine': 'InnoDB',
474 'mysql_charset': 'utf8'},
474 'mysql_charset': 'utf8'},
475 )
475 )
476
476
477 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
477 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
478 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
478 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
479 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
479 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
480
480
481 user = relationship('User', lazy='joined')
481 user = relationship('User', lazy='joined')
482 users_group = relationship('UsersGroup')
482 users_group = relationship('UsersGroup')
483
483
484 def __init__(self, gr_id='', u_id=''):
484 def __init__(self, gr_id='', u_id=''):
485 self.users_group_id = gr_id
485 self.users_group_id = gr_id
486 self.user_id = u_id
486 self.user_id = u_id
487
487
488
488
489 class Repository(Base, BaseModel):
489 class Repository(Base, BaseModel):
490 __tablename__ = 'repositories'
490 __tablename__ = 'repositories'
491 __table_args__ = (
491 __table_args__ = (
492 UniqueConstraint('repo_name'),
492 UniqueConstraint('repo_name'),
493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 'mysql_charset': 'utf8'},
494 'mysql_charset': 'utf8'},
495 )
495 )
496
496
497 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
497 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
498 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
499 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
499 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
500 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
500 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
502 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
502 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
503 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
503 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
504 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
504 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
505 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
505 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
506 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
506 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
507
507
508 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
508 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
509 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
509 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
510
510
511 user = relationship('User')
511 user = relationship('User')
512 fork = relationship('Repository', remote_side=repo_id)
512 fork = relationship('Repository', remote_side=repo_id)
513 group = relationship('RepoGroup')
513 group = relationship('RepoGroup')
514 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
514 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
515 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
515 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
516 stats = relationship('Statistics', cascade='all', uselist=False)
516 stats = relationship('Statistics', cascade='all', uselist=False)
517
517
518 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
518 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
519
519
520 logs = relationship('UserLog')
520 logs = relationship('UserLog')
521
521
522 def __unicode__(self):
522 def __unicode__(self):
523 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
523 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
524 self.repo_name)
524 self.repo_name)
525
525
526 @classmethod
526 @classmethod
527 def url_sep(cls):
527 def url_sep(cls):
528 return '/'
528 return '/'
529
529
530 @classmethod
530 @classmethod
531 def get_by_repo_name(cls, repo_name):
531 def get_by_repo_name(cls, repo_name):
532 q = Session.query(cls).filter(cls.repo_name == repo_name)
532 q = Session.query(cls).filter(cls.repo_name == repo_name)
533 q = q.options(joinedload(Repository.fork))\
533 q = q.options(joinedload(Repository.fork))\
534 .options(joinedload(Repository.user))\
534 .options(joinedload(Repository.user))\
535 .options(joinedload(Repository.group))
535 .options(joinedload(Repository.group))
536 return q.scalar()
536 return q.scalar()
537
537
538 @classmethod
538 @classmethod
539 def get_repo_forks(cls, repo_id):
539 def get_repo_forks(cls, repo_id):
540 return cls.query().filter(Repository.fork_id == repo_id)
540 return cls.query().filter(Repository.fork_id == repo_id)
541
541
542 @classmethod
542 @classmethod
543 def base_path(cls):
543 def base_path(cls):
544 """
544 """
545 Returns base path when all repos are stored
545 Returns base path when all repos are stored
546
546
547 :param cls:
547 :param cls:
548 """
548 """
549 q = Session.query(RhodeCodeUi)\
549 q = Session.query(RhodeCodeUi)\
550 .filter(RhodeCodeUi.ui_key == cls.url_sep())
550 .filter(RhodeCodeUi.ui_key == cls.url_sep())
551 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
551 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
552 return q.one().ui_value
552 return q.one().ui_value
553
553
554 @property
554 @property
555 def just_name(self):
555 def just_name(self):
556 return self.repo_name.split(Repository.url_sep())[-1]
556 return self.repo_name.split(Repository.url_sep())[-1]
557
557
558 @property
558 @property
559 def groups_with_parents(self):
559 def groups_with_parents(self):
560 groups = []
560 groups = []
561 if self.group is None:
561 if self.group is None:
562 return groups
562 return groups
563
563
564 cur_gr = self.group
564 cur_gr = self.group
565 groups.insert(0, cur_gr)
565 groups.insert(0, cur_gr)
566 while 1:
566 while 1:
567 gr = getattr(cur_gr, 'parent_group', None)
567 gr = getattr(cur_gr, 'parent_group', None)
568 cur_gr = cur_gr.parent_group
568 cur_gr = cur_gr.parent_group
569 if gr is None:
569 if gr is None:
570 break
570 break
571 groups.insert(0, gr)
571 groups.insert(0, gr)
572
572
573 return groups
573 return groups
574
574
575 @property
575 @property
576 def groups_and_repo(self):
576 def groups_and_repo(self):
577 return self.groups_with_parents, self.just_name
577 return self.groups_with_parents, self.just_name
578
578
579 @LazyProperty
579 @LazyProperty
580 def repo_path(self):
580 def repo_path(self):
581 """
581 """
582 Returns base full path for that repository means where it actually
582 Returns base full path for that repository means where it actually
583 exists on a filesystem
583 exists on a filesystem
584 """
584 """
585 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
585 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
586 Repository.url_sep())
586 Repository.url_sep())
587 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
587 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
588 return q.one().ui_value
588 return q.one().ui_value
589
589
590 @property
590 @property
591 def repo_full_path(self):
591 def repo_full_path(self):
592 p = [self.repo_path]
592 p = [self.repo_path]
593 # we need to split the name by / since this is how we store the
593 # we need to split the name by / since this is how we store the
594 # names in the database, but that eventually needs to be converted
594 # names in the database, but that eventually needs to be converted
595 # into a valid system path
595 # into a valid system path
596 p += self.repo_name.split(Repository.url_sep())
596 p += self.repo_name.split(Repository.url_sep())
597 return os.path.join(*p)
597 return os.path.join(*p)
598
598
599 def get_new_name(self, repo_name):
599 def get_new_name(self, repo_name):
600 """
600 """
601 returns new full repository name based on assigned group and new new
601 returns new full repository name based on assigned group and new new
602
602
603 :param group_name:
603 :param group_name:
604 """
604 """
605 path_prefix = self.group.full_path_splitted if self.group else []
605 path_prefix = self.group.full_path_splitted if self.group else []
606 return Repository.url_sep().join(path_prefix + [repo_name])
606 return Repository.url_sep().join(path_prefix + [repo_name])
607
607
608 @property
608 @property
609 def _ui(self):
609 def _ui(self):
610 """
610 """
611 Creates an db based ui object for this repository
611 Creates an db based ui object for this repository
612 """
612 """
613 from mercurial import ui
613 from mercurial import ui
614 from mercurial import config
614 from mercurial import config
615 baseui = ui.ui()
615 baseui = ui.ui()
616
616
617 #clean the baseui object
617 #clean the baseui object
618 baseui._ocfg = config.config()
618 baseui._ocfg = config.config()
619 baseui._ucfg = config.config()
619 baseui._ucfg = config.config()
620 baseui._tcfg = config.config()
620 baseui._tcfg = config.config()
621
621
622 ret = RhodeCodeUi.query()\
622 ret = RhodeCodeUi.query()\
623 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
623 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
624
624
625 hg_ui = ret
625 hg_ui = ret
626 for ui_ in hg_ui:
626 for ui_ in hg_ui:
627 if ui_.ui_active:
627 if ui_.ui_active:
628 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
628 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
629 ui_.ui_key, ui_.ui_value)
629 ui_.ui_key, ui_.ui_value)
630 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
630 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
631
631
632 return baseui
632 return baseui
633
633
634 @classmethod
634 @classmethod
635 def is_valid(cls, repo_name):
635 def is_valid(cls, repo_name):
636 """
636 """
637 returns True if given repo name is a valid filesystem repository
637 returns True if given repo name is a valid filesystem repository
638
638
639 :param cls:
639 :param cls:
640 :param repo_name:
640 :param repo_name:
641 """
641 """
642 from rhodecode.lib.utils import is_valid_repo
642 from rhodecode.lib.utils import is_valid_repo
643
643
644 return is_valid_repo(repo_name, cls.base_path())
644 return is_valid_repo(repo_name, cls.base_path())
645
645
646 #==========================================================================
646 #==========================================================================
647 # SCM PROPERTIES
647 # SCM PROPERTIES
648 #==========================================================================
648 #==========================================================================
649
649
650 def get_changeset(self, rev=None):
650 def get_changeset(self, rev=None):
651 return get_changeset_safe(self.scm_instance, rev)
651 return get_changeset_safe(self.scm_instance, rev)
652
652
653 @property
653 @property
654 def tip(self):
654 def tip(self):
655 return self.get_changeset('tip')
655 return self.get_changeset('tip')
656
656
657 @property
657 @property
658 def author(self):
658 def author(self):
659 return self.tip.author
659 return self.tip.author
660
660
661 @property
661 @property
662 def last_change(self):
662 def last_change(self):
663 return self.scm_instance.last_change
663 return self.scm_instance.last_change
664
664
665 def comments(self, revisions=None):
665 def comments(self, revisions=None):
666 """
666 """
667 Returns comments for this repository grouped by revisions
667 Returns comments for this repository grouped by revisions
668
668
669 :param revisions: filter query by revisions only
669 :param revisions: filter query by revisions only
670 """
670 """
671 cmts = ChangesetComment.query()\
671 cmts = ChangesetComment.query()\
672 .filter(ChangesetComment.repo == self)
672 .filter(ChangesetComment.repo == self)
673 if revisions:
673 if revisions:
674 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
674 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
675 grouped = defaultdict(list)
675 grouped = defaultdict(list)
676 for cmt in cmts.all():
676 for cmt in cmts.all():
677 grouped[cmt.revision].append(cmt)
677 grouped[cmt.revision].append(cmt)
678 return grouped
678 return grouped
679
679
680 def statuses(self, revisions=None):
680 def statuses(self, revisions=None):
681 """
681 """
682 Returns statuses for this repository
682 Returns statuses for this repository
683
683
684 :param revisions: list of revisions to get statuses for
684 :param revisions: list of revisions to get statuses for
685 :type revisions: list
685 :type revisions: list
686 """
686 """
687
687
688 statuses = ChangesetStatus.query()\
688 statuses = ChangesetStatus.query()\
689 .filter(ChangesetStatus.repo == self)\
689 .filter(ChangesetStatus.repo == self)\
690 .filter(ChangesetStatus.version == 0)
690 .filter(ChangesetStatus.version == 0)
691 if revisions:
691 if revisions:
692 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
692 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
693 grouped = {}
693 grouped = {}
694 for stat in statuses.all():
694 for stat in statuses.all():
695 grouped[stat.revision] = [str(stat.status), stat.status_lbl]
695 grouped[stat.revision] = [str(stat.status), stat.status_lbl]
696 return grouped
696 return grouped
697
697
698 #==========================================================================
698 #==========================================================================
699 # SCM CACHE INSTANCE
699 # SCM CACHE INSTANCE
700 #==========================================================================
700 #==========================================================================
701
701
702 @property
702 @property
703 def invalidate(self):
703 def invalidate(self):
704 return CacheInvalidation.invalidate(self.repo_name)
704 return CacheInvalidation.invalidate(self.repo_name)
705
705
706 def set_invalidate(self):
706 def set_invalidate(self):
707 """
707 """
708 set a cache for invalidation for this instance
708 set a cache for invalidation for this instance
709 """
709 """
710 CacheInvalidation.set_invalidate(self.repo_name)
710 CacheInvalidation.set_invalidate(self.repo_name)
711
711
712 @LazyProperty
712 @LazyProperty
713 def scm_instance(self):
713 def scm_instance(self):
714 return self.__get_instance()
714 return self.__get_instance()
715
715
716 @property
716 @property
717 def scm_instance_cached(self):
717 def scm_instance_cached(self):
718 @cache_region('long_term')
718 @cache_region('long_term')
719 def _c(repo_name):
719 def _c(repo_name):
720 return self.__get_instance()
720 return self.__get_instance()
721 rn = self.repo_name
721 rn = self.repo_name
722 log.debug('Getting cached instance of repo')
722 log.debug('Getting cached instance of repo')
723 inv = self.invalidate
723 inv = self.invalidate
724 if inv is not None:
724 if inv is not None:
725 region_invalidate(_c, None, rn)
725 region_invalidate(_c, None, rn)
726 # update our cache
726 # update our cache
727 CacheInvalidation.set_valid(inv.cache_key)
727 CacheInvalidation.set_valid(inv.cache_key)
728 return _c(rn)
728 return _c(rn)
729
729
730 def __get_instance(self):
730 def __get_instance(self):
731 repo_full_path = self.repo_full_path
731 repo_full_path = self.repo_full_path
732 try:
732 try:
733 alias = get_scm(repo_full_path)[0]
733 alias = get_scm(repo_full_path)[0]
734 log.debug('Creating instance of %s repository' % alias)
734 log.debug('Creating instance of %s repository' % alias)
735 backend = get_backend(alias)
735 backend = get_backend(alias)
736 except VCSError:
736 except VCSError:
737 log.error(traceback.format_exc())
737 log.error(traceback.format_exc())
738 log.error('Perhaps this repository is in db and not in '
738 log.error('Perhaps this repository is in db and not in '
739 'filesystem run rescan repositories with '
739 'filesystem run rescan repositories with '
740 '"destroy old data " option from admin panel')
740 '"destroy old data " option from admin panel')
741 return
741 return
742
742
743 if alias == 'hg':
743 if alias == 'hg':
744
744
745 repo = backend(safe_str(repo_full_path), create=False,
745 repo = backend(safe_str(repo_full_path), create=False,
746 baseui=self._ui)
746 baseui=self._ui)
747 # skip hidden web repository
747 # skip hidden web repository
748 if repo._get_hidden():
748 if repo._get_hidden():
749 return
749 return
750 else:
750 else:
751 repo = backend(repo_full_path, create=False)
751 repo = backend(repo_full_path, create=False)
752
752
753 return repo
753 return repo
754
754
755
755
756 class RepoGroup(Base, BaseModel):
756 class RepoGroup(Base, BaseModel):
757 __tablename__ = 'groups'
757 __tablename__ = 'groups'
758 __table_args__ = (
758 __table_args__ = (
759 UniqueConstraint('group_name', 'group_parent_id'),
759 UniqueConstraint('group_name', 'group_parent_id'),
760 CheckConstraint('group_id != group_parent_id'),
760 CheckConstraint('group_id != group_parent_id'),
761 {'extend_existing': True, 'mysql_engine': 'InnoDB',
761 {'extend_existing': True, 'mysql_engine': 'InnoDB',
762 'mysql_charset': 'utf8'},
762 'mysql_charset': 'utf8'},
763 )
763 )
764 __mapper_args__ = {'order_by': 'group_name'}
764 __mapper_args__ = {'order_by': 'group_name'}
765
765
766 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
766 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
767 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
767 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
768 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
768 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
769 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
769 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
770
770
771 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
771 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
772 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
772 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
773
773
774 parent_group = relationship('RepoGroup', remote_side=group_id)
774 parent_group = relationship('RepoGroup', remote_side=group_id)
775
775
776 def __init__(self, group_name='', parent_group=None):
776 def __init__(self, group_name='', parent_group=None):
777 self.group_name = group_name
777 self.group_name = group_name
778 self.parent_group = parent_group
778 self.parent_group = parent_group
779
779
780 def __unicode__(self):
780 def __unicode__(self):
781 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
781 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
782 self.group_name)
782 self.group_name)
783
783
784 @classmethod
784 @classmethod
785 def groups_choices(cls):
785 def groups_choices(cls):
786 from webhelpers.html import literal as _literal
786 from webhelpers.html import literal as _literal
787 repo_groups = [('', '')]
787 repo_groups = [('', '')]
788 sep = ' &raquo; '
788 sep = ' &raquo; '
789 _name = lambda k: _literal(sep.join(k))
789 _name = lambda k: _literal(sep.join(k))
790
790
791 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
791 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
792 for x in cls.query().all()])
792 for x in cls.query().all()])
793
793
794 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
794 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
795 return repo_groups
795 return repo_groups
796
796
797 @classmethod
797 @classmethod
798 def url_sep(cls):
798 def url_sep(cls):
799 return '/'
799 return '/'
800
800
801 @classmethod
801 @classmethod
802 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
802 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
803 if case_insensitive:
803 if case_insensitive:
804 gr = cls.query()\
804 gr = cls.query()\
805 .filter(cls.group_name.ilike(group_name))
805 .filter(cls.group_name.ilike(group_name))
806 else:
806 else:
807 gr = cls.query()\
807 gr = cls.query()\
808 .filter(cls.group_name == group_name)
808 .filter(cls.group_name == group_name)
809 if cache:
809 if cache:
810 gr = gr.options(FromCache(
810 gr = gr.options(FromCache(
811 "sql_cache_short",
811 "sql_cache_short",
812 "get_group_%s" % _hash_key(group_name)
812 "get_group_%s" % _hash_key(group_name)
813 )
813 )
814 )
814 )
815 return gr.scalar()
815 return gr.scalar()
816
816
817 @property
817 @property
818 def parents(self):
818 def parents(self):
819 parents_recursion_limit = 5
819 parents_recursion_limit = 5
820 groups = []
820 groups = []
821 if self.parent_group is None:
821 if self.parent_group is None:
822 return groups
822 return groups
823 cur_gr = self.parent_group
823 cur_gr = self.parent_group
824 groups.insert(0, cur_gr)
824 groups.insert(0, cur_gr)
825 cnt = 0
825 cnt = 0
826 while 1:
826 while 1:
827 cnt += 1
827 cnt += 1
828 gr = getattr(cur_gr, 'parent_group', None)
828 gr = getattr(cur_gr, 'parent_group', None)
829 cur_gr = cur_gr.parent_group
829 cur_gr = cur_gr.parent_group
830 if gr is None:
830 if gr is None:
831 break
831 break
832 if cnt == parents_recursion_limit:
832 if cnt == parents_recursion_limit:
833 # this will prevent accidental infinit loops
833 # this will prevent accidental infinit loops
834 log.error('group nested more than %s' %
834 log.error('group nested more than %s' %
835 parents_recursion_limit)
835 parents_recursion_limit)
836 break
836 break
837
837
838 groups.insert(0, gr)
838 groups.insert(0, gr)
839 return groups
839 return groups
840
840
841 @property
841 @property
842 def children(self):
842 def children(self):
843 return RepoGroup.query().filter(RepoGroup.parent_group == self)
843 return RepoGroup.query().filter(RepoGroup.parent_group == self)
844
844
845 @property
845 @property
846 def name(self):
846 def name(self):
847 return self.group_name.split(RepoGroup.url_sep())[-1]
847 return self.group_name.split(RepoGroup.url_sep())[-1]
848
848
849 @property
849 @property
850 def full_path(self):
850 def full_path(self):
851 return self.group_name
851 return self.group_name
852
852
853 @property
853 @property
854 def full_path_splitted(self):
854 def full_path_splitted(self):
855 return self.group_name.split(RepoGroup.url_sep())
855 return self.group_name.split(RepoGroup.url_sep())
856
856
857 @property
857 @property
858 def repositories(self):
858 def repositories(self):
859 return Repository.query()\
859 return Repository.query()\
860 .filter(Repository.group == self)\
860 .filter(Repository.group == self)\
861 .order_by(Repository.repo_name)
861 .order_by(Repository.repo_name)
862
862
863 @property
863 @property
864 def repositories_recursive_count(self):
864 def repositories_recursive_count(self):
865 cnt = self.repositories.count()
865 cnt = self.repositories.count()
866
866
867 def children_count(group):
867 def children_count(group):
868 cnt = 0
868 cnt = 0
869 for child in group.children:
869 for child in group.children:
870 cnt += child.repositories.count()
870 cnt += child.repositories.count()
871 cnt += children_count(child)
871 cnt += children_count(child)
872 return cnt
872 return cnt
873
873
874 return cnt + children_count(self)
874 return cnt + children_count(self)
875
875
876 def get_new_name(self, group_name):
876 def get_new_name(self, group_name):
877 """
877 """
878 returns new full group name based on parent and new name
878 returns new full group name based on parent and new name
879
879
880 :param group_name:
880 :param group_name:
881 """
881 """
882 path_prefix = (self.parent_group.full_path_splitted if
882 path_prefix = (self.parent_group.full_path_splitted if
883 self.parent_group else [])
883 self.parent_group else [])
884 return RepoGroup.url_sep().join(path_prefix + [group_name])
884 return RepoGroup.url_sep().join(path_prefix + [group_name])
885
885
886
886
887 class Permission(Base, BaseModel):
887 class Permission(Base, BaseModel):
888 __tablename__ = 'permissions'
888 __tablename__ = 'permissions'
889 __table_args__ = (
889 __table_args__ = (
890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
890 {'extend_existing': True, 'mysql_engine': 'InnoDB',
891 'mysql_charset': 'utf8'},
891 'mysql_charset': 'utf8'},
892 )
892 )
893 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
893 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
894 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
894 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
895 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
895 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
896
896
897 def __unicode__(self):
897 def __unicode__(self):
898 return u"<%s('%s:%s')>" % (
898 return u"<%s('%s:%s')>" % (
899 self.__class__.__name__, self.permission_id, self.permission_name
899 self.__class__.__name__, self.permission_id, self.permission_name
900 )
900 )
901
901
902 @classmethod
902 @classmethod
903 def get_by_key(cls, key):
903 def get_by_key(cls, key):
904 return cls.query().filter(cls.permission_name == key).scalar()
904 return cls.query().filter(cls.permission_name == key).scalar()
905
905
906 @classmethod
906 @classmethod
907 def get_default_perms(cls, default_user_id):
907 def get_default_perms(cls, default_user_id):
908 q = Session.query(UserRepoToPerm, Repository, cls)\
908 q = Session.query(UserRepoToPerm, Repository, cls)\
909 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
909 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
910 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
910 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
911 .filter(UserRepoToPerm.user_id == default_user_id)
911 .filter(UserRepoToPerm.user_id == default_user_id)
912
912
913 return q.all()
913 return q.all()
914
914
915 @classmethod
915 @classmethod
916 def get_default_group_perms(cls, default_user_id):
916 def get_default_group_perms(cls, default_user_id):
917 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
917 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
918 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
918 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
919 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
919 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
920 .filter(UserRepoGroupToPerm.user_id == default_user_id)
920 .filter(UserRepoGroupToPerm.user_id == default_user_id)
921
921
922 return q.all()
922 return q.all()
923
923
924
924
925 class UserRepoToPerm(Base, BaseModel):
925 class UserRepoToPerm(Base, BaseModel):
926 __tablename__ = 'repo_to_perm'
926 __tablename__ = 'repo_to_perm'
927 __table_args__ = (
927 __table_args__ = (
928 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
928 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
929 {'extend_existing': True, 'mysql_engine': 'InnoDB',
930 'mysql_charset': 'utf8'}
930 'mysql_charset': 'utf8'}
931 )
931 )
932 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
932 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
933 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
933 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
934 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
934 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
935 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
935 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
936
936
937 user = relationship('User')
937 user = relationship('User')
938 repository = relationship('Repository')
938 repository = relationship('Repository')
939 permission = relationship('Permission')
939 permission = relationship('Permission')
940
940
941 @classmethod
941 @classmethod
942 def create(cls, user, repository, permission):
942 def create(cls, user, repository, permission):
943 n = cls()
943 n = cls()
944 n.user = user
944 n.user = user
945 n.repository = repository
945 n.repository = repository
946 n.permission = permission
946 n.permission = permission
947 Session.add(n)
947 Session.add(n)
948 return n
948 return n
949
949
950 def __unicode__(self):
950 def __unicode__(self):
951 return u'<user:%s => %s >' % (self.user, self.repository)
951 return u'<user:%s => %s >' % (self.user, self.repository)
952
952
953
953
954 class UserToPerm(Base, BaseModel):
954 class UserToPerm(Base, BaseModel):
955 __tablename__ = 'user_to_perm'
955 __tablename__ = 'user_to_perm'
956 __table_args__ = (
956 __table_args__ = (
957 UniqueConstraint('user_id', 'permission_id'),
957 UniqueConstraint('user_id', 'permission_id'),
958 {'extend_existing': True, 'mysql_engine': 'InnoDB',
958 {'extend_existing': True, 'mysql_engine': 'InnoDB',
959 'mysql_charset': 'utf8'}
959 'mysql_charset': 'utf8'}
960 )
960 )
961 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
961 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
962 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
963 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
963 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
964
964
965 user = relationship('User')
965 user = relationship('User')
966 permission = relationship('Permission', lazy='joined')
966 permission = relationship('Permission', lazy='joined')
967
967
968
968
969 class UsersGroupRepoToPerm(Base, BaseModel):
969 class UsersGroupRepoToPerm(Base, BaseModel):
970 __tablename__ = 'users_group_repo_to_perm'
970 __tablename__ = 'users_group_repo_to_perm'
971 __table_args__ = (
971 __table_args__ = (
972 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
972 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
973 {'extend_existing': True, 'mysql_engine': 'InnoDB',
974 'mysql_charset': 'utf8'}
974 'mysql_charset': 'utf8'}
975 )
975 )
976 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
976 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
977 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
977 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
978 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
978 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
979 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
979 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
980
980
981 users_group = relationship('UsersGroup')
981 users_group = relationship('UsersGroup')
982 permission = relationship('Permission')
982 permission = relationship('Permission')
983 repository = relationship('Repository')
983 repository = relationship('Repository')
984
984
985 @classmethod
985 @classmethod
986 def create(cls, users_group, repository, permission):
986 def create(cls, users_group, repository, permission):
987 n = cls()
987 n = cls()
988 n.users_group = users_group
988 n.users_group = users_group
989 n.repository = repository
989 n.repository = repository
990 n.permission = permission
990 n.permission = permission
991 Session.add(n)
991 Session.add(n)
992 return n
992 return n
993
993
994 def __unicode__(self):
994 def __unicode__(self):
995 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
995 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
996
996
997
997
998 class UsersGroupToPerm(Base, BaseModel):
998 class UsersGroupToPerm(Base, BaseModel):
999 __tablename__ = 'users_group_to_perm'
999 __tablename__ = 'users_group_to_perm'
1000 __table_args__ = (
1000 __table_args__ = (
1001 UniqueConstraint('users_group_id', 'permission_id',),
1001 UniqueConstraint('users_group_id', 'permission_id',),
1002 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1002 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1003 'mysql_charset': 'utf8'}
1003 'mysql_charset': 'utf8'}
1004 )
1004 )
1005 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1005 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1006 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1006 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1007 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1007 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1008
1008
1009 users_group = relationship('UsersGroup')
1009 users_group = relationship('UsersGroup')
1010 permission = relationship('Permission')
1010 permission = relationship('Permission')
1011
1011
1012
1012
1013 class UserRepoGroupToPerm(Base, BaseModel):
1013 class UserRepoGroupToPerm(Base, BaseModel):
1014 __tablename__ = 'user_repo_group_to_perm'
1014 __tablename__ = 'user_repo_group_to_perm'
1015 __table_args__ = (
1015 __table_args__ = (
1016 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1016 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1017 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1018 'mysql_charset': 'utf8'}
1018 'mysql_charset': 'utf8'}
1019 )
1019 )
1020
1020
1021 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1021 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1022 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1022 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1023 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1023 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1024 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1024 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1025
1025
1026 user = relationship('User')
1026 user = relationship('User')
1027 group = relationship('RepoGroup')
1027 group = relationship('RepoGroup')
1028 permission = relationship('Permission')
1028 permission = relationship('Permission')
1029
1029
1030
1030
1031 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1031 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1032 __tablename__ = 'users_group_repo_group_to_perm'
1032 __tablename__ = 'users_group_repo_group_to_perm'
1033 __table_args__ = (
1033 __table_args__ = (
1034 UniqueConstraint('users_group_id', 'group_id'),
1034 UniqueConstraint('users_group_id', 'group_id'),
1035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1035 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1036 'mysql_charset': 'utf8'}
1036 'mysql_charset': 'utf8'}
1037 )
1037 )
1038
1038
1039 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1039 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1040 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1040 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1041 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1041 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1042 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1042 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1043
1043
1044 users_group = relationship('UsersGroup')
1044 users_group = relationship('UsersGroup')
1045 permission = relationship('Permission')
1045 permission = relationship('Permission')
1046 group = relationship('RepoGroup')
1046 group = relationship('RepoGroup')
1047
1047
1048
1048
1049 class Statistics(Base, BaseModel):
1049 class Statistics(Base, BaseModel):
1050 __tablename__ = 'statistics'
1050 __tablename__ = 'statistics'
1051 __table_args__ = (
1051 __table_args__ = (
1052 UniqueConstraint('repository_id'),
1052 UniqueConstraint('repository_id'),
1053 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1053 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1054 'mysql_charset': 'utf8'}
1054 'mysql_charset': 'utf8'}
1055 )
1055 )
1056 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1056 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1057 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1057 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1058 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1058 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1059 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1059 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1060 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1060 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1061 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1061 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1062
1062
1063 repository = relationship('Repository', single_parent=True)
1063 repository = relationship('Repository', single_parent=True)
1064
1064
1065
1065
1066 class UserFollowing(Base, BaseModel):
1066 class UserFollowing(Base, BaseModel):
1067 __tablename__ = 'user_followings'
1067 __tablename__ = 'user_followings'
1068 __table_args__ = (
1068 __table_args__ = (
1069 UniqueConstraint('user_id', 'follows_repository_id'),
1069 UniqueConstraint('user_id', 'follows_repository_id'),
1070 UniqueConstraint('user_id', 'follows_user_id'),
1070 UniqueConstraint('user_id', 'follows_user_id'),
1071 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1071 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1072 'mysql_charset': 'utf8'}
1072 'mysql_charset': 'utf8'}
1073 )
1073 )
1074
1074
1075 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1075 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1076 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1076 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1077 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1077 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1078 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1078 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1079 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1079 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1080
1080
1081 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1081 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1082
1082
1083 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1083 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1084 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1084 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1085
1085
1086 @classmethod
1086 @classmethod
1087 def get_repo_followers(cls, repo_id):
1087 def get_repo_followers(cls, repo_id):
1088 return cls.query().filter(cls.follows_repo_id == repo_id)
1088 return cls.query().filter(cls.follows_repo_id == repo_id)
1089
1089
1090
1090
1091 class CacheInvalidation(Base, BaseModel):
1091 class CacheInvalidation(Base, BaseModel):
1092 __tablename__ = 'cache_invalidation'
1092 __tablename__ = 'cache_invalidation'
1093 __table_args__ = (
1093 __table_args__ = (
1094 UniqueConstraint('cache_key'),
1094 UniqueConstraint('cache_key'),
1095 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1095 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1096 'mysql_charset': 'utf8'},
1096 'mysql_charset': 'utf8'},
1097 )
1097 )
1098 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1098 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1099 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1099 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1100 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1100 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1101 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1101 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1102
1102
1103 def __init__(self, cache_key, cache_args=''):
1103 def __init__(self, cache_key, cache_args=''):
1104 self.cache_key = cache_key
1104 self.cache_key = cache_key
1105 self.cache_args = cache_args
1105 self.cache_args = cache_args
1106 self.cache_active = False
1106 self.cache_active = False
1107
1107
1108 def __unicode__(self):
1108 def __unicode__(self):
1109 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1109 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1110 self.cache_id, self.cache_key)
1110 self.cache_id, self.cache_key)
1111 @classmethod
1111 @classmethod
1112 def clear_cache(cls):
1112 def clear_cache(cls):
1113 cls.query().delete()
1113 cls.query().delete()
1114
1114
1115 @classmethod
1115 @classmethod
1116 def _get_key(cls, key):
1116 def _get_key(cls, key):
1117 """
1117 """
1118 Wrapper for generating a key, together with a prefix
1118 Wrapper for generating a key, together with a prefix
1119
1119
1120 :param key:
1120 :param key:
1121 """
1121 """
1122 import rhodecode
1122 import rhodecode
1123 prefix = ''
1123 prefix = ''
1124 iid = rhodecode.CONFIG.get('instance_id')
1124 iid = rhodecode.CONFIG.get('instance_id')
1125 if iid:
1125 if iid:
1126 prefix = iid
1126 prefix = iid
1127 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1127 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1128
1128
1129 @classmethod
1129 @classmethod
1130 def get_by_key(cls, key):
1130 def get_by_key(cls, key):
1131 return cls.query().filter(cls.cache_key == key).scalar()
1131 return cls.query().filter(cls.cache_key == key).scalar()
1132
1132
1133 @classmethod
1133 @classmethod
1134 def _get_or_create_key(cls, key, prefix, org_key):
1134 def _get_or_create_key(cls, key, prefix, org_key):
1135 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1135 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1136 if not inv_obj:
1136 if not inv_obj:
1137 try:
1137 try:
1138 inv_obj = CacheInvalidation(key, org_key)
1138 inv_obj = CacheInvalidation(key, org_key)
1139 Session.add(inv_obj)
1139 Session.add(inv_obj)
1140 Session.commit()
1140 Session.commit()
1141 except Exception:
1141 except Exception:
1142 log.error(traceback.format_exc())
1142 log.error(traceback.format_exc())
1143 Session.rollback()
1143 Session.rollback()
1144 return inv_obj
1144 return inv_obj
1145
1145
1146 @classmethod
1146 @classmethod
1147 def invalidate(cls, key):
1147 def invalidate(cls, key):
1148 """
1148 """
1149 Returns Invalidation object if this given key should be invalidated
1149 Returns Invalidation object if this given key should be invalidated
1150 None otherwise. `cache_active = False` means that this cache
1150 None otherwise. `cache_active = False` means that this cache
1151 state is not valid and needs to be invalidated
1151 state is not valid and needs to be invalidated
1152
1152
1153 :param key:
1153 :param key:
1154 """
1154 """
1155
1155
1156 key, _prefix, _org_key = cls._get_key(key)
1156 key, _prefix, _org_key = cls._get_key(key)
1157 inv = cls._get_or_create_key(key, _prefix, _org_key)
1157 inv = cls._get_or_create_key(key, _prefix, _org_key)
1158
1158
1159 if inv and inv.cache_active is False:
1159 if inv and inv.cache_active is False:
1160 return inv
1160 return inv
1161
1161
1162 @classmethod
1162 @classmethod
1163 def set_invalidate(cls, key):
1163 def set_invalidate(cls, key):
1164 """
1164 """
1165 Mark this Cache key for invalidation
1165 Mark this Cache key for invalidation
1166
1166
1167 :param key:
1167 :param key:
1168 """
1168 """
1169
1169
1170 key, _prefix, _org_key = cls._get_key(key)
1170 key, _prefix, _org_key = cls._get_key(key)
1171 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1171 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1172 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1172 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1173 _org_key))
1173 _org_key))
1174 try:
1174 try:
1175 for inv_obj in inv_objs:
1175 for inv_obj in inv_objs:
1176 if inv_obj:
1176 if inv_obj:
1177 inv_obj.cache_active = False
1177 inv_obj.cache_active = False
1178
1178
1179 Session.add(inv_obj)
1179 Session.add(inv_obj)
1180 Session.commit()
1180 Session.commit()
1181 except Exception:
1181 except Exception:
1182 log.error(traceback.format_exc())
1182 log.error(traceback.format_exc())
1183 Session.rollback()
1183 Session.rollback()
1184
1184
1185 @classmethod
1185 @classmethod
1186 def set_valid(cls, key):
1186 def set_valid(cls, key):
1187 """
1187 """
1188 Mark this cache key as active and currently cached
1188 Mark this cache key as active and currently cached
1189
1189
1190 :param key:
1190 :param key:
1191 """
1191 """
1192 inv_obj = cls.get_by_key(key)
1192 inv_obj = cls.get_by_key(key)
1193 inv_obj.cache_active = True
1193 inv_obj.cache_active = True
1194 Session.add(inv_obj)
1194 Session.add(inv_obj)
1195 Session.commit()
1195 Session.commit()
1196
1196
1197
1197
1198 class ChangesetComment(Base, BaseModel):
1198 class ChangesetComment(Base, BaseModel):
1199 __tablename__ = 'changeset_comments'
1199 __tablename__ = 'changeset_comments'
1200 __table_args__ = (
1200 __table_args__ = (
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1201 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1202 'mysql_charset': 'utf8'},
1202 'mysql_charset': 'utf8'},
1203 )
1203 )
1204 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1204 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1205 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1205 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1206 revision = Column('revision', String(40), nullable=False)
1206 revision = Column('revision', String(40), nullable=False)
1207 line_no = Column('line_no', Unicode(10), nullable=True)
1207 line_no = Column('line_no', Unicode(10), nullable=True)
1208 f_path = Column('f_path', Unicode(1000), nullable=True)
1208 f_path = Column('f_path', Unicode(1000), nullable=True)
1209 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1209 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1210 text = Column('text', Unicode(25000), nullable=False)
1210 text = Column('text', Unicode(25000), nullable=False)
1211 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1211 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1212
1212
1213 author = relationship('User', lazy='joined')
1213 author = relationship('User', lazy='joined')
1214 repo = relationship('Repository')
1214 repo = relationship('Repository')
1215 status_change = relationship('ChangesetStatus', uselist=False)
1215 status_change = relationship('ChangesetStatus', uselist=False)
1216
1216
1217 @classmethod
1217 @classmethod
1218 def get_users(cls, revision):
1218 def get_users(cls, revision):
1219 """
1219 """
1220 Returns user associated with this changesetComment. ie those
1220 Returns user associated with this changesetComment. ie those
1221 who actually commented
1221 who actually commented
1222
1222
1223 :param cls:
1223 :param cls:
1224 :param revision:
1224 :param revision:
1225 """
1225 """
1226 return Session.query(User)\
1226 return Session.query(User)\
1227 .filter(cls.revision == revision)\
1227 .filter(cls.revision == revision)\
1228 .join(ChangesetComment.author).all()
1228 .join(ChangesetComment.author).all()
1229
1229
1230
1230
1231 class ChangesetStatus(Base, BaseModel):
1231 class ChangesetStatus(Base, BaseModel):
1232 __tablename__ = 'changeset_statuses'
1232 __tablename__ = 'changeset_statuses'
1233 __table_args__ = (
1233 __table_args__ = (
1234 UniqueConstraint('repo_id', 'revision', 'version'),
1234 UniqueConstraint('repo_id', 'revision', 'version'),
1235 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1235 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1236 'mysql_charset': 'utf8'}
1236 'mysql_charset': 'utf8'}
1237 )
1237 )
1238
1238
1239 STATUSES = [
1239 STATUSES = [
1240 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1240 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1241 ('approved', _("Approved")),
1241 ('approved', _("Approved")),
1242 ('rejected', _("Rejected")),
1242 ('rejected', _("Rejected")),
1243 ('under_review', _("Under Review")),
1243 ('under_review', _("Under Review")),
1244 ]
1244 ]
1245 DEFAULT = STATUSES[0][0]
1245 DEFAULT = STATUSES[0][0]
1246
1246
1247 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1247 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1248 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1248 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1249 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1249 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1250 revision = Column('revision', String(40), nullable=False)
1250 revision = Column('revision', String(40), nullable=False)
1251 status = Column('status', String(128), nullable=False, default=DEFAULT)
1251 status = Column('status', String(128), nullable=False, default=DEFAULT)
1252 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1252 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1253 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1253 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1254 version = Column('version', Integer(), nullable=False, default=0)
1254 version = Column('version', Integer(), nullable=False, default=0)
1255 author = relationship('User', lazy='joined')
1255 author = relationship('User', lazy='joined')
1256 repo = relationship('Repository')
1256 repo = relationship('Repository')
1257 comment = relationship('ChangesetComment', lazy='joined')
1257 comment = relationship('ChangesetComment', lazy='joined')
1258
1258
1259 @classmethod
1260 def get_status_lbl(cls, value):
1261 return dict(cls.STATUSES).get(value)
1262
1259 @property
1263 @property
1260 def status_lbl(self):
1264 def status_lbl(self):
1261 return dict(self.STATUSES).get(self.status)
1265 return ChangesetStatus.get_status_lbl(self.status)
1262
1266
1263
1267
1264 class Notification(Base, BaseModel):
1268 class Notification(Base, BaseModel):
1265 __tablename__ = 'notifications'
1269 __tablename__ = 'notifications'
1266 __table_args__ = (
1270 __table_args__ = (
1267 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1271 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1268 'mysql_charset': 'utf8'},
1272 'mysql_charset': 'utf8'},
1269 )
1273 )
1270
1274
1271 TYPE_CHANGESET_COMMENT = u'cs_comment'
1275 TYPE_CHANGESET_COMMENT = u'cs_comment'
1272 TYPE_MESSAGE = u'message'
1276 TYPE_MESSAGE = u'message'
1273 TYPE_MENTION = u'mention'
1277 TYPE_MENTION = u'mention'
1274 TYPE_REGISTRATION = u'registration'
1278 TYPE_REGISTRATION = u'registration'
1275 TYPE_PULL_REQUEST = u'pull_request'
1279 TYPE_PULL_REQUEST = u'pull_request'
1276
1280
1277 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1281 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1278 subject = Column('subject', Unicode(512), nullable=True)
1282 subject = Column('subject', Unicode(512), nullable=True)
1279 body = Column('body', Unicode(50000), nullable=True)
1283 body = Column('body', Unicode(50000), nullable=True)
1280 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1284 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1281 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1285 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1282 type_ = Column('type', Unicode(256))
1286 type_ = Column('type', Unicode(256))
1283
1287
1284 created_by_user = relationship('User')
1288 created_by_user = relationship('User')
1285 notifications_to_users = relationship('UserNotification', lazy='joined',
1289 notifications_to_users = relationship('UserNotification', lazy='joined',
1286 cascade="all, delete, delete-orphan")
1290 cascade="all, delete, delete-orphan")
1287
1291
1288 @property
1292 @property
1289 def recipients(self):
1293 def recipients(self):
1290 return [x.user for x in UserNotification.query()\
1294 return [x.user for x in UserNotification.query()\
1291 .filter(UserNotification.notification == self)\
1295 .filter(UserNotification.notification == self)\
1292 .order_by(UserNotification.user).all()]
1296 .order_by(UserNotification.user).all()]
1293
1297
1294 @classmethod
1298 @classmethod
1295 def create(cls, created_by, subject, body, recipients, type_=None):
1299 def create(cls, created_by, subject, body, recipients, type_=None):
1296 if type_ is None:
1300 if type_ is None:
1297 type_ = Notification.TYPE_MESSAGE
1301 type_ = Notification.TYPE_MESSAGE
1298
1302
1299 notification = cls()
1303 notification = cls()
1300 notification.created_by_user = created_by
1304 notification.created_by_user = created_by
1301 notification.subject = subject
1305 notification.subject = subject
1302 notification.body = body
1306 notification.body = body
1303 notification.type_ = type_
1307 notification.type_ = type_
1304 notification.created_on = datetime.datetime.now()
1308 notification.created_on = datetime.datetime.now()
1305
1309
1306 for u in recipients:
1310 for u in recipients:
1307 assoc = UserNotification()
1311 assoc = UserNotification()
1308 assoc.notification = notification
1312 assoc.notification = notification
1309 u.notifications.append(assoc)
1313 u.notifications.append(assoc)
1310 Session.add(notification)
1314 Session.add(notification)
1311 return notification
1315 return notification
1312
1316
1313 @property
1317 @property
1314 def description(self):
1318 def description(self):
1315 from rhodecode.model.notification import NotificationModel
1319 from rhodecode.model.notification import NotificationModel
1316 return NotificationModel().make_description(self)
1320 return NotificationModel().make_description(self)
1317
1321
1318
1322
1319 class UserNotification(Base, BaseModel):
1323 class UserNotification(Base, BaseModel):
1320 __tablename__ = 'user_to_notification'
1324 __tablename__ = 'user_to_notification'
1321 __table_args__ = (
1325 __table_args__ = (
1322 UniqueConstraint('user_id', 'notification_id'),
1326 UniqueConstraint('user_id', 'notification_id'),
1323 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1327 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1324 'mysql_charset': 'utf8'}
1328 'mysql_charset': 'utf8'}
1325 )
1329 )
1326 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1330 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1327 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1331 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1328 read = Column('read', Boolean, default=False)
1332 read = Column('read', Boolean, default=False)
1329 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1333 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1330
1334
1331 user = relationship('User', lazy="joined")
1335 user = relationship('User', lazy="joined")
1332 notification = relationship('Notification', lazy="joined",
1336 notification = relationship('Notification', lazy="joined",
1333 order_by=lambda: Notification.created_on.desc(),)
1337 order_by=lambda: Notification.created_on.desc(),)
1334
1338
1335 def mark_as_read(self):
1339 def mark_as_read(self):
1336 self.read = True
1340 self.read = True
1337 Session.add(self)
1341 Session.add(self)
1338
1342
1339
1343
1340 class DbMigrateVersion(Base, BaseModel):
1344 class DbMigrateVersion(Base, BaseModel):
1341 __tablename__ = 'db_migrate_version'
1345 __tablename__ = 'db_migrate_version'
1342 __table_args__ = (
1346 __table_args__ = (
1343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1347 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1344 'mysql_charset': 'utf8'},
1348 'mysql_charset': 'utf8'},
1345 )
1349 )
1346 repository_id = Column('repository_id', String(250), primary_key=True)
1350 repository_id = Column('repository_id', String(250), primary_key=True)
1347 repository_path = Column('repository_path', Text)
1351 repository_path = Column('repository_path', Text)
1348 version = Column('version', Integer)
1352 version = Column('version', Integer)
@@ -1,226 +1,227 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.notification
3 rhodecode.model.notification
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Model for notifications
6 Model for notifications
7
7
8
8
9 :created_on: Nov 20, 2011
9 :created_on: Nov 20, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30 import datetime
30 import datetime
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode
34 import rhodecode
35 from rhodecode.config.conf import DATETIME_FORMAT
35 from rhodecode.config.conf import DATETIME_FORMAT
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import Notification, User, UserNotification
38 from rhodecode.model.db import Notification, User, UserNotification
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 class NotificationModel(BaseModel):
43 class NotificationModel(BaseModel):
44
44
45 def __get_user(self, user):
45 def __get_user(self, user):
46 return self._get_instance(User, user, callback=User.get_by_username)
46 return self._get_instance(User, user, callback=User.get_by_username)
47
47
48 def __get_notification(self, notification):
48 def __get_notification(self, notification):
49 if isinstance(notification, Notification):
49 if isinstance(notification, Notification):
50 return notification
50 return notification
51 elif isinstance(notification, (int, long)):
51 elif isinstance(notification, (int, long)):
52 return Notification.get(notification)
52 return Notification.get(notification)
53 else:
53 else:
54 if notification:
54 if notification:
55 raise Exception('notification must be int, long or Instance'
55 raise Exception('notification must be int, long or Instance'
56 ' of Notification got %s' % type(notification))
56 ' of Notification got %s' % type(notification))
57
57
58 def create(self, created_by, subject, body, recipients=None,
58 def create(self, created_by, subject, body, recipients=None,
59 type_=Notification.TYPE_MESSAGE, with_email=True,
59 type_=Notification.TYPE_MESSAGE, with_email=True,
60 email_kwargs={}):
60 email_kwargs={}):
61 """
61 """
62
62
63 Creates notification of given type
63 Creates notification of given type
64
64
65 :param created_by: int, str or User instance. User who created this
65 :param created_by: int, str or User instance. User who created this
66 notification
66 notification
67 :param subject:
67 :param subject:
68 :param body:
68 :param body:
69 :param recipients: list of int, str or User objects, when None
69 :param recipients: list of int, str or User objects, when None
70 is given send to all admins
70 is given send to all admins
71 :param type_: type of notification
71 :param type_: type of notification
72 :param with_email: send email with this notification
72 :param with_email: send email with this notification
73 :param email_kwargs: additional dict to pass as args to email template
73 :param email_kwargs: additional dict to pass as args to email template
74 """
74 """
75 from rhodecode.lib.celerylib import tasks, run_task
75 from rhodecode.lib.celerylib import tasks, run_task
76
76
77 if recipients and not getattr(recipients, '__iter__', False):
77 if recipients and not getattr(recipients, '__iter__', False):
78 raise Exception('recipients must be a list of iterable')
78 raise Exception('recipients must be a list of iterable')
79
79
80 created_by_obj = self.__get_user(created_by)
80 created_by_obj = self.__get_user(created_by)
81
81
82 if recipients:
82 if recipients:
83 recipients_objs = []
83 recipients_objs = []
84 for u in recipients:
84 for u in recipients:
85 obj = self.__get_user(u)
85 obj = self.__get_user(u)
86 if obj:
86 if obj:
87 recipients_objs.append(obj)
87 recipients_objs.append(obj)
88 recipients_objs = set(recipients_objs)
88 recipients_objs = set(recipients_objs)
89 log.debug('sending notifications %s to %s' % (
89 log.debug('sending notifications %s to %s' % (
90 type_, recipients_objs)
90 type_, recipients_objs)
91 )
91 )
92 else:
92 else:
93 # empty recipients means to all admins
93 # empty recipients means to all admins
94 recipients_objs = User.query().filter(User.admin == True).all()
94 recipients_objs = User.query().filter(User.admin == True).all()
95 log.debug('sending notifications %s to admins: %s' % (
95 log.debug('sending notifications %s to admins: %s' % (
96 type_, recipients_objs)
96 type_, recipients_objs)
97 )
97 )
98 notif = Notification.create(
98 notif = Notification.create(
99 created_by=created_by_obj, subject=subject,
99 created_by=created_by_obj, subject=subject,
100 body=body, recipients=recipients_objs, type_=type_
100 body=body, recipients=recipients_objs, type_=type_
101 )
101 )
102
102
103 if with_email is False:
103 if with_email is False:
104 return notif
104 return notif
105
105
106 # send email with notification
106 # send email with notification
107 for rec in recipients_objs:
107 for rec in recipients_objs:
108 email_subject = NotificationModel().make_description(notif, False)
108 email_subject = NotificationModel().make_description(notif, False)
109 type_ = type_
109 type_ = type_
110 email_body = body
110 email_body = body
111 ## this is passed into template
111 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
112 kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
112 kwargs.update(email_kwargs)
113 kwargs.update(email_kwargs)
113 email_body_html = EmailNotificationModel()\
114 email_body_html = EmailNotificationModel()\
114 .get_email_tmpl(type_, **kwargs)
115 .get_email_tmpl(type_, **kwargs)
115
116
116 run_task(tasks.send_email, rec.email, email_subject, email_body,
117 run_task(tasks.send_email, rec.email, email_subject, email_body,
117 email_body_html)
118 email_body_html)
118
119
119 return notif
120 return notif
120
121
121 def delete(self, user, notification):
122 def delete(self, user, notification):
122 # we don't want to remove actual notification just the assignment
123 # we don't want to remove actual notification just the assignment
123 try:
124 try:
124 notification = self.__get_notification(notification)
125 notification = self.__get_notification(notification)
125 user = self.__get_user(user)
126 user = self.__get_user(user)
126 if notification and user:
127 if notification and user:
127 obj = UserNotification.query()\
128 obj = UserNotification.query()\
128 .filter(UserNotification.user == user)\
129 .filter(UserNotification.user == user)\
129 .filter(UserNotification.notification
130 .filter(UserNotification.notification
130 == notification)\
131 == notification)\
131 .one()
132 .one()
132 self.sa.delete(obj)
133 self.sa.delete(obj)
133 return True
134 return True
134 except Exception:
135 except Exception:
135 log.error(traceback.format_exc())
136 log.error(traceback.format_exc())
136 raise
137 raise
137
138
138 def get_for_user(self, user):
139 def get_for_user(self, user):
139 user = self.__get_user(user)
140 user = self.__get_user(user)
140 return user.notifications
141 return user.notifications
141
142
142 def mark_all_read_for_user(self, user):
143 def mark_all_read_for_user(self, user):
143 user = self.__get_user(user)
144 user = self.__get_user(user)
144 UserNotification.query()\
145 UserNotification.query()\
145 .filter(UserNotification.read == False)\
146 .filter(UserNotification.read == False)\
146 .update({'read': True})
147 .update({'read': True})
147
148
148 def get_unread_cnt_for_user(self, user):
149 def get_unread_cnt_for_user(self, user):
149 user = self.__get_user(user)
150 user = self.__get_user(user)
150 return UserNotification.query()\
151 return UserNotification.query()\
151 .filter(UserNotification.read == False)\
152 .filter(UserNotification.read == False)\
152 .filter(UserNotification.user == user).count()
153 .filter(UserNotification.user == user).count()
153
154
154 def get_unread_for_user(self, user):
155 def get_unread_for_user(self, user):
155 user = self.__get_user(user)
156 user = self.__get_user(user)
156 return [x.notification for x in UserNotification.query()\
157 return [x.notification for x in UserNotification.query()\
157 .filter(UserNotification.read == False)\
158 .filter(UserNotification.read == False)\
158 .filter(UserNotification.user == user).all()]
159 .filter(UserNotification.user == user).all()]
159
160
160 def get_user_notification(self, user, notification):
161 def get_user_notification(self, user, notification):
161 user = self.__get_user(user)
162 user = self.__get_user(user)
162 notification = self.__get_notification(notification)
163 notification = self.__get_notification(notification)
163
164
164 return UserNotification.query()\
165 return UserNotification.query()\
165 .filter(UserNotification.notification == notification)\
166 .filter(UserNotification.notification == notification)\
166 .filter(UserNotification.user == user).scalar()
167 .filter(UserNotification.user == user).scalar()
167
168
168 def make_description(self, notification, show_age=True):
169 def make_description(self, notification, show_age=True):
169 """
170 """
170 Creates a human readable description based on properties
171 Creates a human readable description based on properties
171 of notification object
172 of notification object
172 """
173 """
173
174
174 _map = {
175 _map = {
175 notification.TYPE_CHANGESET_COMMENT: _('commented on commit'),
176 notification.TYPE_CHANGESET_COMMENT: _('commented on commit'),
176 notification.TYPE_MESSAGE: _('sent message'),
177 notification.TYPE_MESSAGE: _('sent message'),
177 notification.TYPE_MENTION: _('mentioned you'),
178 notification.TYPE_MENTION: _('mentioned you'),
178 notification.TYPE_REGISTRATION: _('registered in RhodeCode')
179 notification.TYPE_REGISTRATION: _('registered in RhodeCode')
179 }
180 }
180
181
181 tmpl = "%(user)s %(action)s %(when)s"
182 tmpl = "%(user)s %(action)s %(when)s"
182 if show_age:
183 if show_age:
183 when = h.age(notification.created_on)
184 when = h.age(notification.created_on)
184 else:
185 else:
185 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
186 DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
186 when = DTF(notification.created_on)
187 when = DTF(notification.created_on)
187
188
188 data = dict(
189 data = dict(
189 user=notification.created_by_user.username,
190 user=notification.created_by_user.username,
190 action=_map[notification.type_], when=when,
191 action=_map[notification.type_], when=when,
191 )
192 )
192 return tmpl % data
193 return tmpl % data
193
194
194
195
195 class EmailNotificationModel(BaseModel):
196 class EmailNotificationModel(BaseModel):
196
197
197 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
198 TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
198 TYPE_PASSWORD_RESET = 'passoword_link'
199 TYPE_PASSWORD_RESET = 'passoword_link'
199 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
200 TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
200 TYPE_DEFAULT = 'default'
201 TYPE_DEFAULT = 'default'
201
202
202 def __init__(self):
203 def __init__(self):
203 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
204 self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
204 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
205 self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
205
206
206 self.email_types = {
207 self.email_types = {
207 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
208 self.TYPE_CHANGESET_COMMENT: 'email_templates/changeset_comment.html',
208 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
209 self.TYPE_PASSWORD_RESET: 'email_templates/password_reset.html',
209 self.TYPE_REGISTRATION: 'email_templates/registration.html',
210 self.TYPE_REGISTRATION: 'email_templates/registration.html',
210 self.TYPE_DEFAULT: 'email_templates/default.html'
211 self.TYPE_DEFAULT: 'email_templates/default.html'
211 }
212 }
212
213
213 def get_email_tmpl(self, type_, **kwargs):
214 def get_email_tmpl(self, type_, **kwargs):
214 """
215 """
215 return generated template for email based on given type
216 return generated template for email based on given type
216
217
217 :param type_:
218 :param type_:
218 """
219 """
219
220
220 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
221 base = self.email_types.get(type_, self.email_types[self.TYPE_DEFAULT])
221 email_template = self._tmpl_lookup.get_template(base)
222 email_template = self._tmpl_lookup.get_template(base)
222 # translator inject
223 # translator inject
223 _kwargs = {'_': _}
224 _kwargs = {'_': _}
224 _kwargs.update(kwargs)
225 _kwargs.update(kwargs)
225 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
226 log.debug('rendering tmpl %s with kwargs %s' % (base, _kwargs))
226 return email_template.render(**_kwargs)
227 return email_template.render(**_kwargs)
@@ -1,6 +1,12 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="main.html"/>
2 <%inherit file="main.html"/>
3
3
4 <h4>${subject}</h4>
4 <h4>${subject}</h4>
5
5
6 ${body}
6 ${body}
7
8 % if status_change is not None:
9 <div>
10 New status -> ${status_change}
11 </div>
12 % endif No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now