##// END OF EJS Templates
Adde pull request voting recalculation
marcink -
r2481:4d303243 beta
parent child Browse files
Show More
@@ -1,277 +1,297 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
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 import logging
25 import logging
26 import traceback
26 import traceback
27
27
28 from webob.exc import HTTPNotFound
28 from webob.exc import HTTPNotFound
29 from collections import defaultdict
30 from itertools import groupby
29
31
30 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, response, session, tmpl_context as c, url
31 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
32 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
33 from pylons.decorators import jsonify
35 from pylons.decorators import jsonify
34
36
35 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
38 from rhodecode.lib import diffs
40 from rhodecode.lib import diffs
39 from rhodecode.lib.utils import action_logger
41 from rhodecode.lib.utils import action_logger
40 from rhodecode.model.db import User, PullRequest, ChangesetStatus
42 from rhodecode.model.db import User, PullRequest, ChangesetStatus
41 from rhodecode.model.pull_request import PullRequestModel
43 from rhodecode.model.pull_request import PullRequestModel
42 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
43 from rhodecode.model.repo import RepoModel
45 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.comment import ChangesetCommentsModel
46 from rhodecode.model.comment import ChangesetCommentsModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.changeset_status import ChangesetStatusModel
46
48
47 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
48
50
49
51
50 class PullrequestsController(BaseRepoController):
52 class PullrequestsController(BaseRepoController):
51
53
52 @LoginRequired()
54 @LoginRequired()
53 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
55 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
54 'repository.admin')
56 'repository.admin')
55 def __before__(self):
57 def __before__(self):
56 super(PullrequestsController, self).__before__()
58 super(PullrequestsController, self).__before__()
57
59
58 def _get_repo_refs(self, repo):
60 def _get_repo_refs(self, repo):
59 hist_l = []
61 hist_l = []
60
62
61 branches_group = ([('branch:%s:%s' % (k, v), k) for
63 branches_group = ([('branch:%s:%s' % (k, v), k) for
62 k, v in repo.branches.iteritems()], _("Branches"))
64 k, v in repo.branches.iteritems()], _("Branches"))
63 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
65 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
64 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
66 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
65 tags_group = ([('tag:%s:%s' % (k, v), k) for
67 tags_group = ([('tag:%s:%s' % (k, v), k) for
66 k, v in repo.tags.iteritems()], _("Tags"))
68 k, v in repo.tags.iteritems()], _("Tags"))
67
69
68 hist_l.append(bookmarks_group)
70 hist_l.append(bookmarks_group)
69 hist_l.append(branches_group)
71 hist_l.append(branches_group)
70 hist_l.append(tags_group)
72 hist_l.append(tags_group)
71
73
72 return hist_l
74 return hist_l
73
75
74 def show_all(self, repo_name):
76 def show_all(self, repo_name):
75 c.pull_requests = PullRequestModel().get_all(repo_name)
77 c.pull_requests = PullRequestModel().get_all(repo_name)
76 c.repo_name = repo_name
78 c.repo_name = repo_name
77 return render('/pullrequests/pullrequest_show_all.html')
79 return render('/pullrequests/pullrequest_show_all.html')
78
80
79 def index(self):
81 def index(self):
80 org_repo = c.rhodecode_db_repo
82 org_repo = c.rhodecode_db_repo
81
83
82 if org_repo.scm_instance.alias != 'hg':
84 if org_repo.scm_instance.alias != 'hg':
83 log.error('Review not available for GIT REPOS')
85 log.error('Review not available for GIT REPOS')
84 raise HTTPNotFound
86 raise HTTPNotFound
85
87
86 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
88 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
87 c.org_repos = []
89 c.org_repos = []
88 c.other_repos = []
90 c.other_repos = []
89 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
91 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
90 org_repo.user.username, c.repo_name))
92 org_repo.user.username, c.repo_name))
91 )
93 )
92
94
93 c.other_refs = c.org_refs
95 c.other_refs = c.org_refs
94 c.other_repos.extend(c.org_repos)
96 c.other_repos.extend(c.org_repos)
95 c.default_pull_request = org_repo.repo_name
97 c.default_pull_request = org_repo.repo_name
96 #gather forks and add to this list
98 #gather forks and add to this list
97 for fork in org_repo.forks:
99 for fork in org_repo.forks:
98 c.other_repos.append((fork.repo_name, '%s/%s' % (
100 c.other_repos.append((fork.repo_name, '%s/%s' % (
99 fork.user.username, fork.repo_name))
101 fork.user.username, fork.repo_name))
100 )
102 )
101 #add parents of this fork also
103 #add parents of this fork also
102 if org_repo.parent:
104 if org_repo.parent:
103 c.default_pull_request = org_repo.parent.repo_name
105 c.default_pull_request = org_repo.parent.repo_name
104 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
106 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
105 org_repo.parent.user.username,
107 org_repo.parent.user.username,
106 org_repo.parent.repo_name))
108 org_repo.parent.repo_name))
107 )
109 )
108
110
109 c.review_members = []
111 c.review_members = []
110 c.available_members = []
112 c.available_members = []
111 for u in User.query().filter(User.username != 'default').all():
113 for u in User.query().filter(User.username != 'default').all():
112 uname = u.username
114 uname = u.username
113 if org_repo.user == u:
115 if org_repo.user == u:
114 uname = _('%s (owner)' % u.username)
116 uname = _('%s (owner)' % u.username)
115 # auto add owner to pull-request recipients
117 # auto add owner to pull-request recipients
116 c.review_members.append([u.user_id, uname])
118 c.review_members.append([u.user_id, uname])
117 c.available_members.append([u.user_id, uname])
119 c.available_members.append([u.user_id, uname])
118 return render('/pullrequests/pullrequest.html')
120 return render('/pullrequests/pullrequest.html')
119
121
120 def create(self, repo_name):
122 def create(self, repo_name):
121 req_p = request.POST
123 req_p = request.POST
122 org_repo = req_p['org_repo']
124 org_repo = req_p['org_repo']
123 org_ref = req_p['org_ref']
125 org_ref = req_p['org_ref']
124 other_repo = req_p['other_repo']
126 other_repo = req_p['other_repo']
125 other_ref = req_p['other_ref']
127 other_ref = req_p['other_ref']
126 revisions = req_p.getall('revisions')
128 revisions = req_p.getall('revisions')
127 reviewers = req_p.getall('review_members')
129 reviewers = req_p.getall('review_members')
128 #TODO: wrap this into a FORM !!!
130 #TODO: wrap this into a FORM !!!
129
131
130 title = req_p['pullrequest_title']
132 title = req_p['pullrequest_title']
131 description = req_p['pullrequest_desc']
133 description = req_p['pullrequest_desc']
132
134
133 try:
135 try:
134 model = PullRequestModel()
136 model = PullRequestModel()
135 model.create(self.rhodecode_user.user_id, org_repo,
137 model.create(self.rhodecode_user.user_id, org_repo,
136 org_ref, other_repo, other_ref, revisions,
138 org_ref, other_repo, other_ref, revisions,
137 reviewers, title, description)
139 reviewers, title, description)
138 Session.commit()
140 Session.commit()
139 h.flash(_('Pull request send'), category='success')
141 h.flash(_('Pull request send'), category='success')
140 except Exception:
142 except Exception:
141 raise
143 raise
142 h.flash(_('Error occured during sending pull request'),
144 h.flash(_('Error occured during sending pull request'),
143 category='error')
145 category='error')
144 log.error(traceback.format_exc())
146 log.error(traceback.format_exc())
145
147
146 return redirect(url('changelog_home', repo_name=repo_name))
148 return redirect(url('changelog_home', repo_name=repo_name))
147
149
148 def _load_compare_data(self, pull_request):
150 def _load_compare_data(self, pull_request):
149 """
151 """
150 Load context data needed for generating compare diff
152 Load context data needed for generating compare diff
151
153
152 :param pull_request:
154 :param pull_request:
153 :type pull_request:
155 :type pull_request:
154 """
156 """
155
157
156 org_repo = pull_request.org_repo
158 org_repo = pull_request.org_repo
157 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
159 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
158 other_repo = pull_request.other_repo
160 other_repo = pull_request.other_repo
159 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
161 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
160
162
161 org_ref = (org_ref_type, org_ref)
163 org_ref = (org_ref_type, org_ref)
162 other_ref = (other_ref_type, other_ref)
164 other_ref = (other_ref_type, other_ref)
163
165
164 c.org_repo = org_repo
166 c.org_repo = org_repo
165 c.other_repo = other_repo
167 c.other_repo = other_repo
166
168
167 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
169 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
168 org_repo, org_ref, other_repo, other_ref
170 org_repo, org_ref, other_repo, other_ref
169 )
171 )
170
172
171 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
173 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
172 c.cs_ranges])
174 c.cs_ranges])
173 # defines that we need hidden inputs with changesets
175 # defines that we need hidden inputs with changesets
174 c.as_form = request.GET.get('as_form', False)
176 c.as_form = request.GET.get('as_form', False)
175
177
176 c.org_ref = org_ref[1]
178 c.org_ref = org_ref[1]
177 c.other_ref = other_ref[1]
179 c.other_ref = other_ref[1]
178 # diff needs to have swapped org with other to generate proper diff
180 # diff needs to have swapped org with other to generate proper diff
179 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
181 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
180 discovery_data)
182 discovery_data)
181 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
183 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
182 _parsed = diff_processor.prepare()
184 _parsed = diff_processor.prepare()
183
185
184 c.files = []
186 c.files = []
185 c.changes = {}
187 c.changes = {}
186
188
187 for f in _parsed:
189 for f in _parsed:
188 fid = h.FID('', f['filename'])
190 fid = h.FID('', f['filename'])
189 c.files.append([fid, f['operation'], f['filename'], f['stats']])
191 c.files.append([fid, f['operation'], f['filename'], f['stats']])
190 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
192 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
191 c.changes[fid] = [f['operation'], f['filename'], diff]
193 c.changes[fid] = [f['operation'], f['filename'], diff]
192
194
193 def show(self, repo_name, pull_request_id):
195 def show(self, repo_name, pull_request_id):
194 repo_model = RepoModel()
196 repo_model = RepoModel()
195 c.users_array = repo_model.get_users_js()
197 c.users_array = repo_model.get_users_js()
196 c.users_groups_array = repo_model.get_users_groups_js()
198 c.users_groups_array = repo_model.get_users_groups_js()
197 c.pull_request = PullRequest.get(pull_request_id)
199 c.pull_request = PullRequest.get(pull_request_id)
198
200
199 # valid ID
201 # valid ID
200 if not c.pull_request:
202 if not c.pull_request:
201 raise HTTPNotFound
203 raise HTTPNotFound
204 cc_model = ChangesetCommentsModel()
205 cs_model = ChangesetStatusModel()
206 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
207 pull_request=c.pull_request,
208 with_revisions=True)
209
210 cs_statuses = defaultdict(list)
211 for st in _cs_statuses:
212 cs_statuses[st.author.username] += [st]
213
214 c.pull_request_reviewers = []
215 for o in c.pull_request.reviewers:
216 st = cs_statuses.get(o.user.username, None)
217 if st:
218 sorter = lambda k: k.version
219 st = [(x, list(y)[0])
220 for x, y in (groupby(sorted(st, key=sorter), sorter))]
221 c.pull_request_reviewers.append([o.user, st])
202
222
203 # pull_requests repo_name we opened it against
223 # pull_requests repo_name we opened it against
204 # ie. other_repo must match
224 # ie. other_repo must match
205 if repo_name != c.pull_request.other_repo.repo_name:
225 if repo_name != c.pull_request.other_repo.repo_name:
206 raise HTTPNotFound
226 raise HTTPNotFound
207
227
208 # load compare data into template context
228 # load compare data into template context
209 self._load_compare_data(c.pull_request)
229 self._load_compare_data(c.pull_request)
210
230
211 # inline comments
231 # inline comments
212 c.inline_cnt = 0
232 c.inline_cnt = 0
213 c.inline_comments = ChangesetCommentsModel()\
233 c.inline_comments = cc_model.get_inline_comments(
214 .get_inline_comments(c.rhodecode_db_repo.repo_id,
234 c.rhodecode_db_repo.repo_id,
215 pull_request=pull_request_id)
235 pull_request=pull_request_id)
216 # count inline comments
236 # count inline comments
217 for __, lines in c.inline_comments:
237 for __, lines in c.inline_comments:
218 for comments in lines.values():
238 for comments in lines.values():
219 c.inline_cnt += len(comments)
239 c.inline_cnt += len(comments)
220 # comments
240 # comments
221 c.comments = ChangesetCommentsModel()\
241 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
222 .get_comments(c.rhodecode_db_repo.repo_id,
242 pull_request=pull_request_id)
223 pull_request=pull_request_id)
224
243
225 # changeset(pull-request) status
244 # changeset(pull-request) status
226 c.current_changeset_status = ChangesetStatusModel()\
245 c.current_changeset_status = cs_model.calculate_status(
227 .get_status(c.pull_request.org_repo,
246 c.pull_request_reviewers
228 pull_request=c.pull_request)
247 )
229 c.changeset_statuses = ChangesetStatus.STATUSES
248 c.changeset_statuses = ChangesetStatus.STATUSES
249
230 return render('/pullrequests/pullrequest_show.html')
250 return render('/pullrequests/pullrequest_show.html')
231
251
232 @jsonify
252 @jsonify
233 def comment(self, repo_name, pull_request_id):
253 def comment(self, repo_name, pull_request_id):
234
254
235 status = request.POST.get('changeset_status')
255 status = request.POST.get('changeset_status')
236 change_status = request.POST.get('change_changeset_status')
256 change_status = request.POST.get('change_changeset_status')
237
257
238 comm = ChangesetCommentsModel().create(
258 comm = ChangesetCommentsModel().create(
239 text=request.POST.get('text'),
259 text=request.POST.get('text'),
240 repo_id=c.rhodecode_db_repo.repo_id,
260 repo_id=c.rhodecode_db_repo.repo_id,
241 user_id=c.rhodecode_user.user_id,
261 user_id=c.rhodecode_user.user_id,
242 pull_request=pull_request_id,
262 pull_request=pull_request_id,
243 f_path=request.POST.get('f_path'),
263 f_path=request.POST.get('f_path'),
244 line_no=request.POST.get('line'),
264 line_no=request.POST.get('line'),
245 status_change=(ChangesetStatus.get_status_lbl(status)
265 status_change=(ChangesetStatus.get_status_lbl(status)
246 if status and change_status else None)
266 if status and change_status else None)
247 )
267 )
248
268
249 # get status if set !
269 # get status if set !
250 if status and change_status:
270 if status and change_status:
251 ChangesetStatusModel().set_status(
271 ChangesetStatusModel().set_status(
252 c.rhodecode_db_repo.repo_id,
272 c.rhodecode_db_repo.repo_id,
253 status,
273 status,
254 c.rhodecode_user.user_id,
274 c.rhodecode_user.user_id,
255 comm,
275 comm,
256 pull_request=pull_request_id
276 pull_request=pull_request_id
257 )
277 )
258 action_logger(self.rhodecode_user,
278 action_logger(self.rhodecode_user,
259 'user_commented_pull_request:%s' % pull_request_id,
279 'user_commented_pull_request:%s' % pull_request_id,
260 c.rhodecode_db_repo, self.ip_addr, self.sa)
280 c.rhodecode_db_repo, self.ip_addr, self.sa)
261
281
262 Session.commit()
282 Session.commit()
263
283
264 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
284 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
265 return redirect(h.url('pullrequest_show', repo_name=repo_name,
285 return redirect(h.url('pullrequest_show', repo_name=repo_name,
266 pull_request_id=pull_request_id))
286 pull_request_id=pull_request_id))
267
287
268 data = {
288 data = {
269 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
289 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
270 }
290 }
271 if comm:
291 if comm:
272 c.co = comm
292 c.co = comm
273 data.update(comm.get_dict())
293 data.update(comm.get_dict())
274 data.update({'rendered_text':
294 data.update({'rendered_text':
275 render('changeset/changeset_comment_block.html')})
295 render('changeset/changeset_comment_block.html')})
276
296
277 return data
297 return data
@@ -1,139 +1,174 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.changeset_status
3 rhodecode.model.changeset_status
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6
6
7 :created_on: Apr 30, 2012
7 :created_on: Apr 30, 2012
8 :author: marcink
8 :author: marcink
9 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
9 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :license: GPLv3, see COPYING for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25
25
26 import logging
26 import logging
27 from collections import defaultdict
27
28
28 from rhodecode.model import BaseModel
29 from rhodecode.model import BaseModel
29 from rhodecode.model.db import ChangesetStatus, PullRequest
30 from rhodecode.model.db import ChangesetStatus, PullRequest
30
31
31 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
32
33
33
34
34 class ChangesetStatusModel(BaseModel):
35 class ChangesetStatusModel(BaseModel):
35
36
36 def __get_changeset_status(self, changeset_status):
37 def __get_changeset_status(self, changeset_status):
37 return self._get_instance(ChangesetStatus, changeset_status)
38 return self._get_instance(ChangesetStatus, changeset_status)
38
39
39 def __get_pull_request(self, pull_request):
40 def __get_pull_request(self, pull_request):
40 return self._get_instance(PullRequest, pull_request)
41 return self._get_instance(PullRequest, pull_request)
41
42
43 def _get_status_query(self, repo, revision, pull_request,
44 with_revisions=False):
45 repo = self._get_repo(repo)
46
47 q = ChangesetStatus.query()\
48 .filter(ChangesetStatus.repo == repo)
49 if not with_revisions:
50 q = q.filter(ChangesetStatus.version == 0)
51
52 if revision:
53 q = q.filter(ChangesetStatus.revision == revision)
54 elif pull_request:
55 pull_request = self.__get_pull_request(pull_request)
56 q = q.filter(ChangesetStatus.pull_request == pull_request)
57 else:
58 raise Exception('Please specify revision or pull_request')
59 q.order_by(ChangesetStatus.version.asc())
60 return q
61
62 def calculate_status(self, statuses_by_reviewers):
63 """
64 leading one wins, if number of occurences are equal than weaker wins
65
66 :param statuses_by_reviewers:
67 """
68 status = None
69 votes = defaultdict(int)
70 reviewers_number = len(statuses_by_reviewers)
71 for user, statuses in statuses_by_reviewers:
72 if statuses:
73 ver, latest = statuses[0]
74 votes[latest.status] += 1
75 else:
76 votes[ChangesetStatus.DEFAULT] += 1
77
78 if votes.get(ChangesetStatus.STATUS_APPROVED) == reviewers_number:
79 return ChangesetStatus.STATUS_APPROVED
80 else:
81 return ChangesetStatus.STATUS_UNDER_REVIEW
82
83 def get_statuses(self, repo, revision=None, pull_request=None,
84 with_revisions=False):
85 q = self._get_status_query(repo, revision, pull_request,
86 with_revisions)
87 return q.all()
88
42 def get_status(self, repo, revision=None, pull_request=None):
89 def get_status(self, repo, revision=None, pull_request=None):
43 """
90 """
44 Returns latest status of changeset for given revision or for given
91 Returns latest status of changeset for given revision or for given
45 pull request. Statuses are versioned inside a table itself and
92 pull request. Statuses are versioned inside a table itself and
46 version == 0 is always the current one
93 version == 0 is always the current one
47
94
48 :param repo:
95 :param repo:
49 :type repo:
96 :type repo:
50 :param revision: 40char hash or None
97 :param revision: 40char hash or None
51 :type revision: str
98 :type revision: str
52 :param pull_request: pull_request reference
99 :param pull_request: pull_request reference
53 :type:
100 :type:
54 """
101 """
55 repo = self._get_repo(repo)
102 q = self._get_status_query(repo, revision, pull_request)
56
57 q = ChangesetStatus.query()\
58 .filter(ChangesetStatus.repo == repo)\
59 .filter(ChangesetStatus.version == 0)
60
61 if revision:
62 q = q.filter(ChangesetStatus.revision == revision)
63 elif pull_request:
64 pull_request = self.__get_pull_request(pull_request)
65 q = q.filter(ChangesetStatus.pull_request == pull_request)
66 else:
67 raise Exception('Please specify revision or pull_request')
68
103
69 # need to use first here since there can be multiple statuses
104 # need to use first here since there can be multiple statuses
70 # returned from pull_request
105 # returned from pull_request
71 status = q.first()
106 status = q.first()
72 status = status.status if status else status
107 status = status.status if status else status
73 st = status or ChangesetStatus.DEFAULT
108 st = status or ChangesetStatus.DEFAULT
74 return str(st)
109 return str(st)
75
110
76 def set_status(self, repo, status, user, comment, revision=None,
111 def set_status(self, repo, status, user, comment, revision=None,
77 pull_request=None):
112 pull_request=None):
78 """
113 """
79 Creates new status for changeset or updates the old ones bumping their
114 Creates new status for changeset or updates the old ones bumping their
80 version, leaving the current status at
115 version, leaving the current status at
81
116
82 :param repo:
117 :param repo:
83 :type repo:
118 :type repo:
84 :param revision:
119 :param revision:
85 :type revision:
120 :type revision:
86 :param status:
121 :param status:
87 :type status:
122 :type status:
88 :param user:
123 :param user:
89 :type user:
124 :type user:
90 :param comment:
125 :param comment:
91 :type comment:
126 :type comment:
92 """
127 """
93 repo = self._get_repo(repo)
128 repo = self._get_repo(repo)
94
129
95 q = ChangesetStatus.query()
130 q = ChangesetStatus.query()
96
131
97 if revision:
132 if revision:
98 q = q.filter(ChangesetStatus.repo == repo)
133 q = q.filter(ChangesetStatus.repo == repo)
99 q = q.filter(ChangesetStatus.revision == revision)
134 q = q.filter(ChangesetStatus.revision == revision)
100 elif pull_request:
135 elif pull_request:
101 pull_request = self.__get_pull_request(pull_request)
136 pull_request = self.__get_pull_request(pull_request)
102 q = q.filter(ChangesetStatus.repo == pull_request.org_repo)
137 q = q.filter(ChangesetStatus.repo == pull_request.org_repo)
103 q = q.filter(ChangesetStatus.pull_request == pull_request)
138 q = q.filter(ChangesetStatus.pull_request == pull_request)
104 cur_statuses = q.all()
139 cur_statuses = q.all()
105
140
106 if cur_statuses:
141 if cur_statuses:
107 for st in cur_statuses:
142 for st in cur_statuses:
108 st.version += 1
143 st.version += 1
109 self.sa.add(st)
144 self.sa.add(st)
110
145
111 def _create_status(user, repo, status, comment, revision, pull_request):
146 def _create_status(user, repo, status, comment, revision, pull_request):
112 new_status = ChangesetStatus()
147 new_status = ChangesetStatus()
113 new_status.author = self._get_user(user)
148 new_status.author = self._get_user(user)
114 new_status.repo = self._get_repo(repo)
149 new_status.repo = self._get_repo(repo)
115 new_status.status = status
150 new_status.status = status
116 new_status.comment = comment
151 new_status.comment = comment
117 new_status.revision = revision
152 new_status.revision = revision
118 new_status.pull_request = pull_request
153 new_status.pull_request = pull_request
119 return new_status
154 return new_status
120
155
121 if revision:
156 if revision:
122 new_status = _create_status(user=user, repo=repo, status=status,
157 new_status = _create_status(user=user, repo=repo, status=status,
123 comment=comment, revision=revision,
158 comment=comment, revision=revision,
124 pull_request=None)
159 pull_request=None)
125 self.sa.add(new_status)
160 self.sa.add(new_status)
126 return new_status
161 return new_status
127 elif pull_request:
162 elif pull_request:
128 #pull request can have more than one revision associated to it
163 #pull request can have more than one revision associated to it
129 #we need to create new version for each one
164 #we need to create new version for each one
130 new_statuses = []
165 new_statuses = []
131 repo = pull_request.org_repo
166 repo = pull_request.org_repo
132 for rev in pull_request.revisions:
167 for rev in pull_request.revisions:
133 new_status = _create_status(user=user, repo=repo,
168 new_status = _create_status(user=user, repo=repo,
134 status=status, comment=comment,
169 status=status, comment=comment,
135 revision=rev,
170 revision=rev,
136 pull_request=pull_request)
171 pull_request=pull_request)
137 new_statuses.append(new_status)
172 new_statuses.append(new_status)
138 self.sa.add(new_status)
173 self.sa.add(new_status)
139 return new_statuses
174 return new_statuses
@@ -1,1549 +1,1558 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 import hashlib
30 import hashlib
31 from collections import defaultdict
31 from collections import defaultdict
32
32
33 from sqlalchemy import *
33 from sqlalchemy import *
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.exc import DatabaseError
36 from sqlalchemy.exc import DatabaseError
37 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
38
38
39 from pylons.i18n.translation import lazy_ugettext as _
39 from pylons.i18n.translation import lazy_ugettext as _
40
40
41 from rhodecode.lib.vcs import get_backend
41 from rhodecode.lib.vcs import get_backend
42 from rhodecode.lib.vcs.utils.helpers import get_scm
42 from rhodecode.lib.vcs.utils.helpers import get_scm
43 from rhodecode.lib.vcs.exceptions import VCSError
43 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.utils.lazy import LazyProperty
44 from rhodecode.lib.vcs.utils.lazy import LazyProperty
45
45
46 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
46 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
47 safe_unicode
47 safe_unicode
48 from rhodecode.lib.compat import json
48 from rhodecode.lib.compat import json
49 from rhodecode.lib.caching_query import FromCache
49 from rhodecode.lib.caching_query import FromCache
50
50
51 from rhodecode.model.meta import Base, Session
51 from rhodecode.model.meta import Base, Session
52
52
53
53
54 URL_SEP = '/'
54 URL_SEP = '/'
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 #==============================================================================
57 #==============================================================================
58 # BASE CLASSES
58 # BASE CLASSES
59 #==============================================================================
59 #==============================================================================
60
60
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62
62
63
63
64 class ModelSerializer(json.JSONEncoder):
64 class ModelSerializer(json.JSONEncoder):
65 """
65 """
66 Simple Serializer for JSON,
66 Simple Serializer for JSON,
67
67
68 usage::
68 usage::
69
69
70 to make object customized for serialization implement a __json__
70 to make object customized for serialization implement a __json__
71 method that will return a dict for serialization into json
71 method that will return a dict for serialization into json
72
72
73 example::
73 example::
74
74
75 class Task(object):
75 class Task(object):
76
76
77 def __init__(self, name, value):
77 def __init__(self, name, value):
78 self.name = name
78 self.name = name
79 self.value = value
79 self.value = value
80
80
81 def __json__(self):
81 def __json__(self):
82 return dict(name=self.name,
82 return dict(name=self.name,
83 value=self.value)
83 value=self.value)
84
84
85 """
85 """
86
86
87 def default(self, obj):
87 def default(self, obj):
88
88
89 if hasattr(obj, '__json__'):
89 if hasattr(obj, '__json__'):
90 return obj.__json__()
90 return obj.__json__()
91 else:
91 else:
92 return json.JSONEncoder.default(self, obj)
92 return json.JSONEncoder.default(self, obj)
93
93
94
94
95 class BaseModel(object):
95 class BaseModel(object):
96 """
96 """
97 Base Model for all classess
97 Base Model for all classess
98 """
98 """
99
99
100 @classmethod
100 @classmethod
101 def _get_keys(cls):
101 def _get_keys(cls):
102 """return column names for this model """
102 """return column names for this model """
103 return class_mapper(cls).c.keys()
103 return class_mapper(cls).c.keys()
104
104
105 def get_dict(self):
105 def get_dict(self):
106 """
106 """
107 return dict with keys and values corresponding
107 return dict with keys and values corresponding
108 to this model data """
108 to this model data """
109
109
110 d = {}
110 d = {}
111 for k in self._get_keys():
111 for k in self._get_keys():
112 d[k] = getattr(self, k)
112 d[k] = getattr(self, k)
113
113
114 # also use __json__() if present to get additional fields
114 # also use __json__() if present to get additional fields
115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
116 d[k] = val
116 d[k] = val
117 return d
117 return d
118
118
119 def get_appstruct(self):
119 def get_appstruct(self):
120 """return list with keys and values tupples corresponding
120 """return list with keys and values tupples corresponding
121 to this model data """
121 to this model data """
122
122
123 l = []
123 l = []
124 for k in self._get_keys():
124 for k in self._get_keys():
125 l.append((k, getattr(self, k),))
125 l.append((k, getattr(self, k),))
126 return l
126 return l
127
127
128 def populate_obj(self, populate_dict):
128 def populate_obj(self, populate_dict):
129 """populate model with data from given populate_dict"""
129 """populate model with data from given populate_dict"""
130
130
131 for k in self._get_keys():
131 for k in self._get_keys():
132 if k in populate_dict:
132 if k in populate_dict:
133 setattr(self, k, populate_dict[k])
133 setattr(self, k, populate_dict[k])
134
134
135 @classmethod
135 @classmethod
136 def query(cls):
136 def query(cls):
137 return Session.query(cls)
137 return Session.query(cls)
138
138
139 @classmethod
139 @classmethod
140 def get(cls, id_):
140 def get(cls, id_):
141 if id_:
141 if id_:
142 return cls.query().get(id_)
142 return cls.query().get(id_)
143
143
144 @classmethod
144 @classmethod
145 def getAll(cls):
145 def getAll(cls):
146 return cls.query().all()
146 return cls.query().all()
147
147
148 @classmethod
148 @classmethod
149 def delete(cls, id_):
149 def delete(cls, id_):
150 obj = cls.query().get(id_)
150 obj = cls.query().get(id_)
151 Session.delete(obj)
151 Session.delete(obj)
152
152
153 def __repr__(self):
153 def __repr__(self):
154 if hasattr(self, '__unicode__'):
154 if hasattr(self, '__unicode__'):
155 # python repr needs to return str
155 # python repr needs to return str
156 return safe_str(self.__unicode__())
156 return safe_str(self.__unicode__())
157 return '<DB:%s>' % (self.__class__.__name__)
157 return '<DB:%s>' % (self.__class__.__name__)
158
158
159
159
160 class RhodeCodeSetting(Base, BaseModel):
160 class RhodeCodeSetting(Base, BaseModel):
161 __tablename__ = 'rhodecode_settings'
161 __tablename__ = 'rhodecode_settings'
162 __table_args__ = (
162 __table_args__ = (
163 UniqueConstraint('app_settings_name'),
163 UniqueConstraint('app_settings_name'),
164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
164 {'extend_existing': True, 'mysql_engine': 'InnoDB',
165 'mysql_charset': 'utf8'}
165 'mysql_charset': 'utf8'}
166 )
166 )
167 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
167 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
168 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
168 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
170
170
171 def __init__(self, k='', v=''):
171 def __init__(self, k='', v=''):
172 self.app_settings_name = k
172 self.app_settings_name = k
173 self.app_settings_value = v
173 self.app_settings_value = v
174
174
175 @validates('_app_settings_value')
175 @validates('_app_settings_value')
176 def validate_settings_value(self, key, val):
176 def validate_settings_value(self, key, val):
177 assert type(val) == unicode
177 assert type(val) == unicode
178 return val
178 return val
179
179
180 @hybrid_property
180 @hybrid_property
181 def app_settings_value(self):
181 def app_settings_value(self):
182 v = self._app_settings_value
182 v = self._app_settings_value
183 if self.app_settings_name == 'ldap_active':
183 if self.app_settings_name == 'ldap_active':
184 v = str2bool(v)
184 v = str2bool(v)
185 return v
185 return v
186
186
187 @app_settings_value.setter
187 @app_settings_value.setter
188 def app_settings_value(self, val):
188 def app_settings_value(self, val):
189 """
189 """
190 Setter that will always make sure we use unicode in app_settings_value
190 Setter that will always make sure we use unicode in app_settings_value
191
191
192 :param val:
192 :param val:
193 """
193 """
194 self._app_settings_value = safe_unicode(val)
194 self._app_settings_value = safe_unicode(val)
195
195
196 def __unicode__(self):
196 def __unicode__(self):
197 return u"<%s('%s:%s')>" % (
197 return u"<%s('%s:%s')>" % (
198 self.__class__.__name__,
198 self.__class__.__name__,
199 self.app_settings_name, self.app_settings_value
199 self.app_settings_name, self.app_settings_value
200 )
200 )
201
201
202 @classmethod
202 @classmethod
203 def get_by_name(cls, ldap_key):
203 def get_by_name(cls, ldap_key):
204 return cls.query()\
204 return cls.query()\
205 .filter(cls.app_settings_name == ldap_key).scalar()
205 .filter(cls.app_settings_name == ldap_key).scalar()
206
206
207 @classmethod
207 @classmethod
208 def get_app_settings(cls, cache=False):
208 def get_app_settings(cls, cache=False):
209
209
210 ret = cls.query()
210 ret = cls.query()
211
211
212 if cache:
212 if cache:
213 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
214
214
215 if not ret:
215 if not ret:
216 raise Exception('Could not get application settings !')
216 raise Exception('Could not get application settings !')
217 settings = {}
217 settings = {}
218 for each in ret:
218 for each in ret:
219 settings['rhodecode_' + each.app_settings_name] = \
219 settings['rhodecode_' + each.app_settings_name] = \
220 each.app_settings_value
220 each.app_settings_value
221
221
222 return settings
222 return settings
223
223
224 @classmethod
224 @classmethod
225 def get_ldap_settings(cls, cache=False):
225 def get_ldap_settings(cls, cache=False):
226 ret = cls.query()\
226 ret = cls.query()\
227 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 .filter(cls.app_settings_name.startswith('ldap_')).all()
228 fd = {}
228 fd = {}
229 for row in ret:
229 for row in ret:
230 fd.update({row.app_settings_name: row.app_settings_value})
230 fd.update({row.app_settings_name: row.app_settings_value})
231
231
232 return fd
232 return fd
233
233
234
234
235 class RhodeCodeUi(Base, BaseModel):
235 class RhodeCodeUi(Base, BaseModel):
236 __tablename__ = 'rhodecode_ui'
236 __tablename__ = 'rhodecode_ui'
237 __table_args__ = (
237 __table_args__ = (
238 UniqueConstraint('ui_key'),
238 UniqueConstraint('ui_key'),
239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
240 'mysql_charset': 'utf8'}
240 'mysql_charset': 'utf8'}
241 )
241 )
242
242
243 HOOK_UPDATE = 'changegroup.update'
243 HOOK_UPDATE = 'changegroup.update'
244 HOOK_REPO_SIZE = 'changegroup.repo_size'
244 HOOK_REPO_SIZE = 'changegroup.repo_size'
245 HOOK_PUSH = 'changegroup.push_logger'
245 HOOK_PUSH = 'changegroup.push_logger'
246 HOOK_PULL = 'preoutgoing.pull_logger'
246 HOOK_PULL = 'preoutgoing.pull_logger'
247
247
248 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
248 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
249 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
252 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
253
253
254 @classmethod
254 @classmethod
255 def get_by_key(cls, key):
255 def get_by_key(cls, key):
256 return cls.query().filter(cls.ui_key == key)
256 return cls.query().filter(cls.ui_key == key)
257
257
258 @classmethod
258 @classmethod
259 def get_builtin_hooks(cls):
259 def get_builtin_hooks(cls):
260 q = cls.query()
260 q = cls.query()
261 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
261 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
262 cls.HOOK_REPO_SIZE,
262 cls.HOOK_REPO_SIZE,
263 cls.HOOK_PUSH, cls.HOOK_PULL]))
263 cls.HOOK_PUSH, cls.HOOK_PULL]))
264 return q.all()
264 return q.all()
265
265
266 @classmethod
266 @classmethod
267 def get_custom_hooks(cls):
267 def get_custom_hooks(cls):
268 q = cls.query()
268 q = cls.query()
269 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
269 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
270 cls.HOOK_REPO_SIZE,
270 cls.HOOK_REPO_SIZE,
271 cls.HOOK_PUSH, cls.HOOK_PULL]))
271 cls.HOOK_PUSH, cls.HOOK_PULL]))
272 q = q.filter(cls.ui_section == 'hooks')
272 q = q.filter(cls.ui_section == 'hooks')
273 return q.all()
273 return q.all()
274
274
275 @classmethod
275 @classmethod
276 def get_repos_location(cls):
276 def get_repos_location(cls):
277 return cls.get_by_key('/').one().ui_value
277 return cls.get_by_key('/').one().ui_value
278
278
279 @classmethod
279 @classmethod
280 def create_or_update_hook(cls, key, val):
280 def create_or_update_hook(cls, key, val):
281 new_ui = cls.get_by_key(key).scalar() or cls()
281 new_ui = cls.get_by_key(key).scalar() or cls()
282 new_ui.ui_section = 'hooks'
282 new_ui.ui_section = 'hooks'
283 new_ui.ui_active = True
283 new_ui.ui_active = True
284 new_ui.ui_key = key
284 new_ui.ui_key = key
285 new_ui.ui_value = val
285 new_ui.ui_value = val
286
286
287 Session.add(new_ui)
287 Session.add(new_ui)
288
288
289
289
290 class User(Base, BaseModel):
290 class User(Base, BaseModel):
291 __tablename__ = 'users'
291 __tablename__ = 'users'
292 __table_args__ = (
292 __table_args__ = (
293 UniqueConstraint('username'), UniqueConstraint('email'),
293 UniqueConstraint('username'), UniqueConstraint('email'),
294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
294 {'extend_existing': True, 'mysql_engine': 'InnoDB',
295 'mysql_charset': 'utf8'}
295 'mysql_charset': 'utf8'}
296 )
296 )
297 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
297 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
298 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
300 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
301 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
301 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
302 name = Column("firstname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
302 name = Column("firstname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
303 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
304 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
305 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
305 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
306 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
308
308
309 user_log = relationship('UserLog', cascade='all')
309 user_log = relationship('UserLog', cascade='all')
310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
310 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
311
311
312 repositories = relationship('Repository')
312 repositories = relationship('Repository')
313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
313 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
314 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
315 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
316
316
317 group_member = relationship('UsersGroupMember', cascade='all')
317 group_member = relationship('UsersGroupMember', cascade='all')
318
318
319 notifications = relationship('UserNotification', cascade='all')
319 notifications = relationship('UserNotification', cascade='all')
320 # notifications assigned to this user
320 # notifications assigned to this user
321 user_created_notifications = relationship('Notification', cascade='all')
321 user_created_notifications = relationship('Notification', cascade='all')
322 # comments created by this user
322 # comments created by this user
323 user_comments = relationship('ChangesetComment', cascade='all')
323 user_comments = relationship('ChangesetComment', cascade='all')
324
324
325 @hybrid_property
325 @hybrid_property
326 def email(self):
326 def email(self):
327 return self._email
327 return self._email
328
328
329 @email.setter
329 @email.setter
330 def email(self, val):
330 def email(self, val):
331 self._email = val.lower() if val else None
331 self._email = val.lower() if val else None
332
332
333 @property
333 @property
334 def full_name(self):
334 def full_name(self):
335 return '%s %s' % (self.name, self.lastname)
335 return '%s %s' % (self.name, self.lastname)
336
336
337 @property
337 @property
338 def full_name_or_username(self):
338 def full_name_or_username(self):
339 return ('%s %s' % (self.name, self.lastname)
339 return ('%s %s' % (self.name, self.lastname)
340 if (self.name and self.lastname) else self.username)
340 if (self.name and self.lastname) else self.username)
341
341
342 @property
342 @property
343 def full_contact(self):
343 def full_contact(self):
344 return '%s %s <%s>' % (self.name, self.lastname, self.email)
344 return '%s %s <%s>' % (self.name, self.lastname, self.email)
345
345
346 @property
346 @property
347 def short_contact(self):
347 def short_contact(self):
348 return '%s %s' % (self.name, self.lastname)
348 return '%s %s' % (self.name, self.lastname)
349
349
350 @property
350 @property
351 def is_admin(self):
351 def is_admin(self):
352 return self.admin
352 return self.admin
353
353
354 def __unicode__(self):
354 def __unicode__(self):
355 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
355 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
356 self.user_id, self.username)
356 self.user_id, self.username)
357
357
358 @classmethod
358 @classmethod
359 def get_by_username(cls, username, case_insensitive=False, cache=False):
359 def get_by_username(cls, username, case_insensitive=False, cache=False):
360 if case_insensitive:
360 if case_insensitive:
361 q = cls.query().filter(cls.username.ilike(username))
361 q = cls.query().filter(cls.username.ilike(username))
362 else:
362 else:
363 q = cls.query().filter(cls.username == username)
363 q = cls.query().filter(cls.username == username)
364
364
365 if cache:
365 if cache:
366 q = q.options(FromCache(
366 q = q.options(FromCache(
367 "sql_cache_short",
367 "sql_cache_short",
368 "get_user_%s" % _hash_key(username)
368 "get_user_%s" % _hash_key(username)
369 )
369 )
370 )
370 )
371 return q.scalar()
371 return q.scalar()
372
372
373 @classmethod
373 @classmethod
374 def get_by_api_key(cls, api_key, cache=False):
374 def get_by_api_key(cls, api_key, cache=False):
375 q = cls.query().filter(cls.api_key == api_key)
375 q = cls.query().filter(cls.api_key == api_key)
376
376
377 if cache:
377 if cache:
378 q = q.options(FromCache("sql_cache_short",
378 q = q.options(FromCache("sql_cache_short",
379 "get_api_key_%s" % api_key))
379 "get_api_key_%s" % api_key))
380 return q.scalar()
380 return q.scalar()
381
381
382 @classmethod
382 @classmethod
383 def get_by_email(cls, email, case_insensitive=False, cache=False):
383 def get_by_email(cls, email, case_insensitive=False, cache=False):
384 if case_insensitive:
384 if case_insensitive:
385 q = cls.query().filter(cls.email.ilike(email))
385 q = cls.query().filter(cls.email.ilike(email))
386 else:
386 else:
387 q = cls.query().filter(cls.email == email)
387 q = cls.query().filter(cls.email == email)
388
388
389 if cache:
389 if cache:
390 q = q.options(FromCache("sql_cache_short",
390 q = q.options(FromCache("sql_cache_short",
391 "get_email_key_%s" % email))
391 "get_email_key_%s" % email))
392
392
393 ret = q.scalar()
393 ret = q.scalar()
394 if ret is None:
394 if ret is None:
395 q = UserEmailMap.query()
395 q = UserEmailMap.query()
396 # try fetching in alternate email map
396 # try fetching in alternate email map
397 if case_insensitive:
397 if case_insensitive:
398 q = q.filter(UserEmailMap.email.ilike(email))
398 q = q.filter(UserEmailMap.email.ilike(email))
399 else:
399 else:
400 q = q.filter(UserEmailMap.email == email)
400 q = q.filter(UserEmailMap.email == email)
401 q = q.options(joinedload(UserEmailMap.user))
401 q = q.options(joinedload(UserEmailMap.user))
402 if cache:
402 if cache:
403 q = q.options(FromCache("sql_cache_short",
403 q = q.options(FromCache("sql_cache_short",
404 "get_email_map_key_%s" % email))
404 "get_email_map_key_%s" % email))
405 ret = getattr(q.scalar(), 'user', None)
405 ret = getattr(q.scalar(), 'user', None)
406
406
407 return ret
407 return ret
408
408
409 def update_lastlogin(self):
409 def update_lastlogin(self):
410 """Update user lastlogin"""
410 """Update user lastlogin"""
411 self.last_login = datetime.datetime.now()
411 self.last_login = datetime.datetime.now()
412 Session.add(self)
412 Session.add(self)
413 log.debug('updated user %s lastlogin' % self.username)
413 log.debug('updated user %s lastlogin' % self.username)
414
414
415 def __json__(self):
415 def __json__(self):
416 return dict(
416 return dict(
417 user_id=self.user_id,
417 user_id=self.user_id,
418 first_name=self.name,
418 first_name=self.name,
419 last_name=self.lastname,
419 last_name=self.lastname,
420 email=self.email,
420 email=self.email,
421 full_name=self.full_name,
421 full_name=self.full_name,
422 full_name_or_username=self.full_name_or_username,
422 full_name_or_username=self.full_name_or_username,
423 short_contact=self.short_contact,
423 short_contact=self.short_contact,
424 full_contact=self.full_contact
424 full_contact=self.full_contact
425 )
425 )
426
426
427
427
428 class UserEmailMap(Base, BaseModel):
428 class UserEmailMap(Base, BaseModel):
429 __tablename__ = 'user_email_map'
429 __tablename__ = 'user_email_map'
430 __table_args__ = (
430 __table_args__ = (
431 Index('uem_email_idx', 'email'),
431 Index('uem_email_idx', 'email'),
432 UniqueConstraint('email'),
432 UniqueConstraint('email'),
433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
433 {'extend_existing': True, 'mysql_engine': 'InnoDB',
434 'mysql_charset': 'utf8'}
434 'mysql_charset': 'utf8'}
435 )
435 )
436 __mapper_args__ = {}
436 __mapper_args__ = {}
437
437
438 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
439 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
439 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
440 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
440 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
441
441
442 user = relationship('User', lazy='joined')
442 user = relationship('User', lazy='joined')
443
443
444 @validates('_email')
444 @validates('_email')
445 def validate_email(self, key, email):
445 def validate_email(self, key, email):
446 # check if this email is not main one
446 # check if this email is not main one
447 main_email = Session.query(User).filter(User.email == email).scalar()
447 main_email = Session.query(User).filter(User.email == email).scalar()
448 if main_email is not None:
448 if main_email is not None:
449 raise AttributeError('email %s is present is user table' % email)
449 raise AttributeError('email %s is present is user table' % email)
450 return email
450 return email
451
451
452 @hybrid_property
452 @hybrid_property
453 def email(self):
453 def email(self):
454 return self._email
454 return self._email
455
455
456 @email.setter
456 @email.setter
457 def email(self, val):
457 def email(self, val):
458 self._email = val.lower() if val else None
458 self._email = val.lower() if val else None
459
459
460
460
461 class UserLog(Base, BaseModel):
461 class UserLog(Base, BaseModel):
462 __tablename__ = 'user_logs'
462 __tablename__ = 'user_logs'
463 __table_args__ = (
463 __table_args__ = (
464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
465 'mysql_charset': 'utf8'},
465 'mysql_charset': 'utf8'},
466 )
466 )
467 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
469 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
469 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
470 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
470 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
471 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
471 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
472 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
472 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
473 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
473 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
474
474
475 @property
475 @property
476 def action_as_day(self):
476 def action_as_day(self):
477 return datetime.date(*self.action_date.timetuple()[:3])
477 return datetime.date(*self.action_date.timetuple()[:3])
478
478
479 user = relationship('User')
479 user = relationship('User')
480 repository = relationship('Repository', cascade='')
480 repository = relationship('Repository', cascade='')
481
481
482
482
483 class UsersGroup(Base, BaseModel):
483 class UsersGroup(Base, BaseModel):
484 __tablename__ = 'users_groups'
484 __tablename__ = 'users_groups'
485 __table_args__ = (
485 __table_args__ = (
486 {'extend_existing': True, 'mysql_engine': 'InnoDB',
486 {'extend_existing': True, 'mysql_engine': 'InnoDB',
487 'mysql_charset': 'utf8'},
487 'mysql_charset': 'utf8'},
488 )
488 )
489
489
490 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
490 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
491 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
491 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
492 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
492 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
493
493
494 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
494 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
495 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
495 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
496 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
496 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
497
497
498 def __unicode__(self):
498 def __unicode__(self):
499 return u'<userGroup(%s)>' % (self.users_group_name)
499 return u'<userGroup(%s)>' % (self.users_group_name)
500
500
501 @classmethod
501 @classmethod
502 def get_by_group_name(cls, group_name, cache=False,
502 def get_by_group_name(cls, group_name, cache=False,
503 case_insensitive=False):
503 case_insensitive=False):
504 if case_insensitive:
504 if case_insensitive:
505 q = cls.query().filter(cls.users_group_name.ilike(group_name))
505 q = cls.query().filter(cls.users_group_name.ilike(group_name))
506 else:
506 else:
507 q = cls.query().filter(cls.users_group_name == group_name)
507 q = cls.query().filter(cls.users_group_name == group_name)
508 if cache:
508 if cache:
509 q = q.options(FromCache(
509 q = q.options(FromCache(
510 "sql_cache_short",
510 "sql_cache_short",
511 "get_user_%s" % _hash_key(group_name)
511 "get_user_%s" % _hash_key(group_name)
512 )
512 )
513 )
513 )
514 return q.scalar()
514 return q.scalar()
515
515
516 @classmethod
516 @classmethod
517 def get(cls, users_group_id, cache=False):
517 def get(cls, users_group_id, cache=False):
518 users_group = cls.query()
518 users_group = cls.query()
519 if cache:
519 if cache:
520 users_group = users_group.options(FromCache("sql_cache_short",
520 users_group = users_group.options(FromCache("sql_cache_short",
521 "get_users_group_%s" % users_group_id))
521 "get_users_group_%s" % users_group_id))
522 return users_group.get(users_group_id)
522 return users_group.get(users_group_id)
523
523
524
524
525 class UsersGroupMember(Base, BaseModel):
525 class UsersGroupMember(Base, BaseModel):
526 __tablename__ = 'users_groups_members'
526 __tablename__ = 'users_groups_members'
527 __table_args__ = (
527 __table_args__ = (
528 {'extend_existing': True, 'mysql_engine': 'InnoDB',
528 {'extend_existing': True, 'mysql_engine': 'InnoDB',
529 'mysql_charset': 'utf8'},
529 'mysql_charset': 'utf8'},
530 )
530 )
531
531
532 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
532 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
533 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
533 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
534 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
534 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
535
535
536 user = relationship('User', lazy='joined')
536 user = relationship('User', lazy='joined')
537 users_group = relationship('UsersGroup')
537 users_group = relationship('UsersGroup')
538
538
539 def __init__(self, gr_id='', u_id=''):
539 def __init__(self, gr_id='', u_id=''):
540 self.users_group_id = gr_id
540 self.users_group_id = gr_id
541 self.user_id = u_id
541 self.user_id = u_id
542
542
543
543
544 class Repository(Base, BaseModel):
544 class Repository(Base, BaseModel):
545 __tablename__ = 'repositories'
545 __tablename__ = 'repositories'
546 __table_args__ = (
546 __table_args__ = (
547 UniqueConstraint('repo_name'),
547 UniqueConstraint('repo_name'),
548 {'extend_existing': True, 'mysql_engine': 'InnoDB',
548 {'extend_existing': True, 'mysql_engine': 'InnoDB',
549 'mysql_charset': 'utf8'},
549 'mysql_charset': 'utf8'},
550 )
550 )
551
551
552 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
552 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
553 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
553 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
554 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
554 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
555 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
555 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
556 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
556 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
557 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
557 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
558 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
558 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
559 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
559 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
560 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
560 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
561 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
561 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
562 landing_rev = Column("landing_revision", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
562 landing_rev = Column("landing_revision", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
563
563
564 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
564 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
565 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
565 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
566
566
567 user = relationship('User')
567 user = relationship('User')
568 fork = relationship('Repository', remote_side=repo_id)
568 fork = relationship('Repository', remote_side=repo_id)
569 group = relationship('RepoGroup')
569 group = relationship('RepoGroup')
570 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
570 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
571 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
571 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
572 stats = relationship('Statistics', cascade='all', uselist=False)
572 stats = relationship('Statistics', cascade='all', uselist=False)
573
573
574 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
574 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
575
575
576 logs = relationship('UserLog')
576 logs = relationship('UserLog')
577 comments = relationship('ChangesetComment')
577 comments = relationship('ChangesetComment')
578
578
579 def __unicode__(self):
579 def __unicode__(self):
580 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
580 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
581 self.repo_name)
581 self.repo_name)
582
582
583 @classmethod
583 @classmethod
584 def url_sep(cls):
584 def url_sep(cls):
585 return URL_SEP
585 return URL_SEP
586
586
587 @classmethod
587 @classmethod
588 def get_by_repo_name(cls, repo_name):
588 def get_by_repo_name(cls, repo_name):
589 q = Session.query(cls).filter(cls.repo_name == repo_name)
589 q = Session.query(cls).filter(cls.repo_name == repo_name)
590 q = q.options(joinedload(Repository.fork))\
590 q = q.options(joinedload(Repository.fork))\
591 .options(joinedload(Repository.user))\
591 .options(joinedload(Repository.user))\
592 .options(joinedload(Repository.group))
592 .options(joinedload(Repository.group))
593 return q.scalar()
593 return q.scalar()
594
594
595 @classmethod
595 @classmethod
596 def get_by_full_path(cls, repo_full_path):
596 def get_by_full_path(cls, repo_full_path):
597 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
597 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
598 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
598 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
599
599
600 @classmethod
600 @classmethod
601 def get_repo_forks(cls, repo_id):
601 def get_repo_forks(cls, repo_id):
602 return cls.query().filter(Repository.fork_id == repo_id)
602 return cls.query().filter(Repository.fork_id == repo_id)
603
603
604 @classmethod
604 @classmethod
605 def base_path(cls):
605 def base_path(cls):
606 """
606 """
607 Returns base path when all repos are stored
607 Returns base path when all repos are stored
608
608
609 :param cls:
609 :param cls:
610 """
610 """
611 q = Session.query(RhodeCodeUi)\
611 q = Session.query(RhodeCodeUi)\
612 .filter(RhodeCodeUi.ui_key == cls.url_sep())
612 .filter(RhodeCodeUi.ui_key == cls.url_sep())
613 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
613 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
614 return q.one().ui_value
614 return q.one().ui_value
615
615
616 @property
616 @property
617 def forks(self):
617 def forks(self):
618 """
618 """
619 Return forks of this repo
619 Return forks of this repo
620 """
620 """
621 return Repository.get_repo_forks(self.repo_id)
621 return Repository.get_repo_forks(self.repo_id)
622
622
623 @property
623 @property
624 def parent(self):
624 def parent(self):
625 """
625 """
626 Returns fork parent
626 Returns fork parent
627 """
627 """
628 return self.fork
628 return self.fork
629
629
630 @property
630 @property
631 def just_name(self):
631 def just_name(self):
632 return self.repo_name.split(Repository.url_sep())[-1]
632 return self.repo_name.split(Repository.url_sep())[-1]
633
633
634 @property
634 @property
635 def groups_with_parents(self):
635 def groups_with_parents(self):
636 groups = []
636 groups = []
637 if self.group is None:
637 if self.group is None:
638 return groups
638 return groups
639
639
640 cur_gr = self.group
640 cur_gr = self.group
641 groups.insert(0, cur_gr)
641 groups.insert(0, cur_gr)
642 while 1:
642 while 1:
643 gr = getattr(cur_gr, 'parent_group', None)
643 gr = getattr(cur_gr, 'parent_group', None)
644 cur_gr = cur_gr.parent_group
644 cur_gr = cur_gr.parent_group
645 if gr is None:
645 if gr is None:
646 break
646 break
647 groups.insert(0, gr)
647 groups.insert(0, gr)
648
648
649 return groups
649 return groups
650
650
651 @property
651 @property
652 def groups_and_repo(self):
652 def groups_and_repo(self):
653 return self.groups_with_parents, self.just_name
653 return self.groups_with_parents, self.just_name
654
654
655 @LazyProperty
655 @LazyProperty
656 def repo_path(self):
656 def repo_path(self):
657 """
657 """
658 Returns base full path for that repository means where it actually
658 Returns base full path for that repository means where it actually
659 exists on a filesystem
659 exists on a filesystem
660 """
660 """
661 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
661 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
662 Repository.url_sep())
662 Repository.url_sep())
663 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
663 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
664 return q.one().ui_value
664 return q.one().ui_value
665
665
666 @property
666 @property
667 def repo_full_path(self):
667 def repo_full_path(self):
668 p = [self.repo_path]
668 p = [self.repo_path]
669 # we need to split the name by / since this is how we store the
669 # we need to split the name by / since this is how we store the
670 # names in the database, but that eventually needs to be converted
670 # names in the database, but that eventually needs to be converted
671 # into a valid system path
671 # into a valid system path
672 p += self.repo_name.split(Repository.url_sep())
672 p += self.repo_name.split(Repository.url_sep())
673 return os.path.join(*p)
673 return os.path.join(*p)
674
674
675 def get_new_name(self, repo_name):
675 def get_new_name(self, repo_name):
676 """
676 """
677 returns new full repository name based on assigned group and new new
677 returns new full repository name based on assigned group and new new
678
678
679 :param group_name:
679 :param group_name:
680 """
680 """
681 path_prefix = self.group.full_path_splitted if self.group else []
681 path_prefix = self.group.full_path_splitted if self.group else []
682 return Repository.url_sep().join(path_prefix + [repo_name])
682 return Repository.url_sep().join(path_prefix + [repo_name])
683
683
684 @property
684 @property
685 def _ui(self):
685 def _ui(self):
686 """
686 """
687 Creates an db based ui object for this repository
687 Creates an db based ui object for this repository
688 """
688 """
689 from mercurial import ui
689 from mercurial import ui
690 from mercurial import config
690 from mercurial import config
691 baseui = ui.ui()
691 baseui = ui.ui()
692
692
693 #clean the baseui object
693 #clean the baseui object
694 baseui._ocfg = config.config()
694 baseui._ocfg = config.config()
695 baseui._ucfg = config.config()
695 baseui._ucfg = config.config()
696 baseui._tcfg = config.config()
696 baseui._tcfg = config.config()
697
697
698 ret = RhodeCodeUi.query()\
698 ret = RhodeCodeUi.query()\
699 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
699 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
700
700
701 hg_ui = ret
701 hg_ui = ret
702 for ui_ in hg_ui:
702 for ui_ in hg_ui:
703 if ui_.ui_active:
703 if ui_.ui_active:
704 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
704 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
705 ui_.ui_key, ui_.ui_value)
705 ui_.ui_key, ui_.ui_value)
706 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
706 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
707
707
708 return baseui
708 return baseui
709
709
710 @classmethod
710 @classmethod
711 def is_valid(cls, repo_name):
711 def is_valid(cls, repo_name):
712 """
712 """
713 returns True if given repo name is a valid filesystem repository
713 returns True if given repo name is a valid filesystem repository
714
714
715 :param cls:
715 :param cls:
716 :param repo_name:
716 :param repo_name:
717 """
717 """
718 from rhodecode.lib.utils import is_valid_repo
718 from rhodecode.lib.utils import is_valid_repo
719
719
720 return is_valid_repo(repo_name, cls.base_path())
720 return is_valid_repo(repo_name, cls.base_path())
721
721
722 #==========================================================================
722 #==========================================================================
723 # SCM PROPERTIES
723 # SCM PROPERTIES
724 #==========================================================================
724 #==========================================================================
725
725
726 def get_changeset(self, rev=None):
726 def get_changeset(self, rev=None):
727 return get_changeset_safe(self.scm_instance, rev)
727 return get_changeset_safe(self.scm_instance, rev)
728
728
729 @property
729 @property
730 def tip(self):
730 def tip(self):
731 return self.get_changeset('tip')
731 return self.get_changeset('tip')
732
732
733 @property
733 @property
734 def author(self):
734 def author(self):
735 return self.tip.author
735 return self.tip.author
736
736
737 @property
737 @property
738 def last_change(self):
738 def last_change(self):
739 return self.scm_instance.last_change
739 return self.scm_instance.last_change
740
740
741 def comments(self, revisions=None):
741 def comments(self, revisions=None):
742 """
742 """
743 Returns comments for this repository grouped by revisions
743 Returns comments for this repository grouped by revisions
744
744
745 :param revisions: filter query by revisions only
745 :param revisions: filter query by revisions only
746 """
746 """
747 cmts = ChangesetComment.query()\
747 cmts = ChangesetComment.query()\
748 .filter(ChangesetComment.repo == self)
748 .filter(ChangesetComment.repo == self)
749 if revisions:
749 if revisions:
750 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
750 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
751 grouped = defaultdict(list)
751 grouped = defaultdict(list)
752 for cmt in cmts.all():
752 for cmt in cmts.all():
753 grouped[cmt.revision].append(cmt)
753 grouped[cmt.revision].append(cmt)
754 return grouped
754 return grouped
755
755
756 def statuses(self, revisions=None):
756 def statuses(self, revisions=None):
757 """
757 """
758 Returns statuses for this repository
758 Returns statuses for this repository
759
759
760 :param revisions: list of revisions to get statuses for
760 :param revisions: list of revisions to get statuses for
761 :type revisions: list
761 :type revisions: list
762 """
762 """
763
763
764 statuses = ChangesetStatus.query()\
764 statuses = ChangesetStatus.query()\
765 .filter(ChangesetStatus.repo == self)\
765 .filter(ChangesetStatus.repo == self)\
766 .filter(ChangesetStatus.version == 0)
766 .filter(ChangesetStatus.version == 0)
767 if revisions:
767 if revisions:
768 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
768 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
769 grouped = {}
769 grouped = {}
770 for stat in statuses.all():
770 for stat in statuses.all():
771 pr_id = pr_repo = None
771 pr_id = pr_repo = None
772 if stat.pull_request:
772 if stat.pull_request:
773 pr_id = stat.pull_request.pull_request_id
773 pr_id = stat.pull_request.pull_request_id
774 pr_repo = stat.pull_request.other_repo.repo_name
774 pr_repo = stat.pull_request.other_repo.repo_name
775 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
775 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
776 pr_id, pr_repo]
776 pr_id, pr_repo]
777 return grouped
777 return grouped
778
778
779 #==========================================================================
779 #==========================================================================
780 # SCM CACHE INSTANCE
780 # SCM CACHE INSTANCE
781 #==========================================================================
781 #==========================================================================
782
782
783 @property
783 @property
784 def invalidate(self):
784 def invalidate(self):
785 return CacheInvalidation.invalidate(self.repo_name)
785 return CacheInvalidation.invalidate(self.repo_name)
786
786
787 def set_invalidate(self):
787 def set_invalidate(self):
788 """
788 """
789 set a cache for invalidation for this instance
789 set a cache for invalidation for this instance
790 """
790 """
791 CacheInvalidation.set_invalidate(self.repo_name)
791 CacheInvalidation.set_invalidate(self.repo_name)
792
792
793 @LazyProperty
793 @LazyProperty
794 def scm_instance(self):
794 def scm_instance(self):
795 return self.__get_instance()
795 return self.__get_instance()
796
796
797 def scm_instance_cached(self, cache_map=None):
797 def scm_instance_cached(self, cache_map=None):
798 @cache_region('long_term')
798 @cache_region('long_term')
799 def _c(repo_name):
799 def _c(repo_name):
800 return self.__get_instance()
800 return self.__get_instance()
801 rn = self.repo_name
801 rn = self.repo_name
802 log.debug('Getting cached instance of repo')
802 log.debug('Getting cached instance of repo')
803
803
804 if cache_map:
804 if cache_map:
805 # get using prefilled cache_map
805 # get using prefilled cache_map
806 invalidate_repo = cache_map[self.repo_name]
806 invalidate_repo = cache_map[self.repo_name]
807 if invalidate_repo:
807 if invalidate_repo:
808 invalidate_repo = (None if invalidate_repo.cache_active
808 invalidate_repo = (None if invalidate_repo.cache_active
809 else invalidate_repo)
809 else invalidate_repo)
810 else:
810 else:
811 # get from invalidate
811 # get from invalidate
812 invalidate_repo = self.invalidate
812 invalidate_repo = self.invalidate
813
813
814 if invalidate_repo is not None:
814 if invalidate_repo is not None:
815 region_invalidate(_c, None, rn)
815 region_invalidate(_c, None, rn)
816 # update our cache
816 # update our cache
817 CacheInvalidation.set_valid(invalidate_repo.cache_key)
817 CacheInvalidation.set_valid(invalidate_repo.cache_key)
818 return _c(rn)
818 return _c(rn)
819
819
820 def __get_instance(self):
820 def __get_instance(self):
821 repo_full_path = self.repo_full_path
821 repo_full_path = self.repo_full_path
822 try:
822 try:
823 alias = get_scm(repo_full_path)[0]
823 alias = get_scm(repo_full_path)[0]
824 log.debug('Creating instance of %s repository' % alias)
824 log.debug('Creating instance of %s repository' % alias)
825 backend = get_backend(alias)
825 backend = get_backend(alias)
826 except VCSError:
826 except VCSError:
827 log.error(traceback.format_exc())
827 log.error(traceback.format_exc())
828 log.error('Perhaps this repository is in db and not in '
828 log.error('Perhaps this repository is in db and not in '
829 'filesystem run rescan repositories with '
829 'filesystem run rescan repositories with '
830 '"destroy old data " option from admin panel')
830 '"destroy old data " option from admin panel')
831 return
831 return
832
832
833 if alias == 'hg':
833 if alias == 'hg':
834
834
835 repo = backend(safe_str(repo_full_path), create=False,
835 repo = backend(safe_str(repo_full_path), create=False,
836 baseui=self._ui)
836 baseui=self._ui)
837 # skip hidden web repository
837 # skip hidden web repository
838 if repo._get_hidden():
838 if repo._get_hidden():
839 return
839 return
840 else:
840 else:
841 repo = backend(repo_full_path, create=False)
841 repo = backend(repo_full_path, create=False)
842
842
843 return repo
843 return repo
844
844
845
845
846 class RepoGroup(Base, BaseModel):
846 class RepoGroup(Base, BaseModel):
847 __tablename__ = 'groups'
847 __tablename__ = 'groups'
848 __table_args__ = (
848 __table_args__ = (
849 UniqueConstraint('group_name', 'group_parent_id'),
849 UniqueConstraint('group_name', 'group_parent_id'),
850 CheckConstraint('group_id != group_parent_id'),
850 CheckConstraint('group_id != group_parent_id'),
851 {'extend_existing': True, 'mysql_engine': 'InnoDB',
851 {'extend_existing': True, 'mysql_engine': 'InnoDB',
852 'mysql_charset': 'utf8'},
852 'mysql_charset': 'utf8'},
853 )
853 )
854 __mapper_args__ = {'order_by': 'group_name'}
854 __mapper_args__ = {'order_by': 'group_name'}
855
855
856 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
856 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
857 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
857 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
858 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
858 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
859 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
859 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
860
860
861 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
861 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
862 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
862 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
863
863
864 parent_group = relationship('RepoGroup', remote_side=group_id)
864 parent_group = relationship('RepoGroup', remote_side=group_id)
865
865
866 def __init__(self, group_name='', parent_group=None):
866 def __init__(self, group_name='', parent_group=None):
867 self.group_name = group_name
867 self.group_name = group_name
868 self.parent_group = parent_group
868 self.parent_group = parent_group
869
869
870 def __unicode__(self):
870 def __unicode__(self):
871 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
871 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
872 self.group_name)
872 self.group_name)
873
873
874 @classmethod
874 @classmethod
875 def groups_choices(cls):
875 def groups_choices(cls):
876 from webhelpers.html import literal as _literal
876 from webhelpers.html import literal as _literal
877 repo_groups = [('', '')]
877 repo_groups = [('', '')]
878 sep = ' &raquo; '
878 sep = ' &raquo; '
879 _name = lambda k: _literal(sep.join(k))
879 _name = lambda k: _literal(sep.join(k))
880
880
881 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
881 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
882 for x in cls.query().all()])
882 for x in cls.query().all()])
883
883
884 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
884 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
885 return repo_groups
885 return repo_groups
886
886
887 @classmethod
887 @classmethod
888 def url_sep(cls):
888 def url_sep(cls):
889 return URL_SEP
889 return URL_SEP
890
890
891 @classmethod
891 @classmethod
892 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
892 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
893 if case_insensitive:
893 if case_insensitive:
894 gr = cls.query()\
894 gr = cls.query()\
895 .filter(cls.group_name.ilike(group_name))
895 .filter(cls.group_name.ilike(group_name))
896 else:
896 else:
897 gr = cls.query()\
897 gr = cls.query()\
898 .filter(cls.group_name == group_name)
898 .filter(cls.group_name == group_name)
899 if cache:
899 if cache:
900 gr = gr.options(FromCache(
900 gr = gr.options(FromCache(
901 "sql_cache_short",
901 "sql_cache_short",
902 "get_group_%s" % _hash_key(group_name)
902 "get_group_%s" % _hash_key(group_name)
903 )
903 )
904 )
904 )
905 return gr.scalar()
905 return gr.scalar()
906
906
907 @property
907 @property
908 def parents(self):
908 def parents(self):
909 parents_recursion_limit = 5
909 parents_recursion_limit = 5
910 groups = []
910 groups = []
911 if self.parent_group is None:
911 if self.parent_group is None:
912 return groups
912 return groups
913 cur_gr = self.parent_group
913 cur_gr = self.parent_group
914 groups.insert(0, cur_gr)
914 groups.insert(0, cur_gr)
915 cnt = 0
915 cnt = 0
916 while 1:
916 while 1:
917 cnt += 1
917 cnt += 1
918 gr = getattr(cur_gr, 'parent_group', None)
918 gr = getattr(cur_gr, 'parent_group', None)
919 cur_gr = cur_gr.parent_group
919 cur_gr = cur_gr.parent_group
920 if gr is None:
920 if gr is None:
921 break
921 break
922 if cnt == parents_recursion_limit:
922 if cnt == parents_recursion_limit:
923 # this will prevent accidental infinit loops
923 # this will prevent accidental infinit loops
924 log.error('group nested more than %s' %
924 log.error('group nested more than %s' %
925 parents_recursion_limit)
925 parents_recursion_limit)
926 break
926 break
927
927
928 groups.insert(0, gr)
928 groups.insert(0, gr)
929 return groups
929 return groups
930
930
931 @property
931 @property
932 def children(self):
932 def children(self):
933 return RepoGroup.query().filter(RepoGroup.parent_group == self)
933 return RepoGroup.query().filter(RepoGroup.parent_group == self)
934
934
935 @property
935 @property
936 def name(self):
936 def name(self):
937 return self.group_name.split(RepoGroup.url_sep())[-1]
937 return self.group_name.split(RepoGroup.url_sep())[-1]
938
938
939 @property
939 @property
940 def full_path(self):
940 def full_path(self):
941 return self.group_name
941 return self.group_name
942
942
943 @property
943 @property
944 def full_path_splitted(self):
944 def full_path_splitted(self):
945 return self.group_name.split(RepoGroup.url_sep())
945 return self.group_name.split(RepoGroup.url_sep())
946
946
947 @property
947 @property
948 def repositories(self):
948 def repositories(self):
949 return Repository.query()\
949 return Repository.query()\
950 .filter(Repository.group == self)\
950 .filter(Repository.group == self)\
951 .order_by(Repository.repo_name)
951 .order_by(Repository.repo_name)
952
952
953 @property
953 @property
954 def repositories_recursive_count(self):
954 def repositories_recursive_count(self):
955 cnt = self.repositories.count()
955 cnt = self.repositories.count()
956
956
957 def children_count(group):
957 def children_count(group):
958 cnt = 0
958 cnt = 0
959 for child in group.children:
959 for child in group.children:
960 cnt += child.repositories.count()
960 cnt += child.repositories.count()
961 cnt += children_count(child)
961 cnt += children_count(child)
962 return cnt
962 return cnt
963
963
964 return cnt + children_count(self)
964 return cnt + children_count(self)
965
965
966 def get_new_name(self, group_name):
966 def get_new_name(self, group_name):
967 """
967 """
968 returns new full group name based on parent and new name
968 returns new full group name based on parent and new name
969
969
970 :param group_name:
970 :param group_name:
971 """
971 """
972 path_prefix = (self.parent_group.full_path_splitted if
972 path_prefix = (self.parent_group.full_path_splitted if
973 self.parent_group else [])
973 self.parent_group else [])
974 return RepoGroup.url_sep().join(path_prefix + [group_name])
974 return RepoGroup.url_sep().join(path_prefix + [group_name])
975
975
976
976
977 class Permission(Base, BaseModel):
977 class Permission(Base, BaseModel):
978 __tablename__ = 'permissions'
978 __tablename__ = 'permissions'
979 __table_args__ = (
979 __table_args__ = (
980 Index('p_perm_name_idx', 'permission_name'),
980 Index('p_perm_name_idx', 'permission_name'),
981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
981 {'extend_existing': True, 'mysql_engine': 'InnoDB',
982 'mysql_charset': 'utf8'},
982 'mysql_charset': 'utf8'},
983 )
983 )
984 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
984 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
985 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
985 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
986 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
986 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
987
987
988 def __unicode__(self):
988 def __unicode__(self):
989 return u"<%s('%s:%s')>" % (
989 return u"<%s('%s:%s')>" % (
990 self.__class__.__name__, self.permission_id, self.permission_name
990 self.__class__.__name__, self.permission_id, self.permission_name
991 )
991 )
992
992
993 @classmethod
993 @classmethod
994 def get_by_key(cls, key):
994 def get_by_key(cls, key):
995 return cls.query().filter(cls.permission_name == key).scalar()
995 return cls.query().filter(cls.permission_name == key).scalar()
996
996
997 @classmethod
997 @classmethod
998 def get_default_perms(cls, default_user_id):
998 def get_default_perms(cls, default_user_id):
999 q = Session.query(UserRepoToPerm, Repository, cls)\
999 q = Session.query(UserRepoToPerm, Repository, cls)\
1000 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1000 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1001 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1001 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1002 .filter(UserRepoToPerm.user_id == default_user_id)
1002 .filter(UserRepoToPerm.user_id == default_user_id)
1003
1003
1004 return q.all()
1004 return q.all()
1005
1005
1006 @classmethod
1006 @classmethod
1007 def get_default_group_perms(cls, default_user_id):
1007 def get_default_group_perms(cls, default_user_id):
1008 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
1008 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
1009 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1009 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1010 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1010 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1011 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1011 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1012
1012
1013 return q.all()
1013 return q.all()
1014
1014
1015
1015
1016 class UserRepoToPerm(Base, BaseModel):
1016 class UserRepoToPerm(Base, BaseModel):
1017 __tablename__ = 'repo_to_perm'
1017 __tablename__ = 'repo_to_perm'
1018 __table_args__ = (
1018 __table_args__ = (
1019 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1019 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1020 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1020 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1021 'mysql_charset': 'utf8'}
1021 'mysql_charset': 'utf8'}
1022 )
1022 )
1023 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1023 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1024 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1024 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1025 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1025 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1026 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1026 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1027
1027
1028 user = relationship('User')
1028 user = relationship('User')
1029 repository = relationship('Repository')
1029 repository = relationship('Repository')
1030 permission = relationship('Permission')
1030 permission = relationship('Permission')
1031
1031
1032 @classmethod
1032 @classmethod
1033 def create(cls, user, repository, permission):
1033 def create(cls, user, repository, permission):
1034 n = cls()
1034 n = cls()
1035 n.user = user
1035 n.user = user
1036 n.repository = repository
1036 n.repository = repository
1037 n.permission = permission
1037 n.permission = permission
1038 Session.add(n)
1038 Session.add(n)
1039 return n
1039 return n
1040
1040
1041 def __unicode__(self):
1041 def __unicode__(self):
1042 return u'<user:%s => %s >' % (self.user, self.repository)
1042 return u'<user:%s => %s >' % (self.user, self.repository)
1043
1043
1044
1044
1045 class UserToPerm(Base, BaseModel):
1045 class UserToPerm(Base, BaseModel):
1046 __tablename__ = 'user_to_perm'
1046 __tablename__ = 'user_to_perm'
1047 __table_args__ = (
1047 __table_args__ = (
1048 UniqueConstraint('user_id', 'permission_id'),
1048 UniqueConstraint('user_id', 'permission_id'),
1049 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1049 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1050 'mysql_charset': 'utf8'}
1050 'mysql_charset': 'utf8'}
1051 )
1051 )
1052 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1052 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1053 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1054 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1054 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1055
1055
1056 user = relationship('User')
1056 user = relationship('User')
1057 permission = relationship('Permission', lazy='joined')
1057 permission = relationship('Permission', lazy='joined')
1058
1058
1059
1059
1060 class UsersGroupRepoToPerm(Base, BaseModel):
1060 class UsersGroupRepoToPerm(Base, BaseModel):
1061 __tablename__ = 'users_group_repo_to_perm'
1061 __tablename__ = 'users_group_repo_to_perm'
1062 __table_args__ = (
1062 __table_args__ = (
1063 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1063 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1064 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1064 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1065 'mysql_charset': 'utf8'}
1065 'mysql_charset': 'utf8'}
1066 )
1066 )
1067 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1067 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1068 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1068 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1069 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1069 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1070 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1070 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1071
1071
1072 users_group = relationship('UsersGroup')
1072 users_group = relationship('UsersGroup')
1073 permission = relationship('Permission')
1073 permission = relationship('Permission')
1074 repository = relationship('Repository')
1074 repository = relationship('Repository')
1075
1075
1076 @classmethod
1076 @classmethod
1077 def create(cls, users_group, repository, permission):
1077 def create(cls, users_group, repository, permission):
1078 n = cls()
1078 n = cls()
1079 n.users_group = users_group
1079 n.users_group = users_group
1080 n.repository = repository
1080 n.repository = repository
1081 n.permission = permission
1081 n.permission = permission
1082 Session.add(n)
1082 Session.add(n)
1083 return n
1083 return n
1084
1084
1085 def __unicode__(self):
1085 def __unicode__(self):
1086 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1086 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1087
1087
1088
1088
1089 class UsersGroupToPerm(Base, BaseModel):
1089 class UsersGroupToPerm(Base, BaseModel):
1090 __tablename__ = 'users_group_to_perm'
1090 __tablename__ = 'users_group_to_perm'
1091 __table_args__ = (
1091 __table_args__ = (
1092 UniqueConstraint('users_group_id', 'permission_id',),
1092 UniqueConstraint('users_group_id', 'permission_id',),
1093 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1093 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1094 'mysql_charset': 'utf8'}
1094 'mysql_charset': 'utf8'}
1095 )
1095 )
1096 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1096 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1097 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1097 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1098 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1098 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1099
1099
1100 users_group = relationship('UsersGroup')
1100 users_group = relationship('UsersGroup')
1101 permission = relationship('Permission')
1101 permission = relationship('Permission')
1102
1102
1103
1103
1104 class UserRepoGroupToPerm(Base, BaseModel):
1104 class UserRepoGroupToPerm(Base, BaseModel):
1105 __tablename__ = 'user_repo_group_to_perm'
1105 __tablename__ = 'user_repo_group_to_perm'
1106 __table_args__ = (
1106 __table_args__ = (
1107 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1107 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1108 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1108 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1109 'mysql_charset': 'utf8'}
1109 'mysql_charset': 'utf8'}
1110 )
1110 )
1111
1111
1112 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1112 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1113 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1113 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1114 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1114 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1115 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1115 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1116
1116
1117 user = relationship('User')
1117 user = relationship('User')
1118 group = relationship('RepoGroup')
1118 group = relationship('RepoGroup')
1119 permission = relationship('Permission')
1119 permission = relationship('Permission')
1120
1120
1121
1121
1122 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1122 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1123 __tablename__ = 'users_group_repo_group_to_perm'
1123 __tablename__ = 'users_group_repo_group_to_perm'
1124 __table_args__ = (
1124 __table_args__ = (
1125 UniqueConstraint('users_group_id', 'group_id'),
1125 UniqueConstraint('users_group_id', 'group_id'),
1126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1127 'mysql_charset': 'utf8'}
1127 'mysql_charset': 'utf8'}
1128 )
1128 )
1129
1129
1130 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)
1130 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)
1131 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1131 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1132 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1132 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1133 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1133 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1134
1134
1135 users_group = relationship('UsersGroup')
1135 users_group = relationship('UsersGroup')
1136 permission = relationship('Permission')
1136 permission = relationship('Permission')
1137 group = relationship('RepoGroup')
1137 group = relationship('RepoGroup')
1138
1138
1139
1139
1140 class Statistics(Base, BaseModel):
1140 class Statistics(Base, BaseModel):
1141 __tablename__ = 'statistics'
1141 __tablename__ = 'statistics'
1142 __table_args__ = (
1142 __table_args__ = (
1143 UniqueConstraint('repository_id'),
1143 UniqueConstraint('repository_id'),
1144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1144 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1145 'mysql_charset': 'utf8'}
1145 'mysql_charset': 'utf8'}
1146 )
1146 )
1147 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1147 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1148 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1148 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1149 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1149 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1150 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1150 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1151 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1151 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1152 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1152 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1153
1153
1154 repository = relationship('Repository', single_parent=True)
1154 repository = relationship('Repository', single_parent=True)
1155
1155
1156
1156
1157 class UserFollowing(Base, BaseModel):
1157 class UserFollowing(Base, BaseModel):
1158 __tablename__ = 'user_followings'
1158 __tablename__ = 'user_followings'
1159 __table_args__ = (
1159 __table_args__ = (
1160 UniqueConstraint('user_id', 'follows_repository_id'),
1160 UniqueConstraint('user_id', 'follows_repository_id'),
1161 UniqueConstraint('user_id', 'follows_user_id'),
1161 UniqueConstraint('user_id', 'follows_user_id'),
1162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1162 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1163 'mysql_charset': 'utf8'}
1163 'mysql_charset': 'utf8'}
1164 )
1164 )
1165
1165
1166 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1166 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1167 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1167 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1168 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1168 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1169 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1169 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1170 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1170 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1171
1171
1172 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1172 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1173
1173
1174 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1174 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1175 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1175 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1176
1176
1177 @classmethod
1177 @classmethod
1178 def get_repo_followers(cls, repo_id):
1178 def get_repo_followers(cls, repo_id):
1179 return cls.query().filter(cls.follows_repo_id == repo_id)
1179 return cls.query().filter(cls.follows_repo_id == repo_id)
1180
1180
1181
1181
1182 class CacheInvalidation(Base, BaseModel):
1182 class CacheInvalidation(Base, BaseModel):
1183 __tablename__ = 'cache_invalidation'
1183 __tablename__ = 'cache_invalidation'
1184 __table_args__ = (
1184 __table_args__ = (
1185 UniqueConstraint('cache_key'),
1185 UniqueConstraint('cache_key'),
1186 Index('key_idx', 'cache_key'),
1186 Index('key_idx', 'cache_key'),
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1187 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1188 'mysql_charset': 'utf8'},
1188 'mysql_charset': 'utf8'},
1189 )
1189 )
1190 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1190 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1191 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1191 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1192 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1192 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1193 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1193 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1194
1194
1195 def __init__(self, cache_key, cache_args=''):
1195 def __init__(self, cache_key, cache_args=''):
1196 self.cache_key = cache_key
1196 self.cache_key = cache_key
1197 self.cache_args = cache_args
1197 self.cache_args = cache_args
1198 self.cache_active = False
1198 self.cache_active = False
1199
1199
1200 def __unicode__(self):
1200 def __unicode__(self):
1201 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1201 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1202 self.cache_id, self.cache_key)
1202 self.cache_id, self.cache_key)
1203
1203
1204 @classmethod
1204 @classmethod
1205 def clear_cache(cls):
1205 def clear_cache(cls):
1206 cls.query().delete()
1206 cls.query().delete()
1207
1207
1208 @classmethod
1208 @classmethod
1209 def _get_key(cls, key):
1209 def _get_key(cls, key):
1210 """
1210 """
1211 Wrapper for generating a key, together with a prefix
1211 Wrapper for generating a key, together with a prefix
1212
1212
1213 :param key:
1213 :param key:
1214 """
1214 """
1215 import rhodecode
1215 import rhodecode
1216 prefix = ''
1216 prefix = ''
1217 iid = rhodecode.CONFIG.get('instance_id')
1217 iid = rhodecode.CONFIG.get('instance_id')
1218 if iid:
1218 if iid:
1219 prefix = iid
1219 prefix = iid
1220 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1220 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1221
1221
1222 @classmethod
1222 @classmethod
1223 def get_by_key(cls, key):
1223 def get_by_key(cls, key):
1224 return cls.query().filter(cls.cache_key == key).scalar()
1224 return cls.query().filter(cls.cache_key == key).scalar()
1225
1225
1226 @classmethod
1226 @classmethod
1227 def _get_or_create_key(cls, key, prefix, org_key):
1227 def _get_or_create_key(cls, key, prefix, org_key):
1228 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1228 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1229 if not inv_obj:
1229 if not inv_obj:
1230 try:
1230 try:
1231 inv_obj = CacheInvalidation(key, org_key)
1231 inv_obj = CacheInvalidation(key, org_key)
1232 Session.add(inv_obj)
1232 Session.add(inv_obj)
1233 Session.commit()
1233 Session.commit()
1234 except Exception:
1234 except Exception:
1235 log.error(traceback.format_exc())
1235 log.error(traceback.format_exc())
1236 Session.rollback()
1236 Session.rollback()
1237 return inv_obj
1237 return inv_obj
1238
1238
1239 @classmethod
1239 @classmethod
1240 def invalidate(cls, key):
1240 def invalidate(cls, key):
1241 """
1241 """
1242 Returns Invalidation object if this given key should be invalidated
1242 Returns Invalidation object if this given key should be invalidated
1243 None otherwise. `cache_active = False` means that this cache
1243 None otherwise. `cache_active = False` means that this cache
1244 state is not valid and needs to be invalidated
1244 state is not valid and needs to be invalidated
1245
1245
1246 :param key:
1246 :param key:
1247 """
1247 """
1248
1248
1249 key, _prefix, _org_key = cls._get_key(key)
1249 key, _prefix, _org_key = cls._get_key(key)
1250 inv = cls._get_or_create_key(key, _prefix, _org_key)
1250 inv = cls._get_or_create_key(key, _prefix, _org_key)
1251
1251
1252 if inv and inv.cache_active is False:
1252 if inv and inv.cache_active is False:
1253 return inv
1253 return inv
1254
1254
1255 @classmethod
1255 @classmethod
1256 def set_invalidate(cls, key):
1256 def set_invalidate(cls, key):
1257 """
1257 """
1258 Mark this Cache key for invalidation
1258 Mark this Cache key for invalidation
1259
1259
1260 :param key:
1260 :param key:
1261 """
1261 """
1262
1262
1263 key, _prefix, _org_key = cls._get_key(key)
1263 key, _prefix, _org_key = cls._get_key(key)
1264 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1264 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1265 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1265 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1266 _org_key))
1266 _org_key))
1267 try:
1267 try:
1268 for inv_obj in inv_objs:
1268 for inv_obj in inv_objs:
1269 if inv_obj:
1269 if inv_obj:
1270 inv_obj.cache_active = False
1270 inv_obj.cache_active = False
1271
1271
1272 Session.add(inv_obj)
1272 Session.add(inv_obj)
1273 Session.commit()
1273 Session.commit()
1274 except Exception:
1274 except Exception:
1275 log.error(traceback.format_exc())
1275 log.error(traceback.format_exc())
1276 Session.rollback()
1276 Session.rollback()
1277
1277
1278 @classmethod
1278 @classmethod
1279 def set_valid(cls, key):
1279 def set_valid(cls, key):
1280 """
1280 """
1281 Mark this cache key as active and currently cached
1281 Mark this cache key as active and currently cached
1282
1282
1283 :param key:
1283 :param key:
1284 """
1284 """
1285 inv_obj = cls.get_by_key(key)
1285 inv_obj = cls.get_by_key(key)
1286 inv_obj.cache_active = True
1286 inv_obj.cache_active = True
1287 Session.add(inv_obj)
1287 Session.add(inv_obj)
1288 Session.commit()
1288 Session.commit()
1289
1289
1290 @classmethod
1290 @classmethod
1291 def get_cache_map(cls):
1291 def get_cache_map(cls):
1292
1292
1293 class cachemapdict(dict):
1293 class cachemapdict(dict):
1294
1294
1295 def __init__(self, *args, **kwargs):
1295 def __init__(self, *args, **kwargs):
1296 fixkey = kwargs.get('fixkey')
1296 fixkey = kwargs.get('fixkey')
1297 if fixkey:
1297 if fixkey:
1298 del kwargs['fixkey']
1298 del kwargs['fixkey']
1299 self.fixkey = fixkey
1299 self.fixkey = fixkey
1300 super(cachemapdict, self).__init__(*args, **kwargs)
1300 super(cachemapdict, self).__init__(*args, **kwargs)
1301
1301
1302 def __getattr__(self, name):
1302 def __getattr__(self, name):
1303 key = name
1303 key = name
1304 if self.fixkey:
1304 if self.fixkey:
1305 key, _prefix, _org_key = cls._get_key(key)
1305 key, _prefix, _org_key = cls._get_key(key)
1306 if key in self.__dict__:
1306 if key in self.__dict__:
1307 return self.__dict__[key]
1307 return self.__dict__[key]
1308 else:
1308 else:
1309 return self[key]
1309 return self[key]
1310
1310
1311 def __getitem__(self, key):
1311 def __getitem__(self, key):
1312 if self.fixkey:
1312 if self.fixkey:
1313 key, _prefix, _org_key = cls._get_key(key)
1313 key, _prefix, _org_key = cls._get_key(key)
1314 try:
1314 try:
1315 return super(cachemapdict, self).__getitem__(key)
1315 return super(cachemapdict, self).__getitem__(key)
1316 except KeyError:
1316 except KeyError:
1317 return
1317 return
1318
1318
1319 cache_map = cachemapdict(fixkey=True)
1319 cache_map = cachemapdict(fixkey=True)
1320 for obj in cls.query().all():
1320 for obj in cls.query().all():
1321 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1321 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1322 return cache_map
1322 return cache_map
1323
1323
1324
1324
1325 class ChangesetComment(Base, BaseModel):
1325 class ChangesetComment(Base, BaseModel):
1326 __tablename__ = 'changeset_comments'
1326 __tablename__ = 'changeset_comments'
1327 __table_args__ = (
1327 __table_args__ = (
1328 Index('cc_revision_idx', 'revision'),
1328 Index('cc_revision_idx', 'revision'),
1329 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1329 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1330 'mysql_charset': 'utf8'},
1330 'mysql_charset': 'utf8'},
1331 )
1331 )
1332 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1332 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1333 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1333 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1334 revision = Column('revision', String(40), nullable=True)
1334 revision = Column('revision', String(40), nullable=True)
1335 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1335 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1336 line_no = Column('line_no', Unicode(10), nullable=True)
1336 line_no = Column('line_no', Unicode(10), nullable=True)
1337 f_path = Column('f_path', Unicode(1000), nullable=True)
1337 f_path = Column('f_path', Unicode(1000), nullable=True)
1338 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1338 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1339 text = Column('text', Unicode(25000), nullable=False)
1339 text = Column('text', Unicode(25000), nullable=False)
1340 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1340 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1341
1341
1342 author = relationship('User', lazy='joined')
1342 author = relationship('User', lazy='joined')
1343 repo = relationship('Repository')
1343 repo = relationship('Repository')
1344 status_change = relationship('ChangesetStatus', uselist=False)
1344 status_change = relationship('ChangesetStatus', uselist=False)
1345 pull_request = relationship('PullRequest', lazy='joined')
1345 pull_request = relationship('PullRequest', lazy='joined')
1346
1346
1347 @classmethod
1347 @classmethod
1348 def get_users(cls, revision=None, pull_request_id=None):
1348 def get_users(cls, revision=None, pull_request_id=None):
1349 """
1349 """
1350 Returns user associated with this ChangesetComment. ie those
1350 Returns user associated with this ChangesetComment. ie those
1351 who actually commented
1351 who actually commented
1352
1352
1353 :param cls:
1353 :param cls:
1354 :param revision:
1354 :param revision:
1355 """
1355 """
1356 q = Session.query(User)\
1356 q = Session.query(User)\
1357 .join(ChangesetComment.author)
1357 .join(ChangesetComment.author)
1358 if revision:
1358 if revision:
1359 q = q.filter(cls.revision == revision)
1359 q = q.filter(cls.revision == revision)
1360 elif pull_request_id:
1360 elif pull_request_id:
1361 q = q.filter(cls.pull_request_id == pull_request_id)
1361 q = q.filter(cls.pull_request_id == pull_request_id)
1362 return q.all()
1362 return q.all()
1363
1363
1364
1364
1365 class ChangesetStatus(Base, BaseModel):
1365 class ChangesetStatus(Base, BaseModel):
1366 __tablename__ = 'changeset_statuses'
1366 __tablename__ = 'changeset_statuses'
1367 __table_args__ = (
1367 __table_args__ = (
1368 Index('cs_revision_idx', 'revision'),
1368 Index('cs_revision_idx', 'revision'),
1369 Index('cs_version_idx', 'version'),
1369 Index('cs_version_idx', 'version'),
1370 UniqueConstraint('repo_id', 'revision', 'version'),
1370 UniqueConstraint('repo_id', 'revision', 'version'),
1371 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1371 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1372 'mysql_charset': 'utf8'}
1372 'mysql_charset': 'utf8'}
1373 )
1373 )
1374 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1375 STATUS_APPROVED = 'approved'
1376 STATUS_REJECTED = 'rejected'
1377 STATUS_UNDER_REVIEW = 'under_review'
1374
1378
1375 STATUSES = [
1379 STATUSES = [
1376 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1380 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1377 ('approved', _("Approved")),
1381 (STATUS_APPROVED, _("Approved")),
1378 ('rejected', _("Rejected")),
1382 (STATUS_REJECTED, _("Rejected")),
1379 ('under_review', _("Under Review")),
1383 (STATUS_UNDER_REVIEW, _("Under Review")),
1380 ]
1384 ]
1381 DEFAULT = STATUSES[0][0]
1382
1385
1383 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1386 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1384 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1387 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1388 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1386 revision = Column('revision', String(40), nullable=False)
1389 revision = Column('revision', String(40), nullable=False)
1387 status = Column('status', String(128), nullable=False, default=DEFAULT)
1390 status = Column('status', String(128), nullable=False, default=DEFAULT)
1388 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1391 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1389 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1392 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1390 version = Column('version', Integer(), nullable=False, default=0)
1393 version = Column('version', Integer(), nullable=False, default=0)
1391 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1394 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1392
1395
1393 author = relationship('User', lazy='joined')
1396 author = relationship('User', lazy='joined')
1394 repo = relationship('Repository')
1397 repo = relationship('Repository')
1395 comment = relationship('ChangesetComment', lazy='joined')
1398 comment = relationship('ChangesetComment', lazy='joined')
1396 pull_request = relationship('PullRequest', lazy='joined')
1399 pull_request = relationship('PullRequest', lazy='joined')
1397
1400
1401 def __unicode__(self):
1402 return u"<%s('%s:%s')>" % (
1403 self.__class__.__name__,
1404 self.status, self.author
1405 )
1406
1398 @classmethod
1407 @classmethod
1399 def get_status_lbl(cls, value):
1408 def get_status_lbl(cls, value):
1400 return dict(cls.STATUSES).get(value)
1409 return dict(cls.STATUSES).get(value)
1401
1410
1402 @property
1411 @property
1403 def status_lbl(self):
1412 def status_lbl(self):
1404 return ChangesetStatus.get_status_lbl(self.status)
1413 return ChangesetStatus.get_status_lbl(self.status)
1405
1414
1406
1415
1407 class PullRequest(Base, BaseModel):
1416 class PullRequest(Base, BaseModel):
1408 __tablename__ = 'pull_requests'
1417 __tablename__ = 'pull_requests'
1409 __table_args__ = (
1418 __table_args__ = (
1410 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1411 'mysql_charset': 'utf8'},
1420 'mysql_charset': 'utf8'},
1412 )
1421 )
1413
1422
1414 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1423 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1415 title = Column('title', Unicode(256), nullable=True)
1424 title = Column('title', Unicode(256), nullable=True)
1416 description = Column('description', Unicode(10240), nullable=True)
1425 description = Column('description', Unicode(10240), nullable=True)
1417 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1426 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1418 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1427 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1419 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1428 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1420 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1429 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1421 org_ref = Column('org_ref', Unicode(256), nullable=False)
1430 org_ref = Column('org_ref', Unicode(256), nullable=False)
1422 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1431 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1423 other_ref = Column('other_ref', Unicode(256), nullable=False)
1432 other_ref = Column('other_ref', Unicode(256), nullable=False)
1424
1433
1425 @hybrid_property
1434 @hybrid_property
1426 def revisions(self):
1435 def revisions(self):
1427 return self._revisions.split(':')
1436 return self._revisions.split(':')
1428
1437
1429 @revisions.setter
1438 @revisions.setter
1430 def revisions(self, val):
1439 def revisions(self, val):
1431 self._revisions = ':'.join(val)
1440 self._revisions = ':'.join(val)
1432
1441
1433 author = relationship('User', lazy='joined')
1442 author = relationship('User', lazy='joined')
1434 reviewers = relationship('PullRequestReviewers')
1443 reviewers = relationship('PullRequestReviewers')
1435 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1444 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1436 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1445 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1437
1446
1438 def __json__(self):
1447 def __json__(self):
1439 return dict(
1448 return dict(
1440 revisions=self.revisions
1449 revisions=self.revisions
1441 )
1450 )
1442
1451
1443
1452
1444 class PullRequestReviewers(Base, BaseModel):
1453 class PullRequestReviewers(Base, BaseModel):
1445 __tablename__ = 'pull_request_reviewers'
1454 __tablename__ = 'pull_request_reviewers'
1446 __table_args__ = (
1455 __table_args__ = (
1447 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1456 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1448 'mysql_charset': 'utf8'},
1457 'mysql_charset': 'utf8'},
1449 )
1458 )
1450
1459
1451 def __init__(self, user=None, pull_request=None):
1460 def __init__(self, user=None, pull_request=None):
1452 self.user = user
1461 self.user = user
1453 self.pull_request = pull_request
1462 self.pull_request = pull_request
1454
1463
1455 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1464 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1456 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1465 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1457 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1458
1467
1459 user = relationship('User')
1468 user = relationship('User')
1460 pull_request = relationship('PullRequest')
1469 pull_request = relationship('PullRequest')
1461
1470
1462
1471
1463 class Notification(Base, BaseModel):
1472 class Notification(Base, BaseModel):
1464 __tablename__ = 'notifications'
1473 __tablename__ = 'notifications'
1465 __table_args__ = (
1474 __table_args__ = (
1466 Index('notification_type_idx', 'type'),
1475 Index('notification_type_idx', 'type'),
1467 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1476 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1468 'mysql_charset': 'utf8'},
1477 'mysql_charset': 'utf8'},
1469 )
1478 )
1470
1479
1471 TYPE_CHANGESET_COMMENT = u'cs_comment'
1480 TYPE_CHANGESET_COMMENT = u'cs_comment'
1472 TYPE_MESSAGE = u'message'
1481 TYPE_MESSAGE = u'message'
1473 TYPE_MENTION = u'mention'
1482 TYPE_MENTION = u'mention'
1474 TYPE_REGISTRATION = u'registration'
1483 TYPE_REGISTRATION = u'registration'
1475 TYPE_PULL_REQUEST = u'pull_request'
1484 TYPE_PULL_REQUEST = u'pull_request'
1476 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1485 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1477
1486
1478 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1487 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1479 subject = Column('subject', Unicode(512), nullable=True)
1488 subject = Column('subject', Unicode(512), nullable=True)
1480 body = Column('body', Unicode(50000), nullable=True)
1489 body = Column('body', Unicode(50000), nullable=True)
1481 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1490 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1482 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1491 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1483 type_ = Column('type', Unicode(256))
1492 type_ = Column('type', Unicode(256))
1484
1493
1485 created_by_user = relationship('User')
1494 created_by_user = relationship('User')
1486 notifications_to_users = relationship('UserNotification', lazy='joined',
1495 notifications_to_users = relationship('UserNotification', lazy='joined',
1487 cascade="all, delete, delete-orphan")
1496 cascade="all, delete, delete-orphan")
1488
1497
1489 @property
1498 @property
1490 def recipients(self):
1499 def recipients(self):
1491 return [x.user for x in UserNotification.query()\
1500 return [x.user for x in UserNotification.query()\
1492 .filter(UserNotification.notification == self)\
1501 .filter(UserNotification.notification == self)\
1493 .order_by(UserNotification.user).all()]
1502 .order_by(UserNotification.user).all()]
1494
1503
1495 @classmethod
1504 @classmethod
1496 def create(cls, created_by, subject, body, recipients, type_=None):
1505 def create(cls, created_by, subject, body, recipients, type_=None):
1497 if type_ is None:
1506 if type_ is None:
1498 type_ = Notification.TYPE_MESSAGE
1507 type_ = Notification.TYPE_MESSAGE
1499
1508
1500 notification = cls()
1509 notification = cls()
1501 notification.created_by_user = created_by
1510 notification.created_by_user = created_by
1502 notification.subject = subject
1511 notification.subject = subject
1503 notification.body = body
1512 notification.body = body
1504 notification.type_ = type_
1513 notification.type_ = type_
1505 notification.created_on = datetime.datetime.now()
1514 notification.created_on = datetime.datetime.now()
1506
1515
1507 for u in recipients:
1516 for u in recipients:
1508 assoc = UserNotification()
1517 assoc = UserNotification()
1509 assoc.notification = notification
1518 assoc.notification = notification
1510 u.notifications.append(assoc)
1519 u.notifications.append(assoc)
1511 Session.add(notification)
1520 Session.add(notification)
1512 return notification
1521 return notification
1513
1522
1514 @property
1523 @property
1515 def description(self):
1524 def description(self):
1516 from rhodecode.model.notification import NotificationModel
1525 from rhodecode.model.notification import NotificationModel
1517 return NotificationModel().make_description(self)
1526 return NotificationModel().make_description(self)
1518
1527
1519
1528
1520 class UserNotification(Base, BaseModel):
1529 class UserNotification(Base, BaseModel):
1521 __tablename__ = 'user_to_notification'
1530 __tablename__ = 'user_to_notification'
1522 __table_args__ = (
1531 __table_args__ = (
1523 UniqueConstraint('user_id', 'notification_id'),
1532 UniqueConstraint('user_id', 'notification_id'),
1524 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1533 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1525 'mysql_charset': 'utf8'}
1534 'mysql_charset': 'utf8'}
1526 )
1535 )
1527 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1536 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1528 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1537 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1529 read = Column('read', Boolean, default=False)
1538 read = Column('read', Boolean, default=False)
1530 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1539 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1531
1540
1532 user = relationship('User', lazy="joined")
1541 user = relationship('User', lazy="joined")
1533 notification = relationship('Notification', lazy="joined",
1542 notification = relationship('Notification', lazy="joined",
1534 order_by=lambda: Notification.created_on.desc(),)
1543 order_by=lambda: Notification.created_on.desc(),)
1535
1544
1536 def mark_as_read(self):
1545 def mark_as_read(self):
1537 self.read = True
1546 self.read = True
1538 Session.add(self)
1547 Session.add(self)
1539
1548
1540
1549
1541 class DbMigrateVersion(Base, BaseModel):
1550 class DbMigrateVersion(Base, BaseModel):
1542 __tablename__ = 'db_migrate_version'
1551 __tablename__ = 'db_migrate_version'
1543 __table_args__ = (
1552 __table_args__ = (
1544 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1545 'mysql_charset': 'utf8'},
1554 'mysql_charset': 'utf8'},
1546 )
1555 )
1547 repository_id = Column('repository_id', String(250), primary_key=True)
1556 repository_id = Column('repository_id', String(250), primary_key=True)
1548 repository_path = Column('repository_path', Text)
1557 repository_path = Column('repository_path', Text)
1549 version = Column('version', Integer)
1558 version = Column('version', Integer)
@@ -1,83 +1,107 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
4 ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
12 ${_('Pull request #%s') % c.pull_request.pull_request_id}
13 </%def>
13 </%def>
14
14
15 <%def name="main()">
15 <%def name="main()">
16
16
17 <div class="box">
17 <div class="box">
18 <!-- box / title -->
18 <!-- box / title -->
19 <div class="title">
19 <div class="title">
20 ${self.breadcrumbs()}
20 ${self.breadcrumbs()}
21 </div>
21 </div>
22
22
23 <h3>${_('Title')}: ${c.pull_request.title}</h3>
23 <h3>${_('Title')}: ${c.pull_request.title}</h3>
24 <div class="changeset-status-container" style="float:left;padding:0px 20px 20px 20px">
24 <div class="changeset-status-container" style="float:left;padding:0px 20px 0px 20px">
25 %if c.current_changeset_status:
25 %if c.current_changeset_status:
26 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
26 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
27 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
27 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
28 %endif
28 %endif
29 </div>
29 </div>
30 <div style="padding:4px">
30 <div style="padding:4px 4px 10px 4px">
31 <div>${h.fmt_date(c.pull_request.created_on)}</div>
31 <div>${h.fmt_date(c.pull_request.created_on)}</div>
32 </div>
32 </div>
33
33
34 ##DIFF
34 ## REVIEWERS
35
35 <div>
36 <div class="table">
36 <div class="table" style="float:right;width:46%;clear:none">
37 <div id="body" class="diffblock">
37 <div id="body" class="diffblock">
38 <div style="white-space:pre-wrap;padding:5px">${h.literal(c.pull_request.description)}</div>
38 <div style="white-space:pre-wrap;padding:5px">${_('Pull request reviewers')}</div>
39 </div>
39 </div>
40 <div id="changeset_compare_view_content">
40 <div style="border: 1px solid #CCC">
41 ##CS
41 <div class="group_members_wrap">
42 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
42 <ul class="group_members">
43 <%include file="/compare/compare_cs.html" />
43 %for user,status in c.pull_request_reviewers:
44
44 <li>
45 ## FILES
45 <div class="group_member">
46 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
46 <div style="float:left;padding:3px">
47 <div class="cs_files">
47 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
48 %for fid, change, f, stat in c.files:
48 </div>
49 <div class="cs_${change}">
49 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,20)}"/> </div>
50 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
50 <div style="float:left">${user.username}</div>
51 <div class="changes">${h.fancy_file_stats(stat)}</div>
52 </div>
51 </div>
52 </li>
53 %endfor
53 %endfor
54 </div>
54 </ul>
55 </div>
55 </div>
56 </div>
57 </div>
58 ##DIFF
59 <div class="table" style="float:left;width:46%;clear:none">
60 <div id="body" class="diffblock">
61 <div style="white-space:pre-wrap;padding:5px">${h.literal(c.pull_request.description)}</div>
62 </div>
63 <div id="changeset_compare_view_content">
64 ##CS
65 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
66 <%include file="/compare/compare_cs.html" />
67
68 ## FILES
69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
70 <div class="cs_files">
71 %for fid, change, f, stat in c.files:
72 <div class="cs_${change}">
73 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
74 <div class="changes">${h.fancy_file_stats(stat)}</div>
75 </div>
76 %endfor
77 </div>
78 </div>
79 </div>
56 </div>
80 </div>
57 <script>
81 <script>
58 var _USERS_AC_DATA = ${c.users_array|n};
82 var _USERS_AC_DATA = ${c.users_array|n};
59 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
83 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
60 </script>
84 </script>
61
85
62 ## diff block
86 ## diff block
63 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
87 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
64 %for fid, change, f, stat in c.files:
88 %for fid, change, f, stat in c.files:
65 ${diff_block.diff_block_simple([c.changes[fid]])}
89 ${diff_block.diff_block_simple([c.changes[fid]])}
66 %endfor
90 %endfor
67
91
68 ## template for inline comment form
92 ## template for inline comment form
69 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
93 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
70 ##${comment.comment_inline_form(c.changeset)}
94 ##${comment.comment_inline_form(c.changeset)}
71
95
72 ## render comments main comments form and it status
96 ## render comments main comments form and it status
73 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
97 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
74 c.current_changeset_status)}
98 c.current_changeset_status)}
75
99
76 </div>
100 </div>
77
101
78 <script type="text/javascript">
102 <script type="text/javascript">
79
103
80
104
81 </script>
105 </script>
82
106
83 </%def>
107 </%def>
General Comments 0
You need to be logged in to leave comments. Login now