##// END OF EJS Templates
Added option to close pull requests, in future that will be close & merge
marcink -
r2608:58c52933 beta
parent child Browse files
Show More
@@ -1,328 +1,342 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, HTTPForbidden
28 from webob.exc import HTTPNotFound, HTTPForbidden
29 from collections import defaultdict
29 from collections import defaultdict
30 from itertools import groupby
30 from itertools import groupby
31
31
32 from pylons import request, response, session, tmpl_context as c, url
32 from pylons import request, response, session, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from pylons.decorators import jsonify
35 from pylons.decorators import jsonify
36
36
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.base import BaseRepoController, render
38 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.lib import diffs
41 from rhodecode.lib import diffs
42 from rhodecode.lib.utils import action_logger
42 from rhodecode.lib.utils import action_logger
43 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
43 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
44 ChangesetComment
44 ChangesetComment
45 from rhodecode.model.pull_request import PullRequestModel
45 from rhodecode.model.pull_request import PullRequestModel
46 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
47 from rhodecode.model.repo import RepoModel
47 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.comment import ChangesetCommentsModel
49 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.changeset_status import ChangesetStatusModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class PullrequestsController(BaseRepoController):
54 class PullrequestsController(BaseRepoController):
55
55
56 @LoginRequired()
56 @LoginRequired()
57 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
57 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
58 'repository.admin')
58 'repository.admin')
59 def __before__(self):
59 def __before__(self):
60 super(PullrequestsController, self).__before__()
60 super(PullrequestsController, self).__before__()
61
61
62 def _get_repo_refs(self, repo):
62 def _get_repo_refs(self, repo):
63 hist_l = []
63 hist_l = []
64
64
65 branches_group = ([('branch:%s:%s' % (k, v), k) for
65 branches_group = ([('branch:%s:%s' % (k, v), k) for
66 k, v in repo.branches.iteritems()], _("Branches"))
66 k, v in repo.branches.iteritems()], _("Branches"))
67 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
67 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
68 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
68 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
69 tags_group = ([('tag:%s:%s' % (k, v), k) for
69 tags_group = ([('tag:%s:%s' % (k, v), k) for
70 k, v in repo.tags.iteritems()], _("Tags"))
70 k, v in repo.tags.iteritems()], _("Tags"))
71
71
72 hist_l.append(bookmarks_group)
72 hist_l.append(bookmarks_group)
73 hist_l.append(branches_group)
73 hist_l.append(branches_group)
74 hist_l.append(tags_group)
74 hist_l.append(tags_group)
75
75
76 return hist_l
76 return hist_l
77
77
78 def show_all(self, repo_name):
78 def show_all(self, repo_name):
79 c.pull_requests = PullRequestModel().get_all(repo_name)
79 c.pull_requests = PullRequestModel().get_all(repo_name)
80 c.repo_name = repo_name
80 c.repo_name = repo_name
81 return render('/pullrequests/pullrequest_show_all.html')
81 return render('/pullrequests/pullrequest_show_all.html')
82
82
83 def index(self):
83 def index(self):
84 org_repo = c.rhodecode_db_repo
84 org_repo = c.rhodecode_db_repo
85
85
86 if org_repo.scm_instance.alias != 'hg':
86 if org_repo.scm_instance.alias != 'hg':
87 log.error('Review not available for GIT REPOS')
87 log.error('Review not available for GIT REPOS')
88 raise HTTPNotFound
88 raise HTTPNotFound
89
89
90 other_repos_info = {}
90 other_repos_info = {}
91
91
92 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
92 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
93 c.org_repos = []
93 c.org_repos = []
94 c.other_repos = []
94 c.other_repos = []
95 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
95 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
96 org_repo.user.username, c.repo_name))
96 org_repo.user.username, c.repo_name))
97 )
97 )
98
98
99 c.other_refs = c.org_refs
99 c.other_refs = c.org_refs
100 c.other_repos.extend(c.org_repos)
100 c.other_repos.extend(c.org_repos)
101
101
102 #add orginal repo
102 #add orginal repo
103 other_repos_info[org_repo.repo_name] = {
103 other_repos_info[org_repo.repo_name] = {
104 'gravatar': h.gravatar_url(org_repo.user.email, 24),
104 'gravatar': h.gravatar_url(org_repo.user.email, 24),
105 'description': org_repo.description
105 'description': org_repo.description
106 }
106 }
107
107
108 c.default_pull_request = org_repo.repo_name
108 c.default_pull_request = org_repo.repo_name
109 #gather forks and add to this list
109 #gather forks and add to this list
110 for fork in org_repo.forks:
110 for fork in org_repo.forks:
111 c.other_repos.append((fork.repo_name, '%s/%s' % (
111 c.other_repos.append((fork.repo_name, '%s/%s' % (
112 fork.user.username, fork.repo_name))
112 fork.user.username, fork.repo_name))
113 )
113 )
114 other_repos_info[fork.repo_name] = {
114 other_repos_info[fork.repo_name] = {
115 'gravatar': h.gravatar_url(fork.user.email, 24),
115 'gravatar': h.gravatar_url(fork.user.email, 24),
116 'description': fork.description
116 'description': fork.description
117 }
117 }
118 #add parents of this fork also
118 #add parents of this fork also
119 if org_repo.parent:
119 if org_repo.parent:
120 c.default_pull_request = org_repo.parent.repo_name
120 c.default_pull_request = org_repo.parent.repo_name
121 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
121 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
122 org_repo.parent.user.username,
122 org_repo.parent.user.username,
123 org_repo.parent.repo_name))
123 org_repo.parent.repo_name))
124 )
124 )
125 other_repos_info[org_repo.parent.repo_name] = {
125 other_repos_info[org_repo.parent.repo_name] = {
126 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
126 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
127 'description': org_repo.parent.description
127 'description': org_repo.parent.description
128 }
128 }
129
129
130 c.other_repos_info = json.dumps(other_repos_info)
130 c.other_repos_info = json.dumps(other_repos_info)
131 c.review_members = []
131 c.review_members = []
132 c.available_members = []
132 c.available_members = []
133 for u in User.query().filter(User.username != 'default').all():
133 for u in User.query().filter(User.username != 'default').all():
134 uname = u.username
134 uname = u.username
135 if org_repo.user == u:
135 if org_repo.user == u:
136 uname = _('%s (owner)') % u.username
136 uname = _('%s (owner)') % u.username
137 # auto add owner to pull-request recipients
137 # auto add owner to pull-request recipients
138 c.review_members.append([u.user_id, uname])
138 c.review_members.append([u.user_id, uname])
139 c.available_members.append([u.user_id, uname])
139 c.available_members.append([u.user_id, uname])
140 return render('/pullrequests/pullrequest.html')
140 return render('/pullrequests/pullrequest.html')
141
141
142 def create(self, repo_name):
142 def create(self, repo_name):
143 req_p = request.POST
143 req_p = request.POST
144 org_repo = req_p['org_repo']
144 org_repo = req_p['org_repo']
145 org_ref = req_p['org_ref']
145 org_ref = req_p['org_ref']
146 other_repo = req_p['other_repo']
146 other_repo = req_p['other_repo']
147 other_ref = req_p['other_ref']
147 other_ref = req_p['other_ref']
148 revisions = req_p.getall('revisions')
148 revisions = req_p.getall('revisions')
149 reviewers = req_p.getall('review_members')
149 reviewers = req_p.getall('review_members')
150 #TODO: wrap this into a FORM !!!
150 #TODO: wrap this into a FORM !!!
151
151
152 title = req_p['pullrequest_title']
152 title = req_p['pullrequest_title']
153 description = req_p['pullrequest_desc']
153 description = req_p['pullrequest_desc']
154
154
155 try:
155 try:
156 pull_request = PullRequestModel().create(
156 pull_request = PullRequestModel().create(
157 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
157 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
158 other_ref, revisions, reviewers, title, description
158 other_ref, revisions, reviewers, title, description
159 )
159 )
160 Session().commit()
160 Session().commit()
161 h.flash(_('Successfully opened new pull request'),
161 h.flash(_('Successfully opened new pull request'),
162 category='success')
162 category='success')
163 except Exception:
163 except Exception:
164 h.flash(_('Error occurred during sending pull request'),
164 h.flash(_('Error occurred during sending pull request'),
165 category='error')
165 category='error')
166 log.error(traceback.format_exc())
166 log.error(traceback.format_exc())
167 return redirect(url('changelog_home', repo_name=org_repo,))
167 return redirect(url('changelog_home', repo_name=org_repo,))
168
168
169 return redirect(url('pullrequest_show', repo_name=other_repo,
169 return redirect(url('pullrequest_show', repo_name=other_repo,
170 pull_request_id=pull_request.pull_request_id))
170 pull_request_id=pull_request.pull_request_id))
171
171
172 def _load_compare_data(self, pull_request):
172 def _load_compare_data(self, pull_request, enable_comments=True):
173 """
173 """
174 Load context data needed for generating compare diff
174 Load context data needed for generating compare diff
175
175
176 :param pull_request:
176 :param pull_request:
177 :type pull_request:
177 :type pull_request:
178 """
178 """
179
179
180 org_repo = pull_request.org_repo
180 org_repo = pull_request.org_repo
181 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
181 org_ref_type, org_ref_, org_ref = pull_request.org_ref.split(':')
182 other_repo = pull_request.other_repo
182 other_repo = pull_request.other_repo
183 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
183 other_ref_type, other_ref, other_ref_ = pull_request.other_ref.split(':')
184
184
185 org_ref = (org_ref_type, org_ref)
185 org_ref = (org_ref_type, org_ref)
186 other_ref = (other_ref_type, other_ref)
186 other_ref = (other_ref_type, other_ref)
187
187
188 c.org_repo = org_repo
188 c.org_repo = org_repo
189 c.other_repo = other_repo
189 c.other_repo = other_repo
190
190
191 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
191 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
192 org_repo, org_ref, other_repo, other_ref
192 org_repo, org_ref, other_repo, other_ref
193 )
193 )
194
194
195 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
195 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
196 c.cs_ranges])
196 c.cs_ranges])
197 # defines that we need hidden inputs with changesets
197 # defines that we need hidden inputs with changesets
198 c.as_form = request.GET.get('as_form', False)
198 c.as_form = request.GET.get('as_form', False)
199
199
200 c.org_ref = org_ref[1]
200 c.org_ref = org_ref[1]
201 c.other_ref = other_ref[1]
201 c.other_ref = other_ref[1]
202 # diff needs to have swapped org with other to generate proper diff
202 # diff needs to have swapped org with other to generate proper diff
203 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
203 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
204 discovery_data)
204 discovery_data)
205 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
205 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
206 _parsed = diff_processor.prepare()
206 _parsed = diff_processor.prepare()
207
207
208 c.files = []
208 c.files = []
209 c.changes = {}
209 c.changes = {}
210
210
211 for f in _parsed:
211 for f in _parsed:
212 fid = h.FID('', f['filename'])
212 fid = h.FID('', f['filename'])
213 c.files.append([fid, f['operation'], f['filename'], f['stats']])
213 c.files.append([fid, f['operation'], f['filename'], f['stats']])
214 diff = diff_processor.as_html(enable_comments=True,
214 diff = diff_processor.as_html(enable_comments=enable_comments,
215 diff_lines=[f])
215 diff_lines=[f])
216 c.changes[fid] = [f['operation'], f['filename'], diff]
216 c.changes[fid] = [f['operation'], f['filename'], diff]
217
217
218 def show(self, repo_name, pull_request_id):
218 def show(self, repo_name, pull_request_id):
219 repo_model = RepoModel()
219 repo_model = RepoModel()
220 c.users_array = repo_model.get_users_js()
220 c.users_array = repo_model.get_users_js()
221 c.users_groups_array = repo_model.get_users_groups_js()
221 c.users_groups_array = repo_model.get_users_groups_js()
222 c.pull_request = PullRequest.get_or_404(pull_request_id)
222 c.pull_request = PullRequest.get_or_404(pull_request_id)
223
223
224 cc_model = ChangesetCommentsModel()
224 cc_model = ChangesetCommentsModel()
225 cs_model = ChangesetStatusModel()
225 cs_model = ChangesetStatusModel()
226 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
226 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
227 pull_request=c.pull_request,
227 pull_request=c.pull_request,
228 with_revisions=True)
228 with_revisions=True)
229
229
230 cs_statuses = defaultdict(list)
230 cs_statuses = defaultdict(list)
231 for st in _cs_statuses:
231 for st in _cs_statuses:
232 cs_statuses[st.author.username] += [st]
232 cs_statuses[st.author.username] += [st]
233
233
234 c.pull_request_reviewers = []
234 c.pull_request_reviewers = []
235 for o in c.pull_request.reviewers:
235 for o in c.pull_request.reviewers:
236 st = cs_statuses.get(o.user.username, None)
236 st = cs_statuses.get(o.user.username, None)
237 if st:
237 if st:
238 sorter = lambda k: k.version
238 sorter = lambda k: k.version
239 st = [(x, list(y)[0])
239 st = [(x, list(y)[0])
240 for x, y in (groupby(sorted(st, key=sorter), sorter))]
240 for x, y in (groupby(sorted(st, key=sorter), sorter))]
241 c.pull_request_reviewers.append([o.user, st])
241 c.pull_request_reviewers.append([o.user, st])
242
242
243 # pull_requests repo_name we opened it against
243 # pull_requests repo_name we opened it against
244 # ie. other_repo must match
244 # ie. other_repo must match
245 if repo_name != c.pull_request.other_repo.repo_name:
245 if repo_name != c.pull_request.other_repo.repo_name:
246 raise HTTPNotFound
246 raise HTTPNotFound
247
247
248 # load compare data into template context
248 # load compare data into template context
249 self._load_compare_data(c.pull_request)
249 enable_comments = not c.pull_request.is_closed()
250 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
250
251
251 # inline comments
252 # inline comments
252 c.inline_cnt = 0
253 c.inline_cnt = 0
253 c.inline_comments = cc_model.get_inline_comments(
254 c.inline_comments = cc_model.get_inline_comments(
254 c.rhodecode_db_repo.repo_id,
255 c.rhodecode_db_repo.repo_id,
255 pull_request=pull_request_id)
256 pull_request=pull_request_id)
256 # count inline comments
257 # count inline comments
257 for __, lines in c.inline_comments:
258 for __, lines in c.inline_comments:
258 for comments in lines.values():
259 for comments in lines.values():
259 c.inline_cnt += len(comments)
260 c.inline_cnt += len(comments)
260 # comments
261 # comments
261 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
262 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
262 pull_request=pull_request_id)
263 pull_request=pull_request_id)
263
264
264 # changeset(pull-request) status
265 # changeset(pull-request) status
265 c.current_changeset_status = cs_model.calculate_status(
266 c.current_changeset_status = cs_model.calculate_status(
266 c.pull_request_reviewers
267 c.pull_request_reviewers
267 )
268 )
268 c.changeset_statuses = ChangesetStatus.STATUSES
269 c.changeset_statuses = ChangesetStatus.STATUSES
269 c.target_repo = c.pull_request.org_repo.repo_name
270 c.target_repo = c.pull_request.org_repo.repo_name
270 return render('/pullrequests/pullrequest_show.html')
271 return render('/pullrequests/pullrequest_show.html')
271
272
272 @jsonify
273 @jsonify
273 def comment(self, repo_name, pull_request_id):
274 def comment(self, repo_name, pull_request_id):
275 pull_request = PullRequest.get_or_404(pull_request_id)
276 if pull_request.is_closed():
277 raise HTTPForbidden()
274
278
275 status = request.POST.get('changeset_status')
279 status = request.POST.get('changeset_status')
276 change_status = request.POST.get('change_changeset_status')
280 change_status = request.POST.get('change_changeset_status')
277
281
278 comm = ChangesetCommentsModel().create(
282 comm = ChangesetCommentsModel().create(
279 text=request.POST.get('text'),
283 text=request.POST.get('text'),
280 repo=c.rhodecode_db_repo.repo_id,
284 repo=c.rhodecode_db_repo.repo_id,
281 user=c.rhodecode_user.user_id,
285 user=c.rhodecode_user.user_id,
282 pull_request=pull_request_id,
286 pull_request=pull_request_id,
283 f_path=request.POST.get('f_path'),
287 f_path=request.POST.get('f_path'),
284 line_no=request.POST.get('line'),
288 line_no=request.POST.get('line'),
285 status_change=(ChangesetStatus.get_status_lbl(status)
289 status_change=(ChangesetStatus.get_status_lbl(status)
286 if status and change_status else None)
290 if status and change_status else None)
287 )
291 )
288
292
289 # get status if set !
293 # get status if set !
290 if status and change_status:
294 if status and change_status:
291 ChangesetStatusModel().set_status(
295 ChangesetStatusModel().set_status(
292 c.rhodecode_db_repo.repo_id,
296 c.rhodecode_db_repo.repo_id,
293 status,
297 status,
294 c.rhodecode_user.user_id,
298 c.rhodecode_user.user_id,
295 comm,
299 comm,
296 pull_request=pull_request_id
300 pull_request=pull_request_id
297 )
301 )
298 action_logger(self.rhodecode_user,
302 action_logger(self.rhodecode_user,
299 'user_commented_pull_request:%s' % pull_request_id,
303 'user_commented_pull_request:%s' % pull_request_id,
300 c.rhodecode_db_repo, self.ip_addr, self.sa)
304 c.rhodecode_db_repo, self.ip_addr, self.sa)
301
305
306 if request.POST.get('save_close'):
307 PullRequestModel().close_pull_request(pull_request_id)
308 action_logger(self.rhodecode_user,
309 'user_closed_pull_request:%s' % pull_request_id,
310 c.rhodecode_db_repo, self.ip_addr, self.sa)
311
302 Session().commit()
312 Session().commit()
303
313
304 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
314 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
305 return redirect(h.url('pullrequest_show', repo_name=repo_name,
315 return redirect(h.url('pullrequest_show', repo_name=repo_name,
306 pull_request_id=pull_request_id))
316 pull_request_id=pull_request_id))
307
317
308 data = {
318 data = {
309 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
319 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
310 }
320 }
311 if comm:
321 if comm:
312 c.co = comm
322 c.co = comm
313 data.update(comm.get_dict())
323 data.update(comm.get_dict())
314 data.update({'rendered_text':
324 data.update({'rendered_text':
315 render('changeset/changeset_comment_block.html')})
325 render('changeset/changeset_comment_block.html')})
316
326
317 return data
327 return data
318
328
319 @jsonify
329 @jsonify
320 def delete_comment(self, repo_name, comment_id):
330 def delete_comment(self, repo_name, comment_id):
321 co = ChangesetComment.get(comment_id)
331 co = ChangesetComment.get(comment_id)
332 if co.pull_request.is_closed():
333 #don't allow deleting comments on closed pull request
334 raise HTTPForbidden()
335
322 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
336 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
323 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
337 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
324 ChangesetCommentsModel().delete(comment=co)
338 ChangesetCommentsModel().delete(comment=co)
325 Session().commit()
339 Session().commit()
326 return True
340 return True
327 else:
341 else:
328 raise HTTPForbidden() No newline at end of file
342 raise HTTPForbidden()
@@ -1,1653 +1,1659 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 from webob.exc import HTTPNotFound
38 from webob.exc import HTTPNotFound
39
39
40 from pylons.i18n.translation import lazy_ugettext as _
40 from pylons.i18n.translation import lazy_ugettext as _
41
41
42 from rhodecode.lib.vcs import get_backend
42 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs.utils.helpers import get_scm
43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46
46
47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 safe_unicode
48 safe_unicode
49 from rhodecode.lib.compat import json
49 from rhodecode.lib.compat import json
50 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
51
51
52 from rhodecode.model.meta import Base, Session
52 from rhodecode.model.meta import Base, Session
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 BaseModel(object):
64 class BaseModel(object):
65 """
65 """
66 Base Model for all classess
66 Base Model for all classess
67 """
67 """
68
68
69 @classmethod
69 @classmethod
70 def _get_keys(cls):
70 def _get_keys(cls):
71 """return column names for this model """
71 """return column names for this model """
72 return class_mapper(cls).c.keys()
72 return class_mapper(cls).c.keys()
73
73
74 def get_dict(self):
74 def get_dict(self):
75 """
75 """
76 return dict with keys and values corresponding
76 return dict with keys and values corresponding
77 to this model data """
77 to this model data """
78
78
79 d = {}
79 d = {}
80 for k in self._get_keys():
80 for k in self._get_keys():
81 d[k] = getattr(self, k)
81 d[k] = getattr(self, k)
82
82
83 # also use __json__() if present to get additional fields
83 # also use __json__() if present to get additional fields
84 _json_attr = getattr(self, '__json__', None)
84 _json_attr = getattr(self, '__json__', None)
85 if _json_attr:
85 if _json_attr:
86 # update with attributes from __json__
86 # update with attributes from __json__
87 if callable(_json_attr):
87 if callable(_json_attr):
88 _json_attr = _json_attr()
88 _json_attr = _json_attr()
89 for k, val in _json_attr.iteritems():
89 for k, val in _json_attr.iteritems():
90 d[k] = val
90 d[k] = val
91 return d
91 return d
92
92
93 def get_appstruct(self):
93 def get_appstruct(self):
94 """return list with keys and values tupples corresponding
94 """return list with keys and values tupples corresponding
95 to this model data """
95 to this model data """
96
96
97 l = []
97 l = []
98 for k in self._get_keys():
98 for k in self._get_keys():
99 l.append((k, getattr(self, k),))
99 l.append((k, getattr(self, k),))
100 return l
100 return l
101
101
102 def populate_obj(self, populate_dict):
102 def populate_obj(self, populate_dict):
103 """populate model with data from given populate_dict"""
103 """populate model with data from given populate_dict"""
104
104
105 for k in self._get_keys():
105 for k in self._get_keys():
106 if k in populate_dict:
106 if k in populate_dict:
107 setattr(self, k, populate_dict[k])
107 setattr(self, k, populate_dict[k])
108
108
109 @classmethod
109 @classmethod
110 def query(cls):
110 def query(cls):
111 return Session().query(cls)
111 return Session().query(cls)
112
112
113 @classmethod
113 @classmethod
114 def get(cls, id_):
114 def get(cls, id_):
115 if id_:
115 if id_:
116 return cls.query().get(id_)
116 return cls.query().get(id_)
117
117
118 @classmethod
118 @classmethod
119 def get_or_404(cls, id_):
119 def get_or_404(cls, id_):
120 if id_:
120 if id_:
121 res = cls.query().get(id_)
121 res = cls.query().get(id_)
122 if not res:
122 if not res:
123 raise HTTPNotFound
123 raise HTTPNotFound
124 return res
124 return res
125
125
126 @classmethod
126 @classmethod
127 def getAll(cls):
127 def getAll(cls):
128 return cls.query().all()
128 return cls.query().all()
129
129
130 @classmethod
130 @classmethod
131 def delete(cls, id_):
131 def delete(cls, id_):
132 obj = cls.query().get(id_)
132 obj = cls.query().get(id_)
133 Session().delete(obj)
133 Session().delete(obj)
134
134
135 def __repr__(self):
135 def __repr__(self):
136 if hasattr(self, '__unicode__'):
136 if hasattr(self, '__unicode__'):
137 # python repr needs to return str
137 # python repr needs to return str
138 return safe_str(self.__unicode__())
138 return safe_str(self.__unicode__())
139 return '<DB:%s>' % (self.__class__.__name__)
139 return '<DB:%s>' % (self.__class__.__name__)
140
140
141
141
142 class RhodeCodeSetting(Base, BaseModel):
142 class RhodeCodeSetting(Base, BaseModel):
143 __tablename__ = 'rhodecode_settings'
143 __tablename__ = 'rhodecode_settings'
144 __table_args__ = (
144 __table_args__ = (
145 UniqueConstraint('app_settings_name'),
145 UniqueConstraint('app_settings_name'),
146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 'mysql_charset': 'utf8'}
147 'mysql_charset': 'utf8'}
148 )
148 )
149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152
152
153 def __init__(self, k='', v=''):
153 def __init__(self, k='', v=''):
154 self.app_settings_name = k
154 self.app_settings_name = k
155 self.app_settings_value = v
155 self.app_settings_value = v
156
156
157 @validates('_app_settings_value')
157 @validates('_app_settings_value')
158 def validate_settings_value(self, key, val):
158 def validate_settings_value(self, key, val):
159 assert type(val) == unicode
159 assert type(val) == unicode
160 return val
160 return val
161
161
162 @hybrid_property
162 @hybrid_property
163 def app_settings_value(self):
163 def app_settings_value(self):
164 v = self._app_settings_value
164 v = self._app_settings_value
165 if self.app_settings_name == 'ldap_active':
165 if self.app_settings_name == 'ldap_active':
166 v = str2bool(v)
166 v = str2bool(v)
167 return v
167 return v
168
168
169 @app_settings_value.setter
169 @app_settings_value.setter
170 def app_settings_value(self, val):
170 def app_settings_value(self, val):
171 """
171 """
172 Setter that will always make sure we use unicode in app_settings_value
172 Setter that will always make sure we use unicode in app_settings_value
173
173
174 :param val:
174 :param val:
175 """
175 """
176 self._app_settings_value = safe_unicode(val)
176 self._app_settings_value = safe_unicode(val)
177
177
178 def __unicode__(self):
178 def __unicode__(self):
179 return u"<%s('%s:%s')>" % (
179 return u"<%s('%s:%s')>" % (
180 self.__class__.__name__,
180 self.__class__.__name__,
181 self.app_settings_name, self.app_settings_value
181 self.app_settings_name, self.app_settings_value
182 )
182 )
183
183
184 @classmethod
184 @classmethod
185 def get_by_name(cls, ldap_key):
185 def get_by_name(cls, ldap_key):
186 return cls.query()\
186 return cls.query()\
187 .filter(cls.app_settings_name == ldap_key).scalar()
187 .filter(cls.app_settings_name == ldap_key).scalar()
188
188
189 @classmethod
189 @classmethod
190 def get_app_settings(cls, cache=False):
190 def get_app_settings(cls, cache=False):
191
191
192 ret = cls.query()
192 ret = cls.query()
193
193
194 if cache:
194 if cache:
195 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
195 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
196
196
197 if not ret:
197 if not ret:
198 raise Exception('Could not get application settings !')
198 raise Exception('Could not get application settings !')
199 settings = {}
199 settings = {}
200 for each in ret:
200 for each in ret:
201 settings['rhodecode_' + each.app_settings_name] = \
201 settings['rhodecode_' + each.app_settings_name] = \
202 each.app_settings_value
202 each.app_settings_value
203
203
204 return settings
204 return settings
205
205
206 @classmethod
206 @classmethod
207 def get_ldap_settings(cls, cache=False):
207 def get_ldap_settings(cls, cache=False):
208 ret = cls.query()\
208 ret = cls.query()\
209 .filter(cls.app_settings_name.startswith('ldap_')).all()
209 .filter(cls.app_settings_name.startswith('ldap_')).all()
210 fd = {}
210 fd = {}
211 for row in ret:
211 for row in ret:
212 fd.update({row.app_settings_name: row.app_settings_value})
212 fd.update({row.app_settings_name: row.app_settings_value})
213
213
214 return fd
214 return fd
215
215
216
216
217 class RhodeCodeUi(Base, BaseModel):
217 class RhodeCodeUi(Base, BaseModel):
218 __tablename__ = 'rhodecode_ui'
218 __tablename__ = 'rhodecode_ui'
219 __table_args__ = (
219 __table_args__ = (
220 UniqueConstraint('ui_key'),
220 UniqueConstraint('ui_key'),
221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
222 'mysql_charset': 'utf8'}
222 'mysql_charset': 'utf8'}
223 )
223 )
224
224
225 HOOK_UPDATE = 'changegroup.update'
225 HOOK_UPDATE = 'changegroup.update'
226 HOOK_REPO_SIZE = 'changegroup.repo_size'
226 HOOK_REPO_SIZE = 'changegroup.repo_size'
227 HOOK_PUSH = 'changegroup.push_logger'
227 HOOK_PUSH = 'changegroup.push_logger'
228 HOOK_PULL = 'preoutgoing.pull_logger'
228 HOOK_PULL = 'preoutgoing.pull_logger'
229
229
230 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
230 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
231 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
231 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
232 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
232 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
233 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
233 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
234 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
235
235
236 @classmethod
236 @classmethod
237 def get_by_key(cls, key):
237 def get_by_key(cls, key):
238 return cls.query().filter(cls.ui_key == key)
238 return cls.query().filter(cls.ui_key == key)
239
239
240 @classmethod
240 @classmethod
241 def get_builtin_hooks(cls):
241 def get_builtin_hooks(cls):
242 q = cls.query()
242 q = cls.query()
243 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
243 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
244 cls.HOOK_REPO_SIZE,
244 cls.HOOK_REPO_SIZE,
245 cls.HOOK_PUSH, cls.HOOK_PULL]))
245 cls.HOOK_PUSH, cls.HOOK_PULL]))
246 return q.all()
246 return q.all()
247
247
248 @classmethod
248 @classmethod
249 def get_custom_hooks(cls):
249 def get_custom_hooks(cls):
250 q = cls.query()
250 q = cls.query()
251 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
251 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
252 cls.HOOK_REPO_SIZE,
252 cls.HOOK_REPO_SIZE,
253 cls.HOOK_PUSH, cls.HOOK_PULL]))
253 cls.HOOK_PUSH, cls.HOOK_PULL]))
254 q = q.filter(cls.ui_section == 'hooks')
254 q = q.filter(cls.ui_section == 'hooks')
255 return q.all()
255 return q.all()
256
256
257 @classmethod
257 @classmethod
258 def get_repos_location(cls):
258 def get_repos_location(cls):
259 return cls.get_by_key('/').one().ui_value
259 return cls.get_by_key('/').one().ui_value
260
260
261 @classmethod
261 @classmethod
262 def create_or_update_hook(cls, key, val):
262 def create_or_update_hook(cls, key, val):
263 new_ui = cls.get_by_key(key).scalar() or cls()
263 new_ui = cls.get_by_key(key).scalar() or cls()
264 new_ui.ui_section = 'hooks'
264 new_ui.ui_section = 'hooks'
265 new_ui.ui_active = True
265 new_ui.ui_active = True
266 new_ui.ui_key = key
266 new_ui.ui_key = key
267 new_ui.ui_value = val
267 new_ui.ui_value = val
268
268
269 Session().add(new_ui)
269 Session().add(new_ui)
270
270
271
271
272 class User(Base, BaseModel):
272 class User(Base, BaseModel):
273 __tablename__ = 'users'
273 __tablename__ = 'users'
274 __table_args__ = (
274 __table_args__ = (
275 UniqueConstraint('username'), UniqueConstraint('email'),
275 UniqueConstraint('username'), UniqueConstraint('email'),
276 {'extend_existing': True, 'mysql_engine': 'InnoDB',
276 {'extend_existing': True, 'mysql_engine': 'InnoDB',
277 'mysql_charset': 'utf8'}
277 'mysql_charset': 'utf8'}
278 )
278 )
279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
280 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
281 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
281 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
282 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
283 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
283 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
284 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
284 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
287 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
288 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
288 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290
290
291 user_log = relationship('UserLog', cascade='all')
291 user_log = relationship('UserLog', cascade='all')
292 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
292 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
293
293
294 repositories = relationship('Repository')
294 repositories = relationship('Repository')
295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
297 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
297 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
298
298
299 group_member = relationship('UsersGroupMember', cascade='all')
299 group_member = relationship('UsersGroupMember', cascade='all')
300
300
301 notifications = relationship('UserNotification', cascade='all')
301 notifications = relationship('UserNotification', cascade='all')
302 # notifications assigned to this user
302 # notifications assigned to this user
303 user_created_notifications = relationship('Notification', cascade='all')
303 user_created_notifications = relationship('Notification', cascade='all')
304 # comments created by this user
304 # comments created by this user
305 user_comments = relationship('ChangesetComment', cascade='all')
305 user_comments = relationship('ChangesetComment', cascade='all')
306 #extra emails for this user
306 #extra emails for this user
307 user_emails = relationship('UserEmailMap', cascade='all')
307 user_emails = relationship('UserEmailMap', cascade='all')
308
308
309 @hybrid_property
309 @hybrid_property
310 def email(self):
310 def email(self):
311 return self._email
311 return self._email
312
312
313 @email.setter
313 @email.setter
314 def email(self, val):
314 def email(self, val):
315 self._email = val.lower() if val else None
315 self._email = val.lower() if val else None
316
316
317 @property
317 @property
318 def emails(self):
318 def emails(self):
319 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
319 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
320 return [self.email] + [x.email for x in other]
320 return [self.email] + [x.email for x in other]
321
321
322 @property
322 @property
323 def full_name(self):
323 def full_name(self):
324 return '%s %s' % (self.name, self.lastname)
324 return '%s %s' % (self.name, self.lastname)
325
325
326 @property
326 @property
327 def full_name_or_username(self):
327 def full_name_or_username(self):
328 return ('%s %s' % (self.name, self.lastname)
328 return ('%s %s' % (self.name, self.lastname)
329 if (self.name and self.lastname) else self.username)
329 if (self.name and self.lastname) else self.username)
330
330
331 @property
331 @property
332 def full_contact(self):
332 def full_contact(self):
333 return '%s %s <%s>' % (self.name, self.lastname, self.email)
333 return '%s %s <%s>' % (self.name, self.lastname, self.email)
334
334
335 @property
335 @property
336 def short_contact(self):
336 def short_contact(self):
337 return '%s %s' % (self.name, self.lastname)
337 return '%s %s' % (self.name, self.lastname)
338
338
339 @property
339 @property
340 def is_admin(self):
340 def is_admin(self):
341 return self.admin
341 return self.admin
342
342
343 def __unicode__(self):
343 def __unicode__(self):
344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
345 self.user_id, self.username)
345 self.user_id, self.username)
346
346
347 @classmethod
347 @classmethod
348 def get_by_username(cls, username, case_insensitive=False, cache=False):
348 def get_by_username(cls, username, case_insensitive=False, cache=False):
349 if case_insensitive:
349 if case_insensitive:
350 q = cls.query().filter(cls.username.ilike(username))
350 q = cls.query().filter(cls.username.ilike(username))
351 else:
351 else:
352 q = cls.query().filter(cls.username == username)
352 q = cls.query().filter(cls.username == username)
353
353
354 if cache:
354 if cache:
355 q = q.options(FromCache(
355 q = q.options(FromCache(
356 "sql_cache_short",
356 "sql_cache_short",
357 "get_user_%s" % _hash_key(username)
357 "get_user_%s" % _hash_key(username)
358 )
358 )
359 )
359 )
360 return q.scalar()
360 return q.scalar()
361
361
362 @classmethod
362 @classmethod
363 def get_by_api_key(cls, api_key, cache=False):
363 def get_by_api_key(cls, api_key, cache=False):
364 q = cls.query().filter(cls.api_key == api_key)
364 q = cls.query().filter(cls.api_key == api_key)
365
365
366 if cache:
366 if cache:
367 q = q.options(FromCache("sql_cache_short",
367 q = q.options(FromCache("sql_cache_short",
368 "get_api_key_%s" % api_key))
368 "get_api_key_%s" % api_key))
369 return q.scalar()
369 return q.scalar()
370
370
371 @classmethod
371 @classmethod
372 def get_by_email(cls, email, case_insensitive=False, cache=False):
372 def get_by_email(cls, email, case_insensitive=False, cache=False):
373 if case_insensitive:
373 if case_insensitive:
374 q = cls.query().filter(cls.email.ilike(email))
374 q = cls.query().filter(cls.email.ilike(email))
375 else:
375 else:
376 q = cls.query().filter(cls.email == email)
376 q = cls.query().filter(cls.email == email)
377
377
378 if cache:
378 if cache:
379 q = q.options(FromCache("sql_cache_short",
379 q = q.options(FromCache("sql_cache_short",
380 "get_email_key_%s" % email))
380 "get_email_key_%s" % email))
381
381
382 ret = q.scalar()
382 ret = q.scalar()
383 if ret is None:
383 if ret is None:
384 q = UserEmailMap.query()
384 q = UserEmailMap.query()
385 # try fetching in alternate email map
385 # try fetching in alternate email map
386 if case_insensitive:
386 if case_insensitive:
387 q = q.filter(UserEmailMap.email.ilike(email))
387 q = q.filter(UserEmailMap.email.ilike(email))
388 else:
388 else:
389 q = q.filter(UserEmailMap.email == email)
389 q = q.filter(UserEmailMap.email == email)
390 q = q.options(joinedload(UserEmailMap.user))
390 q = q.options(joinedload(UserEmailMap.user))
391 if cache:
391 if cache:
392 q = q.options(FromCache("sql_cache_short",
392 q = q.options(FromCache("sql_cache_short",
393 "get_email_map_key_%s" % email))
393 "get_email_map_key_%s" % email))
394 ret = getattr(q.scalar(), 'user', None)
394 ret = getattr(q.scalar(), 'user', None)
395
395
396 return ret
396 return ret
397
397
398 def update_lastlogin(self):
398 def update_lastlogin(self):
399 """Update user lastlogin"""
399 """Update user lastlogin"""
400 self.last_login = datetime.datetime.now()
400 self.last_login = datetime.datetime.now()
401 Session().add(self)
401 Session().add(self)
402 log.debug('updated user %s lastlogin' % self.username)
402 log.debug('updated user %s lastlogin' % self.username)
403
403
404 def get_api_data(self):
404 def get_api_data(self):
405 """
405 """
406 Common function for generating user related data for API
406 Common function for generating user related data for API
407 """
407 """
408 user = self
408 user = self
409 data = dict(
409 data = dict(
410 user_id=user.user_id,
410 user_id=user.user_id,
411 username=user.username,
411 username=user.username,
412 firstname=user.name,
412 firstname=user.name,
413 lastname=user.lastname,
413 lastname=user.lastname,
414 email=user.email,
414 email=user.email,
415 emails=user.emails,
415 emails=user.emails,
416 api_key=user.api_key,
416 api_key=user.api_key,
417 active=user.active,
417 active=user.active,
418 admin=user.admin,
418 admin=user.admin,
419 ldap_dn=user.ldap_dn,
419 ldap_dn=user.ldap_dn,
420 last_login=user.last_login,
420 last_login=user.last_login,
421 )
421 )
422 return data
422 return data
423
423
424 def __json__(self):
424 def __json__(self):
425 data = dict(
425 data = dict(
426 full_name=self.full_name,
426 full_name=self.full_name,
427 full_name_or_username=self.full_name_or_username,
427 full_name_or_username=self.full_name_or_username,
428 short_contact=self.short_contact,
428 short_contact=self.short_contact,
429 full_contact=self.full_contact
429 full_contact=self.full_contact
430 )
430 )
431 data.update(self.get_api_data())
431 data.update(self.get_api_data())
432 return data
432 return data
433
433
434
434
435 class UserEmailMap(Base, BaseModel):
435 class UserEmailMap(Base, BaseModel):
436 __tablename__ = 'user_email_map'
436 __tablename__ = 'user_email_map'
437 __table_args__ = (
437 __table_args__ = (
438 Index('uem_email_idx', 'email'),
438 Index('uem_email_idx', 'email'),
439 UniqueConstraint('email'),
439 UniqueConstraint('email'),
440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
441 'mysql_charset': 'utf8'}
441 'mysql_charset': 'utf8'}
442 )
442 )
443 __mapper_args__ = {}
443 __mapper_args__ = {}
444
444
445 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
445 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
447 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
447 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
448
448
449 user = relationship('User', lazy='joined')
449 user = relationship('User', lazy='joined')
450
450
451 @validates('_email')
451 @validates('_email')
452 def validate_email(self, key, email):
452 def validate_email(self, key, email):
453 # check if this email is not main one
453 # check if this email is not main one
454 main_email = Session().query(User).filter(User.email == email).scalar()
454 main_email = Session().query(User).filter(User.email == email).scalar()
455 if main_email is not None:
455 if main_email is not None:
456 raise AttributeError('email %s is present is user table' % email)
456 raise AttributeError('email %s is present is user table' % email)
457 return email
457 return email
458
458
459 @hybrid_property
459 @hybrid_property
460 def email(self):
460 def email(self):
461 return self._email
461 return self._email
462
462
463 @email.setter
463 @email.setter
464 def email(self, val):
464 def email(self, val):
465 self._email = val.lower() if val else None
465 self._email = val.lower() if val else None
466
466
467
467
468 class UserLog(Base, BaseModel):
468 class UserLog(Base, BaseModel):
469 __tablename__ = 'user_logs'
469 __tablename__ = 'user_logs'
470 __table_args__ = (
470 __table_args__ = (
471 {'extend_existing': True, 'mysql_engine': 'InnoDB',
471 {'extend_existing': True, 'mysql_engine': 'InnoDB',
472 'mysql_charset': 'utf8'},
472 'mysql_charset': 'utf8'},
473 )
473 )
474 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
474 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
475 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
475 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
476 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
476 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
477 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
477 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
478 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
478 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
479 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
479 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
480 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
480 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
481
481
482 @property
482 @property
483 def action_as_day(self):
483 def action_as_day(self):
484 return datetime.date(*self.action_date.timetuple()[:3])
484 return datetime.date(*self.action_date.timetuple()[:3])
485
485
486 user = relationship('User')
486 user = relationship('User')
487 repository = relationship('Repository', cascade='')
487 repository = relationship('Repository', cascade='')
488
488
489
489
490 class UsersGroup(Base, BaseModel):
490 class UsersGroup(Base, BaseModel):
491 __tablename__ = 'users_groups'
491 __tablename__ = 'users_groups'
492 __table_args__ = (
492 __table_args__ = (
493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 'mysql_charset': 'utf8'},
494 'mysql_charset': 'utf8'},
495 )
495 )
496
496
497 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
497 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
498 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
499 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
499 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
500
500
501 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
501 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
502 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
502 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
503 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
503 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
504
504
505 def __unicode__(self):
505 def __unicode__(self):
506 return u'<userGroup(%s)>' % (self.users_group_name)
506 return u'<userGroup(%s)>' % (self.users_group_name)
507
507
508 @classmethod
508 @classmethod
509 def get_by_group_name(cls, group_name, cache=False,
509 def get_by_group_name(cls, group_name, cache=False,
510 case_insensitive=False):
510 case_insensitive=False):
511 if case_insensitive:
511 if case_insensitive:
512 q = cls.query().filter(cls.users_group_name.ilike(group_name))
512 q = cls.query().filter(cls.users_group_name.ilike(group_name))
513 else:
513 else:
514 q = cls.query().filter(cls.users_group_name == group_name)
514 q = cls.query().filter(cls.users_group_name == group_name)
515 if cache:
515 if cache:
516 q = q.options(FromCache(
516 q = q.options(FromCache(
517 "sql_cache_short",
517 "sql_cache_short",
518 "get_user_%s" % _hash_key(group_name)
518 "get_user_%s" % _hash_key(group_name)
519 )
519 )
520 )
520 )
521 return q.scalar()
521 return q.scalar()
522
522
523 @classmethod
523 @classmethod
524 def get(cls, users_group_id, cache=False):
524 def get(cls, users_group_id, cache=False):
525 users_group = cls.query()
525 users_group = cls.query()
526 if cache:
526 if cache:
527 users_group = users_group.options(FromCache("sql_cache_short",
527 users_group = users_group.options(FromCache("sql_cache_short",
528 "get_users_group_%s" % users_group_id))
528 "get_users_group_%s" % users_group_id))
529 return users_group.get(users_group_id)
529 return users_group.get(users_group_id)
530
530
531 def get_api_data(self):
531 def get_api_data(self):
532 users_group = self
532 users_group = self
533
533
534 data = dict(
534 data = dict(
535 users_group_id=users_group.users_group_id,
535 users_group_id=users_group.users_group_id,
536 group_name=users_group.users_group_name,
536 group_name=users_group.users_group_name,
537 active=users_group.users_group_active,
537 active=users_group.users_group_active,
538 )
538 )
539
539
540 return data
540 return data
541
541
542
542
543 class UsersGroupMember(Base, BaseModel):
543 class UsersGroupMember(Base, BaseModel):
544 __tablename__ = 'users_groups_members'
544 __tablename__ = 'users_groups_members'
545 __table_args__ = (
545 __table_args__ = (
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 'mysql_charset': 'utf8'},
547 'mysql_charset': 'utf8'},
548 )
548 )
549
549
550 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
550 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
553
553
554 user = relationship('User', lazy='joined')
554 user = relationship('User', lazy='joined')
555 users_group = relationship('UsersGroup')
555 users_group = relationship('UsersGroup')
556
556
557 def __init__(self, gr_id='', u_id=''):
557 def __init__(self, gr_id='', u_id=''):
558 self.users_group_id = gr_id
558 self.users_group_id = gr_id
559 self.user_id = u_id
559 self.user_id = u_id
560
560
561
561
562 class Repository(Base, BaseModel):
562 class Repository(Base, BaseModel):
563 __tablename__ = 'repositories'
563 __tablename__ = 'repositories'
564 __table_args__ = (
564 __table_args__ = (
565 UniqueConstraint('repo_name'),
565 UniqueConstraint('repo_name'),
566 {'extend_existing': True, 'mysql_engine': 'InnoDB',
566 {'extend_existing': True, 'mysql_engine': 'InnoDB',
567 'mysql_charset': 'utf8'},
567 'mysql_charset': 'utf8'},
568 )
568 )
569
569
570 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
570 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
571 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
572 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
572 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
573 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
573 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
574 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
574 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
575 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
575 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
576 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
576 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
577 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
577 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
578 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
578 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
579 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
579 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
580 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
580 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
581
581
582 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
582 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
583 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
583 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
584
584
585 user = relationship('User')
585 user = relationship('User')
586 fork = relationship('Repository', remote_side=repo_id)
586 fork = relationship('Repository', remote_side=repo_id)
587 group = relationship('RepoGroup')
587 group = relationship('RepoGroup')
588 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
588 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
589 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
589 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
590 stats = relationship('Statistics', cascade='all', uselist=False)
590 stats = relationship('Statistics', cascade='all', uselist=False)
591
591
592 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
592 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
593
593
594 logs = relationship('UserLog')
594 logs = relationship('UserLog')
595 comments = relationship('ChangesetComment')
595 comments = relationship('ChangesetComment')
596
596
597 def __unicode__(self):
597 def __unicode__(self):
598 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
598 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
599 self.repo_name)
599 self.repo_name)
600
600
601 @classmethod
601 @classmethod
602 def url_sep(cls):
602 def url_sep(cls):
603 return URL_SEP
603 return URL_SEP
604
604
605 @classmethod
605 @classmethod
606 def get_by_repo_name(cls, repo_name):
606 def get_by_repo_name(cls, repo_name):
607 q = Session().query(cls).filter(cls.repo_name == repo_name)
607 q = Session().query(cls).filter(cls.repo_name == repo_name)
608 q = q.options(joinedload(Repository.fork))\
608 q = q.options(joinedload(Repository.fork))\
609 .options(joinedload(Repository.user))\
609 .options(joinedload(Repository.user))\
610 .options(joinedload(Repository.group))
610 .options(joinedload(Repository.group))
611 return q.scalar()
611 return q.scalar()
612
612
613 @classmethod
613 @classmethod
614 def get_by_full_path(cls, repo_full_path):
614 def get_by_full_path(cls, repo_full_path):
615 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
615 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
616 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
616 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
617
617
618 @classmethod
618 @classmethod
619 def get_repo_forks(cls, repo_id):
619 def get_repo_forks(cls, repo_id):
620 return cls.query().filter(Repository.fork_id == repo_id)
620 return cls.query().filter(Repository.fork_id == repo_id)
621
621
622 @classmethod
622 @classmethod
623 def base_path(cls):
623 def base_path(cls):
624 """
624 """
625 Returns base path when all repos are stored
625 Returns base path when all repos are stored
626
626
627 :param cls:
627 :param cls:
628 """
628 """
629 q = Session().query(RhodeCodeUi)\
629 q = Session().query(RhodeCodeUi)\
630 .filter(RhodeCodeUi.ui_key == cls.url_sep())
630 .filter(RhodeCodeUi.ui_key == cls.url_sep())
631 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
631 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
632 return q.one().ui_value
632 return q.one().ui_value
633
633
634 @property
634 @property
635 def forks(self):
635 def forks(self):
636 """
636 """
637 Return forks of this repo
637 Return forks of this repo
638 """
638 """
639 return Repository.get_repo_forks(self.repo_id)
639 return Repository.get_repo_forks(self.repo_id)
640
640
641 @property
641 @property
642 def parent(self):
642 def parent(self):
643 """
643 """
644 Returns fork parent
644 Returns fork parent
645 """
645 """
646 return self.fork
646 return self.fork
647
647
648 @property
648 @property
649 def just_name(self):
649 def just_name(self):
650 return self.repo_name.split(Repository.url_sep())[-1]
650 return self.repo_name.split(Repository.url_sep())[-1]
651
651
652 @property
652 @property
653 def groups_with_parents(self):
653 def groups_with_parents(self):
654 groups = []
654 groups = []
655 if self.group is None:
655 if self.group is None:
656 return groups
656 return groups
657
657
658 cur_gr = self.group
658 cur_gr = self.group
659 groups.insert(0, cur_gr)
659 groups.insert(0, cur_gr)
660 while 1:
660 while 1:
661 gr = getattr(cur_gr, 'parent_group', None)
661 gr = getattr(cur_gr, 'parent_group', None)
662 cur_gr = cur_gr.parent_group
662 cur_gr = cur_gr.parent_group
663 if gr is None:
663 if gr is None:
664 break
664 break
665 groups.insert(0, gr)
665 groups.insert(0, gr)
666
666
667 return groups
667 return groups
668
668
669 @property
669 @property
670 def groups_and_repo(self):
670 def groups_and_repo(self):
671 return self.groups_with_parents, self.just_name
671 return self.groups_with_parents, self.just_name
672
672
673 @LazyProperty
673 @LazyProperty
674 def repo_path(self):
674 def repo_path(self):
675 """
675 """
676 Returns base full path for that repository means where it actually
676 Returns base full path for that repository means where it actually
677 exists on a filesystem
677 exists on a filesystem
678 """
678 """
679 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
679 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
680 Repository.url_sep())
680 Repository.url_sep())
681 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
681 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
682 return q.one().ui_value
682 return q.one().ui_value
683
683
684 @property
684 @property
685 def repo_full_path(self):
685 def repo_full_path(self):
686 p = [self.repo_path]
686 p = [self.repo_path]
687 # we need to split the name by / since this is how we store the
687 # we need to split the name by / since this is how we store the
688 # names in the database, but that eventually needs to be converted
688 # names in the database, but that eventually needs to be converted
689 # into a valid system path
689 # into a valid system path
690 p += self.repo_name.split(Repository.url_sep())
690 p += self.repo_name.split(Repository.url_sep())
691 return os.path.join(*p)
691 return os.path.join(*p)
692
692
693 def get_new_name(self, repo_name):
693 def get_new_name(self, repo_name):
694 """
694 """
695 returns new full repository name based on assigned group and new new
695 returns new full repository name based on assigned group and new new
696
696
697 :param group_name:
697 :param group_name:
698 """
698 """
699 path_prefix = self.group.full_path_splitted if self.group else []
699 path_prefix = self.group.full_path_splitted if self.group else []
700 return Repository.url_sep().join(path_prefix + [repo_name])
700 return Repository.url_sep().join(path_prefix + [repo_name])
701
701
702 @property
702 @property
703 def _ui(self):
703 def _ui(self):
704 """
704 """
705 Creates an db based ui object for this repository
705 Creates an db based ui object for this repository
706 """
706 """
707 from mercurial import ui
707 from mercurial import ui
708 from mercurial import config
708 from mercurial import config
709 baseui = ui.ui()
709 baseui = ui.ui()
710
710
711 #clean the baseui object
711 #clean the baseui object
712 baseui._ocfg = config.config()
712 baseui._ocfg = config.config()
713 baseui._ucfg = config.config()
713 baseui._ucfg = config.config()
714 baseui._tcfg = config.config()
714 baseui._tcfg = config.config()
715
715
716 ret = RhodeCodeUi.query()\
716 ret = RhodeCodeUi.query()\
717 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
717 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
718
718
719 hg_ui = ret
719 hg_ui = ret
720 for ui_ in hg_ui:
720 for ui_ in hg_ui:
721 if ui_.ui_active:
721 if ui_.ui_active:
722 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
722 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
723 ui_.ui_key, ui_.ui_value)
723 ui_.ui_key, ui_.ui_value)
724 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
724 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
725
725
726 return baseui
726 return baseui
727
727
728 @classmethod
728 @classmethod
729 def inject_ui(cls, repo, extras={}):
729 def inject_ui(cls, repo, extras={}):
730 from rhodecode.lib.vcs.backends.hg import MercurialRepository
730 from rhodecode.lib.vcs.backends.hg import MercurialRepository
731 from rhodecode.lib.vcs.backends.git import GitRepository
731 from rhodecode.lib.vcs.backends.git import GitRepository
732 required = (MercurialRepository, GitRepository)
732 required = (MercurialRepository, GitRepository)
733 if not isinstance(repo, required):
733 if not isinstance(repo, required):
734 raise Exception('repo must be instance of %s' % required)
734 raise Exception('repo must be instance of %s' % required)
735
735
736 # inject ui extra param to log this action via push logger
736 # inject ui extra param to log this action via push logger
737 for k, v in extras.items():
737 for k, v in extras.items():
738 repo._repo.ui.setconfig('rhodecode_extras', k, v)
738 repo._repo.ui.setconfig('rhodecode_extras', k, v)
739
739
740 @classmethod
740 @classmethod
741 def is_valid(cls, repo_name):
741 def is_valid(cls, repo_name):
742 """
742 """
743 returns True if given repo name is a valid filesystem repository
743 returns True if given repo name is a valid filesystem repository
744
744
745 :param cls:
745 :param cls:
746 :param repo_name:
746 :param repo_name:
747 """
747 """
748 from rhodecode.lib.utils import is_valid_repo
748 from rhodecode.lib.utils import is_valid_repo
749
749
750 return is_valid_repo(repo_name, cls.base_path())
750 return is_valid_repo(repo_name, cls.base_path())
751
751
752 def get_api_data(self):
752 def get_api_data(self):
753 """
753 """
754 Common function for generating repo api data
754 Common function for generating repo api data
755
755
756 """
756 """
757 repo = self
757 repo = self
758 data = dict(
758 data = dict(
759 repo_id=repo.repo_id,
759 repo_id=repo.repo_id,
760 repo_name=repo.repo_name,
760 repo_name=repo.repo_name,
761 repo_type=repo.repo_type,
761 repo_type=repo.repo_type,
762 clone_uri=repo.clone_uri,
762 clone_uri=repo.clone_uri,
763 private=repo.private,
763 private=repo.private,
764 created_on=repo.created_on,
764 created_on=repo.created_on,
765 description=repo.description,
765 description=repo.description,
766 landing_rev=repo.landing_rev,
766 landing_rev=repo.landing_rev,
767 owner=repo.user.username,
767 owner=repo.user.username,
768 fork_of=repo.fork.repo_name if repo.fork else None
768 fork_of=repo.fork.repo_name if repo.fork else None
769 )
769 )
770
770
771 return data
771 return data
772
772
773 #==========================================================================
773 #==========================================================================
774 # SCM PROPERTIES
774 # SCM PROPERTIES
775 #==========================================================================
775 #==========================================================================
776
776
777 def get_changeset(self, rev=None):
777 def get_changeset(self, rev=None):
778 return get_changeset_safe(self.scm_instance, rev)
778 return get_changeset_safe(self.scm_instance, rev)
779
779
780 def get_landing_changeset(self):
780 def get_landing_changeset(self):
781 """
781 """
782 Returns landing changeset, or if that doesn't exist returns the tip
782 Returns landing changeset, or if that doesn't exist returns the tip
783 """
783 """
784 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
784 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
785 return cs
785 return cs
786
786
787 @property
787 @property
788 def tip(self):
788 def tip(self):
789 return self.get_changeset('tip')
789 return self.get_changeset('tip')
790
790
791 @property
791 @property
792 def author(self):
792 def author(self):
793 return self.tip.author
793 return self.tip.author
794
794
795 @property
795 @property
796 def last_change(self):
796 def last_change(self):
797 return self.scm_instance.last_change
797 return self.scm_instance.last_change
798
798
799 def get_comments(self, revisions=None):
799 def get_comments(self, revisions=None):
800 """
800 """
801 Returns comments for this repository grouped by revisions
801 Returns comments for this repository grouped by revisions
802
802
803 :param revisions: filter query by revisions only
803 :param revisions: filter query by revisions only
804 """
804 """
805 cmts = ChangesetComment.query()\
805 cmts = ChangesetComment.query()\
806 .filter(ChangesetComment.repo == self)
806 .filter(ChangesetComment.repo == self)
807 if revisions:
807 if revisions:
808 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
808 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
809 grouped = defaultdict(list)
809 grouped = defaultdict(list)
810 for cmt in cmts.all():
810 for cmt in cmts.all():
811 grouped[cmt.revision].append(cmt)
811 grouped[cmt.revision].append(cmt)
812 return grouped
812 return grouped
813
813
814 def statuses(self, revisions=None):
814 def statuses(self, revisions=None):
815 """
815 """
816 Returns statuses for this repository
816 Returns statuses for this repository
817
817
818 :param revisions: list of revisions to get statuses for
818 :param revisions: list of revisions to get statuses for
819 :type revisions: list
819 :type revisions: list
820 """
820 """
821
821
822 statuses = ChangesetStatus.query()\
822 statuses = ChangesetStatus.query()\
823 .filter(ChangesetStatus.repo == self)\
823 .filter(ChangesetStatus.repo == self)\
824 .filter(ChangesetStatus.version == 0)
824 .filter(ChangesetStatus.version == 0)
825 if revisions:
825 if revisions:
826 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
826 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
827 grouped = {}
827 grouped = {}
828
828
829 #maybe we have open new pullrequest without a status ?
829 #maybe we have open new pullrequest without a status ?
830 stat = ChangesetStatus.STATUS_UNDER_REVIEW
830 stat = ChangesetStatus.STATUS_UNDER_REVIEW
831 status_lbl = ChangesetStatus.get_status_lbl(stat)
831 status_lbl = ChangesetStatus.get_status_lbl(stat)
832 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
832 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
833 for rev in pr.revisions:
833 for rev in pr.revisions:
834 pr_id = pr.pull_request_id
834 pr_id = pr.pull_request_id
835 pr_repo = pr.other_repo.repo_name
835 pr_repo = pr.other_repo.repo_name
836 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
836 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
837
837
838 for stat in statuses.all():
838 for stat in statuses.all():
839 pr_id = pr_repo = None
839 pr_id = pr_repo = None
840 if stat.pull_request:
840 if stat.pull_request:
841 pr_id = stat.pull_request.pull_request_id
841 pr_id = stat.pull_request.pull_request_id
842 pr_repo = stat.pull_request.other_repo.repo_name
842 pr_repo = stat.pull_request.other_repo.repo_name
843 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
843 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
844 pr_id, pr_repo]
844 pr_id, pr_repo]
845 return grouped
845 return grouped
846
846
847 #==========================================================================
847 #==========================================================================
848 # SCM CACHE INSTANCE
848 # SCM CACHE INSTANCE
849 #==========================================================================
849 #==========================================================================
850
850
851 @property
851 @property
852 def invalidate(self):
852 def invalidate(self):
853 return CacheInvalidation.invalidate(self.repo_name)
853 return CacheInvalidation.invalidate(self.repo_name)
854
854
855 def set_invalidate(self):
855 def set_invalidate(self):
856 """
856 """
857 set a cache for invalidation for this instance
857 set a cache for invalidation for this instance
858 """
858 """
859 CacheInvalidation.set_invalidate(self.repo_name)
859 CacheInvalidation.set_invalidate(self.repo_name)
860
860
861 @LazyProperty
861 @LazyProperty
862 def scm_instance(self):
862 def scm_instance(self):
863 return self.__get_instance()
863 return self.__get_instance()
864
864
865 def scm_instance_cached(self, cache_map=None):
865 def scm_instance_cached(self, cache_map=None):
866 @cache_region('long_term')
866 @cache_region('long_term')
867 def _c(repo_name):
867 def _c(repo_name):
868 return self.__get_instance()
868 return self.__get_instance()
869 rn = self.repo_name
869 rn = self.repo_name
870 log.debug('Getting cached instance of repo')
870 log.debug('Getting cached instance of repo')
871
871
872 if cache_map:
872 if cache_map:
873 # get using prefilled cache_map
873 # get using prefilled cache_map
874 invalidate_repo = cache_map[self.repo_name]
874 invalidate_repo = cache_map[self.repo_name]
875 if invalidate_repo:
875 if invalidate_repo:
876 invalidate_repo = (None if invalidate_repo.cache_active
876 invalidate_repo = (None if invalidate_repo.cache_active
877 else invalidate_repo)
877 else invalidate_repo)
878 else:
878 else:
879 # get from invalidate
879 # get from invalidate
880 invalidate_repo = self.invalidate
880 invalidate_repo = self.invalidate
881
881
882 if invalidate_repo is not None:
882 if invalidate_repo is not None:
883 region_invalidate(_c, None, rn)
883 region_invalidate(_c, None, rn)
884 # update our cache
884 # update our cache
885 CacheInvalidation.set_valid(invalidate_repo.cache_key)
885 CacheInvalidation.set_valid(invalidate_repo.cache_key)
886 return _c(rn)
886 return _c(rn)
887
887
888 def __get_instance(self):
888 def __get_instance(self):
889 repo_full_path = self.repo_full_path
889 repo_full_path = self.repo_full_path
890 try:
890 try:
891 alias = get_scm(repo_full_path)[0]
891 alias = get_scm(repo_full_path)[0]
892 log.debug('Creating instance of %s repository' % alias)
892 log.debug('Creating instance of %s repository' % alias)
893 backend = get_backend(alias)
893 backend = get_backend(alias)
894 except VCSError:
894 except VCSError:
895 log.error(traceback.format_exc())
895 log.error(traceback.format_exc())
896 log.error('Perhaps this repository is in db and not in '
896 log.error('Perhaps this repository is in db and not in '
897 'filesystem run rescan repositories with '
897 'filesystem run rescan repositories with '
898 '"destroy old data " option from admin panel')
898 '"destroy old data " option from admin panel')
899 return
899 return
900
900
901 if alias == 'hg':
901 if alias == 'hg':
902
902
903 repo = backend(safe_str(repo_full_path), create=False,
903 repo = backend(safe_str(repo_full_path), create=False,
904 baseui=self._ui)
904 baseui=self._ui)
905 # skip hidden web repository
905 # skip hidden web repository
906 if repo._get_hidden():
906 if repo._get_hidden():
907 return
907 return
908 else:
908 else:
909 repo = backend(repo_full_path, create=False)
909 repo = backend(repo_full_path, create=False)
910
910
911 return repo
911 return repo
912
912
913
913
914 class RepoGroup(Base, BaseModel):
914 class RepoGroup(Base, BaseModel):
915 __tablename__ = 'groups'
915 __tablename__ = 'groups'
916 __table_args__ = (
916 __table_args__ = (
917 UniqueConstraint('group_name', 'group_parent_id'),
917 UniqueConstraint('group_name', 'group_parent_id'),
918 CheckConstraint('group_id != group_parent_id'),
918 CheckConstraint('group_id != group_parent_id'),
919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
920 'mysql_charset': 'utf8'},
920 'mysql_charset': 'utf8'},
921 )
921 )
922 __mapper_args__ = {'order_by': 'group_name'}
922 __mapper_args__ = {'order_by': 'group_name'}
923
923
924 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
924 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
925 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
925 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
926 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
926 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
927 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
927 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
928
928
929 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
929 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
930 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
930 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
931
931
932 parent_group = relationship('RepoGroup', remote_side=group_id)
932 parent_group = relationship('RepoGroup', remote_side=group_id)
933
933
934 def __init__(self, group_name='', parent_group=None):
934 def __init__(self, group_name='', parent_group=None):
935 self.group_name = group_name
935 self.group_name = group_name
936 self.parent_group = parent_group
936 self.parent_group = parent_group
937
937
938 def __unicode__(self):
938 def __unicode__(self):
939 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
939 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
940 self.group_name)
940 self.group_name)
941
941
942 @classmethod
942 @classmethod
943 def groups_choices(cls):
943 def groups_choices(cls):
944 from webhelpers.html import literal as _literal
944 from webhelpers.html import literal as _literal
945 repo_groups = [('', '')]
945 repo_groups = [('', '')]
946 sep = ' &raquo; '
946 sep = ' &raquo; '
947 _name = lambda k: _literal(sep.join(k))
947 _name = lambda k: _literal(sep.join(k))
948
948
949 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
949 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
950 for x in cls.query().all()])
950 for x in cls.query().all()])
951
951
952 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
952 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
953 return repo_groups
953 return repo_groups
954
954
955 @classmethod
955 @classmethod
956 def url_sep(cls):
956 def url_sep(cls):
957 return URL_SEP
957 return URL_SEP
958
958
959 @classmethod
959 @classmethod
960 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
960 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
961 if case_insensitive:
961 if case_insensitive:
962 gr = cls.query()\
962 gr = cls.query()\
963 .filter(cls.group_name.ilike(group_name))
963 .filter(cls.group_name.ilike(group_name))
964 else:
964 else:
965 gr = cls.query()\
965 gr = cls.query()\
966 .filter(cls.group_name == group_name)
966 .filter(cls.group_name == group_name)
967 if cache:
967 if cache:
968 gr = gr.options(FromCache(
968 gr = gr.options(FromCache(
969 "sql_cache_short",
969 "sql_cache_short",
970 "get_group_%s" % _hash_key(group_name)
970 "get_group_%s" % _hash_key(group_name)
971 )
971 )
972 )
972 )
973 return gr.scalar()
973 return gr.scalar()
974
974
975 @property
975 @property
976 def parents(self):
976 def parents(self):
977 parents_recursion_limit = 5
977 parents_recursion_limit = 5
978 groups = []
978 groups = []
979 if self.parent_group is None:
979 if self.parent_group is None:
980 return groups
980 return groups
981 cur_gr = self.parent_group
981 cur_gr = self.parent_group
982 groups.insert(0, cur_gr)
982 groups.insert(0, cur_gr)
983 cnt = 0
983 cnt = 0
984 while 1:
984 while 1:
985 cnt += 1
985 cnt += 1
986 gr = getattr(cur_gr, 'parent_group', None)
986 gr = getattr(cur_gr, 'parent_group', None)
987 cur_gr = cur_gr.parent_group
987 cur_gr = cur_gr.parent_group
988 if gr is None:
988 if gr is None:
989 break
989 break
990 if cnt == parents_recursion_limit:
990 if cnt == parents_recursion_limit:
991 # this will prevent accidental infinit loops
991 # this will prevent accidental infinit loops
992 log.error('group nested more than %s' %
992 log.error('group nested more than %s' %
993 parents_recursion_limit)
993 parents_recursion_limit)
994 break
994 break
995
995
996 groups.insert(0, gr)
996 groups.insert(0, gr)
997 return groups
997 return groups
998
998
999 @property
999 @property
1000 def children(self):
1000 def children(self):
1001 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1001 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1002
1002
1003 @property
1003 @property
1004 def name(self):
1004 def name(self):
1005 return self.group_name.split(RepoGroup.url_sep())[-1]
1005 return self.group_name.split(RepoGroup.url_sep())[-1]
1006
1006
1007 @property
1007 @property
1008 def full_path(self):
1008 def full_path(self):
1009 return self.group_name
1009 return self.group_name
1010
1010
1011 @property
1011 @property
1012 def full_path_splitted(self):
1012 def full_path_splitted(self):
1013 return self.group_name.split(RepoGroup.url_sep())
1013 return self.group_name.split(RepoGroup.url_sep())
1014
1014
1015 @property
1015 @property
1016 def repositories(self):
1016 def repositories(self):
1017 return Repository.query()\
1017 return Repository.query()\
1018 .filter(Repository.group == self)\
1018 .filter(Repository.group == self)\
1019 .order_by(Repository.repo_name)
1019 .order_by(Repository.repo_name)
1020
1020
1021 @property
1021 @property
1022 def repositories_recursive_count(self):
1022 def repositories_recursive_count(self):
1023 cnt = self.repositories.count()
1023 cnt = self.repositories.count()
1024
1024
1025 def children_count(group):
1025 def children_count(group):
1026 cnt = 0
1026 cnt = 0
1027 for child in group.children:
1027 for child in group.children:
1028 cnt += child.repositories.count()
1028 cnt += child.repositories.count()
1029 cnt += children_count(child)
1029 cnt += children_count(child)
1030 return cnt
1030 return cnt
1031
1031
1032 return cnt + children_count(self)
1032 return cnt + children_count(self)
1033
1033
1034 def get_new_name(self, group_name):
1034 def get_new_name(self, group_name):
1035 """
1035 """
1036 returns new full group name based on parent and new name
1036 returns new full group name based on parent and new name
1037
1037
1038 :param group_name:
1038 :param group_name:
1039 """
1039 """
1040 path_prefix = (self.parent_group.full_path_splitted if
1040 path_prefix = (self.parent_group.full_path_splitted if
1041 self.parent_group else [])
1041 self.parent_group else [])
1042 return RepoGroup.url_sep().join(path_prefix + [group_name])
1042 return RepoGroup.url_sep().join(path_prefix + [group_name])
1043
1043
1044
1044
1045 class Permission(Base, BaseModel):
1045 class Permission(Base, BaseModel):
1046 __tablename__ = 'permissions'
1046 __tablename__ = 'permissions'
1047 __table_args__ = (
1047 __table_args__ = (
1048 Index('p_perm_name_idx', 'permission_name'),
1048 Index('p_perm_name_idx', 'permission_name'),
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 PERMS = [
1052 PERMS = [
1053 ('repository.none', _('Repository no access')),
1053 ('repository.none', _('Repository no access')),
1054 ('repository.read', _('Repository read access')),
1054 ('repository.read', _('Repository read access')),
1055 ('repository.write', _('Repository write access')),
1055 ('repository.write', _('Repository write access')),
1056 ('repository.admin', _('Repository admin access')),
1056 ('repository.admin', _('Repository admin access')),
1057
1057
1058 ('group.none', _('Repositories Group no access')),
1058 ('group.none', _('Repositories Group no access')),
1059 ('group.read', _('Repositories Group read access')),
1059 ('group.read', _('Repositories Group read access')),
1060 ('group.write', _('Repositories Group write access')),
1060 ('group.write', _('Repositories Group write access')),
1061 ('group.admin', _('Repositories Group admin access')),
1061 ('group.admin', _('Repositories Group admin access')),
1062
1062
1063 ('hg.admin', _('RhodeCode Administrator')),
1063 ('hg.admin', _('RhodeCode Administrator')),
1064 ('hg.create.none', _('Repository creation disabled')),
1064 ('hg.create.none', _('Repository creation disabled')),
1065 ('hg.create.repository', _('Repository creation enabled')),
1065 ('hg.create.repository', _('Repository creation enabled')),
1066 ('hg.register.none', _('Register disabled')),
1066 ('hg.register.none', _('Register disabled')),
1067 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1067 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1068 'with manual activation')),
1068 'with manual activation')),
1069
1069
1070 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1070 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1071 'with auto activation')),
1071 'with auto activation')),
1072 ]
1072 ]
1073
1073
1074 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1074 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1075 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1075 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1076 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1076 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1077
1077
1078 def __unicode__(self):
1078 def __unicode__(self):
1079 return u"<%s('%s:%s')>" % (
1079 return u"<%s('%s:%s')>" % (
1080 self.__class__.__name__, self.permission_id, self.permission_name
1080 self.__class__.__name__, self.permission_id, self.permission_name
1081 )
1081 )
1082
1082
1083 @classmethod
1083 @classmethod
1084 def get_by_key(cls, key):
1084 def get_by_key(cls, key):
1085 return cls.query().filter(cls.permission_name == key).scalar()
1085 return cls.query().filter(cls.permission_name == key).scalar()
1086
1086
1087 @classmethod
1087 @classmethod
1088 def get_default_perms(cls, default_user_id):
1088 def get_default_perms(cls, default_user_id):
1089 q = Session().query(UserRepoToPerm, Repository, cls)\
1089 q = Session().query(UserRepoToPerm, Repository, cls)\
1090 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1090 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1091 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1091 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1092 .filter(UserRepoToPerm.user_id == default_user_id)
1092 .filter(UserRepoToPerm.user_id == default_user_id)
1093
1093
1094 return q.all()
1094 return q.all()
1095
1095
1096 @classmethod
1096 @classmethod
1097 def get_default_group_perms(cls, default_user_id):
1097 def get_default_group_perms(cls, default_user_id):
1098 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1098 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1099 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1099 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1100 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1100 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1101 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1101 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1102
1102
1103 return q.all()
1103 return q.all()
1104
1104
1105
1105
1106 class UserRepoToPerm(Base, BaseModel):
1106 class UserRepoToPerm(Base, BaseModel):
1107 __tablename__ = 'repo_to_perm'
1107 __tablename__ = 'repo_to_perm'
1108 __table_args__ = (
1108 __table_args__ = (
1109 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1109 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1110 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1110 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1111 'mysql_charset': 'utf8'}
1111 'mysql_charset': 'utf8'}
1112 )
1112 )
1113 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1113 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1114 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1114 user_id = Column("user_id", Integer(), ForeignKey('users.user_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 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1116 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1117
1117
1118 user = relationship('User')
1118 user = relationship('User')
1119 repository = relationship('Repository')
1119 repository = relationship('Repository')
1120 permission = relationship('Permission')
1120 permission = relationship('Permission')
1121
1121
1122 @classmethod
1122 @classmethod
1123 def create(cls, user, repository, permission):
1123 def create(cls, user, repository, permission):
1124 n = cls()
1124 n = cls()
1125 n.user = user
1125 n.user = user
1126 n.repository = repository
1126 n.repository = repository
1127 n.permission = permission
1127 n.permission = permission
1128 Session().add(n)
1128 Session().add(n)
1129 return n
1129 return n
1130
1130
1131 def __unicode__(self):
1131 def __unicode__(self):
1132 return u'<user:%s => %s >' % (self.user, self.repository)
1132 return u'<user:%s => %s >' % (self.user, self.repository)
1133
1133
1134
1134
1135 class UserToPerm(Base, BaseModel):
1135 class UserToPerm(Base, BaseModel):
1136 __tablename__ = 'user_to_perm'
1136 __tablename__ = 'user_to_perm'
1137 __table_args__ = (
1137 __table_args__ = (
1138 UniqueConstraint('user_id', 'permission_id'),
1138 UniqueConstraint('user_id', 'permission_id'),
1139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1140 'mysql_charset': 'utf8'}
1140 'mysql_charset': 'utf8'}
1141 )
1141 )
1142 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1142 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1143 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1143 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1144 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1144 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1145
1145
1146 user = relationship('User')
1146 user = relationship('User')
1147 permission = relationship('Permission', lazy='joined')
1147 permission = relationship('Permission', lazy='joined')
1148
1148
1149
1149
1150 class UsersGroupRepoToPerm(Base, BaseModel):
1150 class UsersGroupRepoToPerm(Base, BaseModel):
1151 __tablename__ = 'users_group_repo_to_perm'
1151 __tablename__ = 'users_group_repo_to_perm'
1152 __table_args__ = (
1152 __table_args__ = (
1153 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1153 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1155 'mysql_charset': 'utf8'}
1155 'mysql_charset': 'utf8'}
1156 )
1156 )
1157 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1157 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1158 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1158 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1159 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1159 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1160 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1160 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1161
1161
1162 users_group = relationship('UsersGroup')
1162 users_group = relationship('UsersGroup')
1163 permission = relationship('Permission')
1163 permission = relationship('Permission')
1164 repository = relationship('Repository')
1164 repository = relationship('Repository')
1165
1165
1166 @classmethod
1166 @classmethod
1167 def create(cls, users_group, repository, permission):
1167 def create(cls, users_group, repository, permission):
1168 n = cls()
1168 n = cls()
1169 n.users_group = users_group
1169 n.users_group = users_group
1170 n.repository = repository
1170 n.repository = repository
1171 n.permission = permission
1171 n.permission = permission
1172 Session().add(n)
1172 Session().add(n)
1173 return n
1173 return n
1174
1174
1175 def __unicode__(self):
1175 def __unicode__(self):
1176 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1176 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1177
1177
1178
1178
1179 class UsersGroupToPerm(Base, BaseModel):
1179 class UsersGroupToPerm(Base, BaseModel):
1180 __tablename__ = 'users_group_to_perm'
1180 __tablename__ = 'users_group_to_perm'
1181 __table_args__ = (
1181 __table_args__ = (
1182 UniqueConstraint('users_group_id', 'permission_id',),
1182 UniqueConstraint('users_group_id', 'permission_id',),
1183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1184 'mysql_charset': 'utf8'}
1184 'mysql_charset': 'utf8'}
1185 )
1185 )
1186 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1186 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1187 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1187 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1188 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1188 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1189
1189
1190 users_group = relationship('UsersGroup')
1190 users_group = relationship('UsersGroup')
1191 permission = relationship('Permission')
1191 permission = relationship('Permission')
1192
1192
1193
1193
1194 class UserRepoGroupToPerm(Base, BaseModel):
1194 class UserRepoGroupToPerm(Base, BaseModel):
1195 __tablename__ = 'user_repo_group_to_perm'
1195 __tablename__ = 'user_repo_group_to_perm'
1196 __table_args__ = (
1196 __table_args__ = (
1197 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1197 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1199 'mysql_charset': 'utf8'}
1199 'mysql_charset': 'utf8'}
1200 )
1200 )
1201
1201
1202 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1202 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1204 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1204 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1205 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1205 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1206
1206
1207 user = relationship('User')
1207 user = relationship('User')
1208 group = relationship('RepoGroup')
1208 group = relationship('RepoGroup')
1209 permission = relationship('Permission')
1209 permission = relationship('Permission')
1210
1210
1211
1211
1212 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1212 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1213 __tablename__ = 'users_group_repo_group_to_perm'
1213 __tablename__ = 'users_group_repo_group_to_perm'
1214 __table_args__ = (
1214 __table_args__ = (
1215 UniqueConstraint('users_group_id', 'group_id'),
1215 UniqueConstraint('users_group_id', 'group_id'),
1216 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1216 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1217 'mysql_charset': 'utf8'}
1217 'mysql_charset': 'utf8'}
1218 )
1218 )
1219
1219
1220 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)
1220 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)
1221 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1221 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1222 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1222 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1223 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1223 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1224
1224
1225 users_group = relationship('UsersGroup')
1225 users_group = relationship('UsersGroup')
1226 permission = relationship('Permission')
1226 permission = relationship('Permission')
1227 group = relationship('RepoGroup')
1227 group = relationship('RepoGroup')
1228
1228
1229
1229
1230 class Statistics(Base, BaseModel):
1230 class Statistics(Base, BaseModel):
1231 __tablename__ = 'statistics'
1231 __tablename__ = 'statistics'
1232 __table_args__ = (
1232 __table_args__ = (
1233 UniqueConstraint('repository_id'),
1233 UniqueConstraint('repository_id'),
1234 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1234 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1235 'mysql_charset': 'utf8'}
1235 'mysql_charset': 'utf8'}
1236 )
1236 )
1237 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1237 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1238 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1238 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1239 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1239 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1240 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1240 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1241 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1241 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1242 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1242 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1243
1243
1244 repository = relationship('Repository', single_parent=True)
1244 repository = relationship('Repository', single_parent=True)
1245
1245
1246
1246
1247 class UserFollowing(Base, BaseModel):
1247 class UserFollowing(Base, BaseModel):
1248 __tablename__ = 'user_followings'
1248 __tablename__ = 'user_followings'
1249 __table_args__ = (
1249 __table_args__ = (
1250 UniqueConstraint('user_id', 'follows_repository_id'),
1250 UniqueConstraint('user_id', 'follows_repository_id'),
1251 UniqueConstraint('user_id', 'follows_user_id'),
1251 UniqueConstraint('user_id', 'follows_user_id'),
1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1253 'mysql_charset': 'utf8'}
1253 'mysql_charset': 'utf8'}
1254 )
1254 )
1255
1255
1256 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1256 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1257 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1257 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1258 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1258 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1259 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1259 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1260 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1260 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1261
1261
1262 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1262 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1263
1263
1264 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1264 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1265 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1265 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1266
1266
1267 @classmethod
1267 @classmethod
1268 def get_repo_followers(cls, repo_id):
1268 def get_repo_followers(cls, repo_id):
1269 return cls.query().filter(cls.follows_repo_id == repo_id)
1269 return cls.query().filter(cls.follows_repo_id == repo_id)
1270
1270
1271
1271
1272 class CacheInvalidation(Base, BaseModel):
1272 class CacheInvalidation(Base, BaseModel):
1273 __tablename__ = 'cache_invalidation'
1273 __tablename__ = 'cache_invalidation'
1274 __table_args__ = (
1274 __table_args__ = (
1275 UniqueConstraint('cache_key'),
1275 UniqueConstraint('cache_key'),
1276 Index('key_idx', 'cache_key'),
1276 Index('key_idx', 'cache_key'),
1277 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1277 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1278 'mysql_charset': 'utf8'},
1278 'mysql_charset': 'utf8'},
1279 )
1279 )
1280 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1280 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1281 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1281 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1282 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1282 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1283 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1283 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1284
1284
1285 def __init__(self, cache_key, cache_args=''):
1285 def __init__(self, cache_key, cache_args=''):
1286 self.cache_key = cache_key
1286 self.cache_key = cache_key
1287 self.cache_args = cache_args
1287 self.cache_args = cache_args
1288 self.cache_active = False
1288 self.cache_active = False
1289
1289
1290 def __unicode__(self):
1290 def __unicode__(self):
1291 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1291 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1292 self.cache_id, self.cache_key)
1292 self.cache_id, self.cache_key)
1293
1293
1294 @classmethod
1294 @classmethod
1295 def clear_cache(cls):
1295 def clear_cache(cls):
1296 cls.query().delete()
1296 cls.query().delete()
1297
1297
1298 @classmethod
1298 @classmethod
1299 def _get_key(cls, key):
1299 def _get_key(cls, key):
1300 """
1300 """
1301 Wrapper for generating a key, together with a prefix
1301 Wrapper for generating a key, together with a prefix
1302
1302
1303 :param key:
1303 :param key:
1304 """
1304 """
1305 import rhodecode
1305 import rhodecode
1306 prefix = ''
1306 prefix = ''
1307 iid = rhodecode.CONFIG.get('instance_id')
1307 iid = rhodecode.CONFIG.get('instance_id')
1308 if iid:
1308 if iid:
1309 prefix = iid
1309 prefix = iid
1310 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1310 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1311
1311
1312 @classmethod
1312 @classmethod
1313 def get_by_key(cls, key):
1313 def get_by_key(cls, key):
1314 return cls.query().filter(cls.cache_key == key).scalar()
1314 return cls.query().filter(cls.cache_key == key).scalar()
1315
1315
1316 @classmethod
1316 @classmethod
1317 def _get_or_create_key(cls, key, prefix, org_key):
1317 def _get_or_create_key(cls, key, prefix, org_key):
1318 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1318 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1319 if not inv_obj:
1319 if not inv_obj:
1320 try:
1320 try:
1321 inv_obj = CacheInvalidation(key, org_key)
1321 inv_obj = CacheInvalidation(key, org_key)
1322 Session().add(inv_obj)
1322 Session().add(inv_obj)
1323 Session().commit()
1323 Session().commit()
1324 except Exception:
1324 except Exception:
1325 log.error(traceback.format_exc())
1325 log.error(traceback.format_exc())
1326 Session().rollback()
1326 Session().rollback()
1327 return inv_obj
1327 return inv_obj
1328
1328
1329 @classmethod
1329 @classmethod
1330 def invalidate(cls, key):
1330 def invalidate(cls, key):
1331 """
1331 """
1332 Returns Invalidation object if this given key should be invalidated
1332 Returns Invalidation object if this given key should be invalidated
1333 None otherwise. `cache_active = False` means that this cache
1333 None otherwise. `cache_active = False` means that this cache
1334 state is not valid and needs to be invalidated
1334 state is not valid and needs to be invalidated
1335
1335
1336 :param key:
1336 :param key:
1337 """
1337 """
1338
1338
1339 key, _prefix, _org_key = cls._get_key(key)
1339 key, _prefix, _org_key = cls._get_key(key)
1340 inv = cls._get_or_create_key(key, _prefix, _org_key)
1340 inv = cls._get_or_create_key(key, _prefix, _org_key)
1341
1341
1342 if inv and inv.cache_active is False:
1342 if inv and inv.cache_active is False:
1343 return inv
1343 return inv
1344
1344
1345 @classmethod
1345 @classmethod
1346 def set_invalidate(cls, key):
1346 def set_invalidate(cls, key):
1347 """
1347 """
1348 Mark this Cache key for invalidation
1348 Mark this Cache key for invalidation
1349
1349
1350 :param key:
1350 :param key:
1351 """
1351 """
1352
1352
1353 key, _prefix, _org_key = cls._get_key(key)
1353 key, _prefix, _org_key = cls._get_key(key)
1354 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1354 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1355 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1355 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1356 _org_key))
1356 _org_key))
1357 try:
1357 try:
1358 for inv_obj in inv_objs:
1358 for inv_obj in inv_objs:
1359 if inv_obj:
1359 if inv_obj:
1360 inv_obj.cache_active = False
1360 inv_obj.cache_active = False
1361
1361
1362 Session().add(inv_obj)
1362 Session().add(inv_obj)
1363 Session().commit()
1363 Session().commit()
1364 except Exception:
1364 except Exception:
1365 log.error(traceback.format_exc())
1365 log.error(traceback.format_exc())
1366 Session().rollback()
1366 Session().rollback()
1367
1367
1368 @classmethod
1368 @classmethod
1369 def set_valid(cls, key):
1369 def set_valid(cls, key):
1370 """
1370 """
1371 Mark this cache key as active and currently cached
1371 Mark this cache key as active and currently cached
1372
1372
1373 :param key:
1373 :param key:
1374 """
1374 """
1375 inv_obj = cls.get_by_key(key)
1375 inv_obj = cls.get_by_key(key)
1376 inv_obj.cache_active = True
1376 inv_obj.cache_active = True
1377 Session().add(inv_obj)
1377 Session().add(inv_obj)
1378 Session().commit()
1378 Session().commit()
1379
1379
1380 @classmethod
1380 @classmethod
1381 def get_cache_map(cls):
1381 def get_cache_map(cls):
1382
1382
1383 class cachemapdict(dict):
1383 class cachemapdict(dict):
1384
1384
1385 def __init__(self, *args, **kwargs):
1385 def __init__(self, *args, **kwargs):
1386 fixkey = kwargs.get('fixkey')
1386 fixkey = kwargs.get('fixkey')
1387 if fixkey:
1387 if fixkey:
1388 del kwargs['fixkey']
1388 del kwargs['fixkey']
1389 self.fixkey = fixkey
1389 self.fixkey = fixkey
1390 super(cachemapdict, self).__init__(*args, **kwargs)
1390 super(cachemapdict, self).__init__(*args, **kwargs)
1391
1391
1392 def __getattr__(self, name):
1392 def __getattr__(self, name):
1393 key = name
1393 key = name
1394 if self.fixkey:
1394 if self.fixkey:
1395 key, _prefix, _org_key = cls._get_key(key)
1395 key, _prefix, _org_key = cls._get_key(key)
1396 if key in self.__dict__:
1396 if key in self.__dict__:
1397 return self.__dict__[key]
1397 return self.__dict__[key]
1398 else:
1398 else:
1399 return self[key]
1399 return self[key]
1400
1400
1401 def __getitem__(self, key):
1401 def __getitem__(self, key):
1402 if self.fixkey:
1402 if self.fixkey:
1403 key, _prefix, _org_key = cls._get_key(key)
1403 key, _prefix, _org_key = cls._get_key(key)
1404 try:
1404 try:
1405 return super(cachemapdict, self).__getitem__(key)
1405 return super(cachemapdict, self).__getitem__(key)
1406 except KeyError:
1406 except KeyError:
1407 return
1407 return
1408
1408
1409 cache_map = cachemapdict(fixkey=True)
1409 cache_map = cachemapdict(fixkey=True)
1410 for obj in cls.query().all():
1410 for obj in cls.query().all():
1411 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1411 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1412 return cache_map
1412 return cache_map
1413
1413
1414
1414
1415 class ChangesetComment(Base, BaseModel):
1415 class ChangesetComment(Base, BaseModel):
1416 __tablename__ = 'changeset_comments'
1416 __tablename__ = 'changeset_comments'
1417 __table_args__ = (
1417 __table_args__ = (
1418 Index('cc_revision_idx', 'revision'),
1418 Index('cc_revision_idx', 'revision'),
1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1420 'mysql_charset': 'utf8'},
1420 'mysql_charset': 'utf8'},
1421 )
1421 )
1422 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1422 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1423 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1423 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1424 revision = Column('revision', String(40), nullable=True)
1424 revision = Column('revision', String(40), nullable=True)
1425 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1425 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1426 line_no = Column('line_no', Unicode(10), nullable=True)
1426 line_no = Column('line_no', Unicode(10), nullable=True)
1427 f_path = Column('f_path', Unicode(1000), nullable=True)
1427 f_path = Column('f_path', Unicode(1000), nullable=True)
1428 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1428 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1429 text = Column('text', Unicode(25000), nullable=False)
1429 text = Column('text', Unicode(25000), nullable=False)
1430 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1430 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1431
1431
1432 author = relationship('User', lazy='joined')
1432 author = relationship('User', lazy='joined')
1433 repo = relationship('Repository')
1433 repo = relationship('Repository')
1434 status_change = relationship('ChangesetStatus', uselist=False)
1434 status_change = relationship('ChangesetStatus', uselist=False)
1435 pull_request = relationship('PullRequest', lazy='joined')
1435 pull_request = relationship('PullRequest', lazy='joined')
1436
1436
1437 @classmethod
1437 @classmethod
1438 def get_users(cls, revision=None, pull_request_id=None):
1438 def get_users(cls, revision=None, pull_request_id=None):
1439 """
1439 """
1440 Returns user associated with this ChangesetComment. ie those
1440 Returns user associated with this ChangesetComment. ie those
1441 who actually commented
1441 who actually commented
1442
1442
1443 :param cls:
1443 :param cls:
1444 :param revision:
1444 :param revision:
1445 """
1445 """
1446 q = Session().query(User)\
1446 q = Session().query(User)\
1447 .join(ChangesetComment.author)
1447 .join(ChangesetComment.author)
1448 if revision:
1448 if revision:
1449 q = q.filter(cls.revision == revision)
1449 q = q.filter(cls.revision == revision)
1450 elif pull_request_id:
1450 elif pull_request_id:
1451 q = q.filter(cls.pull_request_id == pull_request_id)
1451 q = q.filter(cls.pull_request_id == pull_request_id)
1452 return q.all()
1452 return q.all()
1453
1453
1454
1454
1455 class ChangesetStatus(Base, BaseModel):
1455 class ChangesetStatus(Base, BaseModel):
1456 __tablename__ = 'changeset_statuses'
1456 __tablename__ = 'changeset_statuses'
1457 __table_args__ = (
1457 __table_args__ = (
1458 Index('cs_revision_idx', 'revision'),
1458 Index('cs_revision_idx', 'revision'),
1459 Index('cs_version_idx', 'version'),
1459 Index('cs_version_idx', 'version'),
1460 UniqueConstraint('repo_id', 'revision', 'version'),
1460 UniqueConstraint('repo_id', 'revision', 'version'),
1461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1462 'mysql_charset': 'utf8'}
1462 'mysql_charset': 'utf8'}
1463 )
1463 )
1464 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1464 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1465 STATUS_APPROVED = 'approved'
1465 STATUS_APPROVED = 'approved'
1466 STATUS_REJECTED = 'rejected'
1466 STATUS_REJECTED = 'rejected'
1467 STATUS_UNDER_REVIEW = 'under_review'
1467 STATUS_UNDER_REVIEW = 'under_review'
1468
1468
1469 STATUSES = [
1469 STATUSES = [
1470 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1470 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1471 (STATUS_APPROVED, _("Approved")),
1471 (STATUS_APPROVED, _("Approved")),
1472 (STATUS_REJECTED, _("Rejected")),
1472 (STATUS_REJECTED, _("Rejected")),
1473 (STATUS_UNDER_REVIEW, _("Under Review")),
1473 (STATUS_UNDER_REVIEW, _("Under Review")),
1474 ]
1474 ]
1475
1475
1476 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1476 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1477 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1477 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1479 revision = Column('revision', String(40), nullable=False)
1479 revision = Column('revision', String(40), nullable=False)
1480 status = Column('status', String(128), nullable=False, default=DEFAULT)
1480 status = Column('status', String(128), nullable=False, default=DEFAULT)
1481 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1481 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1482 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1482 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1483 version = Column('version', Integer(), nullable=False, default=0)
1483 version = Column('version', Integer(), nullable=False, default=0)
1484 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1484 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1485
1485
1486 author = relationship('User', lazy='joined')
1486 author = relationship('User', lazy='joined')
1487 repo = relationship('Repository')
1487 repo = relationship('Repository')
1488 comment = relationship('ChangesetComment', lazy='joined')
1488 comment = relationship('ChangesetComment', lazy='joined')
1489 pull_request = relationship('PullRequest', lazy='joined')
1489 pull_request = relationship('PullRequest', lazy='joined')
1490
1490
1491 def __unicode__(self):
1491 def __unicode__(self):
1492 return u"<%s('%s:%s')>" % (
1492 return u"<%s('%s:%s')>" % (
1493 self.__class__.__name__,
1493 self.__class__.__name__,
1494 self.status, self.author
1494 self.status, self.author
1495 )
1495 )
1496
1496
1497 @classmethod
1497 @classmethod
1498 def get_status_lbl(cls, value):
1498 def get_status_lbl(cls, value):
1499 return dict(cls.STATUSES).get(value)
1499 return dict(cls.STATUSES).get(value)
1500
1500
1501 @property
1501 @property
1502 def status_lbl(self):
1502 def status_lbl(self):
1503 return ChangesetStatus.get_status_lbl(self.status)
1503 return ChangesetStatus.get_status_lbl(self.status)
1504
1504
1505
1505
1506 class PullRequest(Base, BaseModel):
1506 class PullRequest(Base, BaseModel):
1507 __tablename__ = 'pull_requests'
1507 __tablename__ = 'pull_requests'
1508 __table_args__ = (
1508 __table_args__ = (
1509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1510 'mysql_charset': 'utf8'},
1510 'mysql_charset': 'utf8'},
1511 )
1511 )
1512
1512
1513 STATUS_NEW = u'new'
1513 STATUS_NEW = u'new'
1514 STATUS_OPEN = u'open'
1514 STATUS_OPEN = u'open'
1515 STATUS_CLOSED = u'closed'
1515 STATUS_CLOSED = u'closed'
1516
1516
1517 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1517 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1518 title = Column('title', Unicode(256), nullable=True)
1518 title = Column('title', Unicode(256), nullable=True)
1519 description = Column('description', UnicodeText(10240), nullable=True)
1519 description = Column('description', UnicodeText(10240), nullable=True)
1520 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1520 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1521 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1521 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1522 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1522 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1523 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1523 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1524 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1524 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1525 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1525 org_ref = Column('org_ref', Unicode(256), nullable=False)
1526 org_ref = Column('org_ref', Unicode(256), nullable=False)
1526 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1527 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1527 other_ref = Column('other_ref', Unicode(256), nullable=False)
1528 other_ref = Column('other_ref', Unicode(256), nullable=False)
1528
1529
1530 statuses = relationship('ChangesetStatus')
1531
1529 @hybrid_property
1532 @hybrid_property
1530 def revisions(self):
1533 def revisions(self):
1531 return self._revisions.split(':')
1534 return self._revisions.split(':')
1532
1535
1533 @revisions.setter
1536 @revisions.setter
1534 def revisions(self, val):
1537 def revisions(self, val):
1535 self._revisions = ':'.join(val)
1538 self._revisions = ':'.join(val)
1536
1539
1537 author = relationship('User', lazy='joined')
1540 author = relationship('User', lazy='joined')
1538 reviewers = relationship('PullRequestReviewers')
1541 reviewers = relationship('PullRequestReviewers')
1539 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1542 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1540 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1543 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1541
1544
1545 def is_closed(self):
1546 return self.status == self.STATUS_CLOSED
1547
1542 def __json__(self):
1548 def __json__(self):
1543 return dict(
1549 return dict(
1544 revisions=self.revisions
1550 revisions=self.revisions
1545 )
1551 )
1546
1552
1547
1553
1548 class PullRequestReviewers(Base, BaseModel):
1554 class PullRequestReviewers(Base, BaseModel):
1549 __tablename__ = 'pull_request_reviewers'
1555 __tablename__ = 'pull_request_reviewers'
1550 __table_args__ = (
1556 __table_args__ = (
1551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1557 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 'mysql_charset': 'utf8'},
1558 'mysql_charset': 'utf8'},
1553 )
1559 )
1554
1560
1555 def __init__(self, user=None, pull_request=None):
1561 def __init__(self, user=None, pull_request=None):
1556 self.user = user
1562 self.user = user
1557 self.pull_request = pull_request
1563 self.pull_request = pull_request
1558
1564
1559 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1565 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1560 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1566 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1561 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1562
1568
1563 user = relationship('User')
1569 user = relationship('User')
1564 pull_request = relationship('PullRequest')
1570 pull_request = relationship('PullRequest')
1565
1571
1566
1572
1567 class Notification(Base, BaseModel):
1573 class Notification(Base, BaseModel):
1568 __tablename__ = 'notifications'
1574 __tablename__ = 'notifications'
1569 __table_args__ = (
1575 __table_args__ = (
1570 Index('notification_type_idx', 'type'),
1576 Index('notification_type_idx', 'type'),
1571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1577 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1572 'mysql_charset': 'utf8'},
1578 'mysql_charset': 'utf8'},
1573 )
1579 )
1574
1580
1575 TYPE_CHANGESET_COMMENT = u'cs_comment'
1581 TYPE_CHANGESET_COMMENT = u'cs_comment'
1576 TYPE_MESSAGE = u'message'
1582 TYPE_MESSAGE = u'message'
1577 TYPE_MENTION = u'mention'
1583 TYPE_MENTION = u'mention'
1578 TYPE_REGISTRATION = u'registration'
1584 TYPE_REGISTRATION = u'registration'
1579 TYPE_PULL_REQUEST = u'pull_request'
1585 TYPE_PULL_REQUEST = u'pull_request'
1580 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1586 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1581
1587
1582 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1588 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1583 subject = Column('subject', Unicode(512), nullable=True)
1589 subject = Column('subject', Unicode(512), nullable=True)
1584 body = Column('body', UnicodeText(50000), nullable=True)
1590 body = Column('body', UnicodeText(50000), nullable=True)
1585 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1591 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1592 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1587 type_ = Column('type', Unicode(256))
1593 type_ = Column('type', Unicode(256))
1588
1594
1589 created_by_user = relationship('User')
1595 created_by_user = relationship('User')
1590 notifications_to_users = relationship('UserNotification', lazy='joined',
1596 notifications_to_users = relationship('UserNotification', lazy='joined',
1591 cascade="all, delete, delete-orphan")
1597 cascade="all, delete, delete-orphan")
1592
1598
1593 @property
1599 @property
1594 def recipients(self):
1600 def recipients(self):
1595 return [x.user for x in UserNotification.query()\
1601 return [x.user for x in UserNotification.query()\
1596 .filter(UserNotification.notification == self)\
1602 .filter(UserNotification.notification == self)\
1597 .order_by(UserNotification.user_id.asc()).all()]
1603 .order_by(UserNotification.user_id.asc()).all()]
1598
1604
1599 @classmethod
1605 @classmethod
1600 def create(cls, created_by, subject, body, recipients, type_=None):
1606 def create(cls, created_by, subject, body, recipients, type_=None):
1601 if type_ is None:
1607 if type_ is None:
1602 type_ = Notification.TYPE_MESSAGE
1608 type_ = Notification.TYPE_MESSAGE
1603
1609
1604 notification = cls()
1610 notification = cls()
1605 notification.created_by_user = created_by
1611 notification.created_by_user = created_by
1606 notification.subject = subject
1612 notification.subject = subject
1607 notification.body = body
1613 notification.body = body
1608 notification.type_ = type_
1614 notification.type_ = type_
1609 notification.created_on = datetime.datetime.now()
1615 notification.created_on = datetime.datetime.now()
1610
1616
1611 for u in recipients:
1617 for u in recipients:
1612 assoc = UserNotification()
1618 assoc = UserNotification()
1613 assoc.notification = notification
1619 assoc.notification = notification
1614 u.notifications.append(assoc)
1620 u.notifications.append(assoc)
1615 Session().add(notification)
1621 Session().add(notification)
1616 return notification
1622 return notification
1617
1623
1618 @property
1624 @property
1619 def description(self):
1625 def description(self):
1620 from rhodecode.model.notification import NotificationModel
1626 from rhodecode.model.notification import NotificationModel
1621 return NotificationModel().make_description(self)
1627 return NotificationModel().make_description(self)
1622
1628
1623
1629
1624 class UserNotification(Base, BaseModel):
1630 class UserNotification(Base, BaseModel):
1625 __tablename__ = 'user_to_notification'
1631 __tablename__ = 'user_to_notification'
1626 __table_args__ = (
1632 __table_args__ = (
1627 UniqueConstraint('user_id', 'notification_id'),
1633 UniqueConstraint('user_id', 'notification_id'),
1628 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1634 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1629 'mysql_charset': 'utf8'}
1635 'mysql_charset': 'utf8'}
1630 )
1636 )
1631 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1637 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1632 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1638 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1633 read = Column('read', Boolean, default=False)
1639 read = Column('read', Boolean, default=False)
1634 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1640 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1635
1641
1636 user = relationship('User', lazy="joined")
1642 user = relationship('User', lazy="joined")
1637 notification = relationship('Notification', lazy="joined",
1643 notification = relationship('Notification', lazy="joined",
1638 order_by=lambda: Notification.created_on.desc(),)
1644 order_by=lambda: Notification.created_on.desc(),)
1639
1645
1640 def mark_as_read(self):
1646 def mark_as_read(self):
1641 self.read = True
1647 self.read = True
1642 Session().add(self)
1648 Session().add(self)
1643
1649
1644
1650
1645 class DbMigrateVersion(Base, BaseModel):
1651 class DbMigrateVersion(Base, BaseModel):
1646 __tablename__ = 'db_migrate_version'
1652 __tablename__ = 'db_migrate_version'
1647 __table_args__ = (
1653 __table_args__ = (
1648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1654 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1649 'mysql_charset': 'utf8'},
1655 'mysql_charset': 'utf8'},
1650 )
1656 )
1651 repository_id = Column('repository_id', String(250), primary_key=True)
1657 repository_id = Column('repository_id', String(250), primary_key=True)
1652 repository_path = Column('repository_path', Text)
1658 repository_path = Column('repository_path', Text)
1653 version = Column('version', Integer)
1659 version = Column('version', Integer)
@@ -1,194 +1,204 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.pull_reuquest
3 rhodecode.model.pull_request
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull request model for RhodeCode
6 pull request model for RhodeCode
7
7
8 :created_on: Jun 6, 2012
8 :created_on: Jun 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import binascii
27 import binascii
28 import datetime
28
29
29 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
30
31
31 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
32 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
33 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
34 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
35 from rhodecode.model.notification import NotificationModel
36 from rhodecode.model.notification import NotificationModel
36 from rhodecode.lib.utils2 import safe_unicode
37 from rhodecode.lib.utils2 import safe_unicode
37
38
38 from rhodecode.lib.vcs.utils.hgcompat import discovery
39 from rhodecode.lib.vcs.utils.hgcompat import discovery
39
40
40 log = logging.getLogger(__name__)
41 log = logging.getLogger(__name__)
41
42
42
43
43 class PullRequestModel(BaseModel):
44 class PullRequestModel(BaseModel):
44
45
45 cls = PullRequest
46 cls = PullRequest
46
47
48 def __get_pull_request(self, pull_request):
49 return self._get_instance(PullRequest, pull_request)
50
47 def get_all(self, repo):
51 def get_all(self, repo):
48 repo = self._get_repo(repo)
52 repo = self._get_repo(repo)
49 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
53 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
50
54
51 def create(self, created_by, org_repo, org_ref, other_repo,
55 def create(self, created_by, org_repo, org_ref, other_repo,
52 other_ref, revisions, reviewers, title, description=None):
56 other_ref, revisions, reviewers, title, description=None):
53
57
54 created_by_user = self._get_user(created_by)
58 created_by_user = self._get_user(created_by)
55 org_repo = self._get_repo(org_repo)
59 org_repo = self._get_repo(org_repo)
56 other_repo = self._get_repo(other_repo)
60 other_repo = self._get_repo(other_repo)
57
61
58 new = PullRequest()
62 new = PullRequest()
59 new.org_repo = org_repo
63 new.org_repo = org_repo
60 new.org_ref = org_ref
64 new.org_ref = org_ref
61 new.other_repo = other_repo
65 new.other_repo = other_repo
62 new.other_ref = other_ref
66 new.other_ref = other_ref
63 new.revisions = revisions
67 new.revisions = revisions
64 new.title = title
68 new.title = title
65 new.description = description
69 new.description = description
66 new.author = created_by_user
70 new.author = created_by_user
67 self.sa.add(new)
71 self.sa.add(new)
68 Session().flush()
72 Session().flush()
69 #members
73 #members
70 for member in reviewers:
74 for member in reviewers:
71 _usr = self._get_user(member)
75 _usr = self._get_user(member)
72 reviewer = PullRequestReviewers(_usr, new)
76 reviewer = PullRequestReviewers(_usr, new)
73 self.sa.add(reviewer)
77 self.sa.add(reviewer)
74
78
75 #notification to reviewers
79 #notification to reviewers
76 notif = NotificationModel()
80 notif = NotificationModel()
77
81
78 subject = safe_unicode(
82 subject = safe_unicode(
79 h.link_to(
83 h.link_to(
80 _('%(user)s wants you to review pull request #%(pr_id)s') % \
84 _('%(user)s wants you to review pull request #%(pr_id)s') % \
81 {'user': created_by_user.username,
85 {'user': created_by_user.username,
82 'pr_id': new.pull_request_id},
86 'pr_id': new.pull_request_id},
83 h.url('pullrequest_show', repo_name=other_repo.repo_name,
87 h.url('pullrequest_show', repo_name=other_repo.repo_name,
84 pull_request_id=new.pull_request_id,
88 pull_request_id=new.pull_request_id,
85 qualified=True,
89 qualified=True,
86 )
90 )
87 )
91 )
88 )
92 )
89 body = description
93 body = description
90 notif.create(created_by=created_by_user, subject=subject, body=body,
94 notif.create(created_by=created_by_user, subject=subject, body=body,
91 recipients=reviewers,
95 recipients=reviewers,
92 type_=Notification.TYPE_PULL_REQUEST,)
96 type_=Notification.TYPE_PULL_REQUEST,)
93
97
94 return new
98 return new
95
99
100 def close_pull_request(self, pull_request):
101 pull_request = self.__get_pull_request(pull_request)
102 pull_request.status = PullRequest.STATUS_CLOSED
103 pull_request.updated_on = datetime.datetime.now()
104 self.sa.add(pull_request)
105
96 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
106 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
97 discovery_data):
107 discovery_data):
98 """
108 """
99 Returns a list of changesets that are incoming from org_repo@org_ref
109 Returns a list of changesets that are incoming from org_repo@org_ref
100 to other_repo@other_ref
110 to other_repo@other_ref
101
111
102 :param org_repo:
112 :param org_repo:
103 :type org_repo:
113 :type org_repo:
104 :param org_ref:
114 :param org_ref:
105 :type org_ref:
115 :type org_ref:
106 :param other_repo:
116 :param other_repo:
107 :type other_repo:
117 :type other_repo:
108 :param other_ref:
118 :param other_ref:
109 :type other_ref:
119 :type other_ref:
110 :param tmp:
120 :param tmp:
111 :type tmp:
121 :type tmp:
112 """
122 """
113 changesets = []
123 changesets = []
114 #case two independent repos
124 #case two independent repos
115 if org_repo != other_repo:
125 if org_repo != other_repo:
116 common, incoming, rheads = discovery_data
126 common, incoming, rheads = discovery_data
117
127
118 if not incoming:
128 if not incoming:
119 revs = []
129 revs = []
120 else:
130 else:
121 revs = org_repo._repo.changelog.findmissing(common, rheads)
131 revs = org_repo._repo.changelog.findmissing(common, rheads)
122
132
123 for cs in reversed(map(binascii.hexlify, revs)):
133 for cs in reversed(map(binascii.hexlify, revs)):
124 changesets.append(org_repo.get_changeset(cs))
134 changesets.append(org_repo.get_changeset(cs))
125 else:
135 else:
126 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
136 revs = ['ancestors(%s) and not ancestors(%s)' % (org_ref[1],
127 other_ref[1])]
137 other_ref[1])]
128 from mercurial import scmutil
138 from mercurial import scmutil
129 out = scmutil.revrange(org_repo._repo, revs)
139 out = scmutil.revrange(org_repo._repo, revs)
130 for cs in reversed(out):
140 for cs in reversed(out):
131 changesets.append(org_repo.get_changeset(cs))
141 changesets.append(org_repo.get_changeset(cs))
132
142
133 return changesets
143 return changesets
134
144
135 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
145 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
136 """
146 """
137 Get's mercurial discovery data used to calculate difference between
147 Get's mercurial discovery data used to calculate difference between
138 repos and refs
148 repos and refs
139
149
140 :param org_repo:
150 :param org_repo:
141 :type org_repo:
151 :type org_repo:
142 :param org_ref:
152 :param org_ref:
143 :type org_ref:
153 :type org_ref:
144 :param other_repo:
154 :param other_repo:
145 :type other_repo:
155 :type other_repo:
146 :param other_ref:
156 :param other_ref:
147 :type other_ref:
157 :type other_ref:
148 """
158 """
149
159
150 other = org_repo._repo
160 other = org_repo._repo
151 repo = other_repo._repo
161 repo = other_repo._repo
152 tip = other[org_ref[1]]
162 tip = other[org_ref[1]]
153 log.debug('Doing discovery for %s@%s vs %s@%s' % (
163 log.debug('Doing discovery for %s@%s vs %s@%s' % (
154 org_repo, org_ref, other_repo, other_ref)
164 org_repo, org_ref, other_repo, other_ref)
155 )
165 )
156 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
166 log.debug('Filter heads are %s[%s]' % (tip, org_ref[1]))
157 tmp = discovery.findcommonincoming(
167 tmp = discovery.findcommonincoming(
158 repo=repo, # other_repo we check for incoming
168 repo=repo, # other_repo we check for incoming
159 remote=other, # org_repo source for incoming
169 remote=other, # org_repo source for incoming
160 heads=[tip.node()],
170 heads=[tip.node()],
161 force=False
171 force=False
162 )
172 )
163 return tmp
173 return tmp
164
174
165 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
175 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
166 """
176 """
167 Returns a tuple of incomming changesets, and discoverydata cache
177 Returns a tuple of incomming changesets, and discoverydata cache
168
178
169 :param org_repo:
179 :param org_repo:
170 :type org_repo:
180 :type org_repo:
171 :param org_ref:
181 :param org_ref:
172 :type org_ref:
182 :type org_ref:
173 :param other_repo:
183 :param other_repo:
174 :type other_repo:
184 :type other_repo:
175 :param other_ref:
185 :param other_ref:
176 :type other_ref:
186 :type other_ref:
177 """
187 """
178
188
179 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
189 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
180 raise Exception('org_ref must be a two element list/tuple')
190 raise Exception('org_ref must be a two element list/tuple')
181
191
182 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
192 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
183 raise Exception('other_ref must be a two element list/tuple')
193 raise Exception('other_ref must be a two element list/tuple')
184
194
185 discovery_data = self._get_discovery(org_repo.scm_instance,
195 discovery_data = self._get_discovery(org_repo.scm_instance,
186 org_ref,
196 org_ref,
187 other_repo.scm_instance,
197 other_repo.scm_instance,
188 other_ref)
198 other_ref)
189 cs_ranges = self._get_changesets(org_repo.scm_instance,
199 cs_ranges = self._get_changesets(org_repo.scm_instance,
190 org_ref,
200 org_ref,
191 other_repo.scm_instance,
201 other_repo.scm_instance,
192 other_ref,
202 other_ref,
193 discovery_data)
203 discovery_data)
194 return cs_ranges, discovery_data
204 return cs_ranges, discovery_data
@@ -1,1476 +1,1477 b''
1 /**
1 /**
2 RhodeCode JS Files
2 RhodeCode JS Files
3 **/
3 **/
4
4
5 if (typeof console == "undefined" || typeof console.log == "undefined"){
5 if (typeof console == "undefined" || typeof console.log == "undefined"){
6 console = { log: function() {} }
6 console = { log: function() {} }
7 }
7 }
8
8
9
9
10 var str_repeat = function(i, m) {
10 var str_repeat = function(i, m) {
11 for (var o = []; m > 0; o[--m] = i);
11 for (var o = []; m > 0; o[--m] = i);
12 return o.join('');
12 return o.join('');
13 };
13 };
14
14
15 /**
15 /**
16 * INJECT .format function into String
16 * INJECT .format function into String
17 * Usage: "My name is {0} {1}".format("Johny","Bravo")
17 * Usage: "My name is {0} {1}".format("Johny","Bravo")
18 * Return "My name is Johny Bravo"
18 * Return "My name is Johny Bravo"
19 * Inspired by https://gist.github.com/1049426
19 * Inspired by https://gist.github.com/1049426
20 */
20 */
21 String.prototype.format = function() {
21 String.prototype.format = function() {
22
22
23 function format() {
23 function format() {
24 var str = this;
24 var str = this;
25 var len = arguments.length+1;
25 var len = arguments.length+1;
26 var safe = undefined;
26 var safe = undefined;
27 var arg = undefined;
27 var arg = undefined;
28
28
29 // For each {0} {1} {n...} replace with the argument in that position. If
29 // For each {0} {1} {n...} replace with the argument in that position. If
30 // the argument is an object or an array it will be stringified to JSON.
30 // the argument is an object or an array it will be stringified to JSON.
31 for (var i=0; i < len; arg = arguments[i++]) {
31 for (var i=0; i < len; arg = arguments[i++]) {
32 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
32 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
33 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
33 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
34 }
34 }
35 return str;
35 return str;
36 }
36 }
37
37
38 // Save a reference of what may already exist under the property native.
38 // Save a reference of what may already exist under the property native.
39 // Allows for doing something like: if("".format.native) { /* use native */ }
39 // Allows for doing something like: if("".format.native) { /* use native */ }
40 format.native = String.prototype.format;
40 format.native = String.prototype.format;
41
41
42 // Replace the prototype property
42 // Replace the prototype property
43 return format;
43 return format;
44
44
45 }();
45 }();
46
46
47 String.prototype.strip = function(char) {
47 String.prototype.strip = function(char) {
48 if(char === undefined){
48 if(char === undefined){
49 char = '\\s';
49 char = '\\s';
50 }
50 }
51 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
51 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
52 }
52 }
53 String.prototype.lstrip = function(char) {
53 String.prototype.lstrip = function(char) {
54 if(char === undefined){
54 if(char === undefined){
55 char = '\\s';
55 char = '\\s';
56 }
56 }
57 return this.replace(new RegExp('^'+char+'+'),'');
57 return this.replace(new RegExp('^'+char+'+'),'');
58 }
58 }
59 String.prototype.rstrip = function(char) {
59 String.prototype.rstrip = function(char) {
60 if(char === undefined){
60 if(char === undefined){
61 char = '\\s';
61 char = '\\s';
62 }
62 }
63 return this.replace(new RegExp(''+char+'+$'),'');
63 return this.replace(new RegExp(''+char+'+$'),'');
64 }
64 }
65
65
66 /**
66 /**
67 * SmartColorGenerator
67 * SmartColorGenerator
68 *
68 *
69 *usage::
69 *usage::
70 * var CG = new ColorGenerator();
70 * var CG = new ColorGenerator();
71 * var col = CG.getColor(key); //returns array of RGB
71 * var col = CG.getColor(key); //returns array of RGB
72 * 'rgb({0})'.format(col.join(',')
72 * 'rgb({0})'.format(col.join(',')
73 *
73 *
74 * @returns {ColorGenerator}
74 * @returns {ColorGenerator}
75 */
75 */
76 var ColorGenerator = function(){
76 var ColorGenerator = function(){
77 this.GOLDEN_RATIO = 0.618033988749895;
77 this.GOLDEN_RATIO = 0.618033988749895;
78 this.CURRENT_RATIO = 0.22717784590367374 // this can be random
78 this.CURRENT_RATIO = 0.22717784590367374 // this can be random
79 this.HSV_1 = 0.75;//saturation
79 this.HSV_1 = 0.75;//saturation
80 this.HSV_2 = 0.95;
80 this.HSV_2 = 0.95;
81 this.color;
81 this.color;
82 this.cacheColorMap = {};
82 this.cacheColorMap = {};
83 };
83 };
84
84
85 ColorGenerator.prototype = {
85 ColorGenerator.prototype = {
86 getColor:function(key){
86 getColor:function(key){
87 if(this.cacheColorMap[key] !== undefined){
87 if(this.cacheColorMap[key] !== undefined){
88 return this.cacheColorMap[key];
88 return this.cacheColorMap[key];
89 }
89 }
90 else{
90 else{
91 this.cacheColorMap[key] = this.generateColor();
91 this.cacheColorMap[key] = this.generateColor();
92 return this.cacheColorMap[key];
92 return this.cacheColorMap[key];
93 }
93 }
94 },
94 },
95 _hsvToRgb:function(h,s,v){
95 _hsvToRgb:function(h,s,v){
96 if (s == 0.0)
96 if (s == 0.0)
97 return [v, v, v];
97 return [v, v, v];
98 i = parseInt(h * 6.0)
98 i = parseInt(h * 6.0)
99 f = (h * 6.0) - i
99 f = (h * 6.0) - i
100 p = v * (1.0 - s)
100 p = v * (1.0 - s)
101 q = v * (1.0 - s * f)
101 q = v * (1.0 - s * f)
102 t = v * (1.0 - s * (1.0 - f))
102 t = v * (1.0 - s * (1.0 - f))
103 i = i % 6
103 i = i % 6
104 if (i == 0)
104 if (i == 0)
105 return [v, t, p]
105 return [v, t, p]
106 if (i == 1)
106 if (i == 1)
107 return [q, v, p]
107 return [q, v, p]
108 if (i == 2)
108 if (i == 2)
109 return [p, v, t]
109 return [p, v, t]
110 if (i == 3)
110 if (i == 3)
111 return [p, q, v]
111 return [p, q, v]
112 if (i == 4)
112 if (i == 4)
113 return [t, p, v]
113 return [t, p, v]
114 if (i == 5)
114 if (i == 5)
115 return [v, p, q]
115 return [v, p, q]
116 },
116 },
117 generateColor:function(){
117 generateColor:function(){
118 this.CURRENT_RATIO = this.CURRENT_RATIO+this.GOLDEN_RATIO;
118 this.CURRENT_RATIO = this.CURRENT_RATIO+this.GOLDEN_RATIO;
119 this.CURRENT_RATIO = this.CURRENT_RATIO %= 1;
119 this.CURRENT_RATIO = this.CURRENT_RATIO %= 1;
120 HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
120 HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
121 RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
121 RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
122 function toRgb(v){
122 function toRgb(v){
123 return ""+parseInt(v*256)
123 return ""+parseInt(v*256)
124 }
124 }
125 return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
125 return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
126
126
127 }
127 }
128 }
128 }
129
129
130
130
131
131
132
132
133
133
134 /**
134 /**
135 * GLOBAL YUI Shortcuts
135 * GLOBAL YUI Shortcuts
136 */
136 */
137 var YUC = YAHOO.util.Connect;
137 var YUC = YAHOO.util.Connect;
138 var YUD = YAHOO.util.Dom;
138 var YUD = YAHOO.util.Dom;
139 var YUE = YAHOO.util.Event;
139 var YUE = YAHOO.util.Event;
140 var YUQ = YAHOO.util.Selector.query;
140 var YUQ = YAHOO.util.Selector.query;
141
141
142 // defines if push state is enabled for this browser ?
142 // defines if push state is enabled for this browser ?
143 var push_state_enabled = Boolean(
143 var push_state_enabled = Boolean(
144 window.history && window.history.pushState && window.history.replaceState
144 window.history && window.history.pushState && window.history.replaceState
145 && !( /* disable for versions of iOS before version 4.3 (8F190) */
145 && !( /* disable for versions of iOS before version 4.3 (8F190) */
146 (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
146 (/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
147 /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
147 /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
148 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
148 || (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
149 )
149 )
150 );
150 );
151
151
152 var _run_callbacks = function(callbacks){
152 var _run_callbacks = function(callbacks){
153 if (callbacks !== undefined){
153 if (callbacks !== undefined){
154 var _l = callbacks.length;
154 var _l = callbacks.length;
155 for (var i=0;i<_l;i++){
155 for (var i=0;i<_l;i++){
156 var func = callbacks[i];
156 var func = callbacks[i];
157 if(typeof(func)=='function'){
157 if(typeof(func)=='function'){
158 try{
158 try{
159 func();
159 func();
160 }catch (err){};
160 }catch (err){};
161 }
161 }
162 }
162 }
163 }
163 }
164 }
164 }
165
165
166 /**
166 /**
167 * Partial Ajax Implementation
167 * Partial Ajax Implementation
168 *
168 *
169 * @param url: defines url to make partial request
169 * @param url: defines url to make partial request
170 * @param container: defines id of container to input partial result
170 * @param container: defines id of container to input partial result
171 * @param s_call: success callback function that takes o as arg
171 * @param s_call: success callback function that takes o as arg
172 * o.tId
172 * o.tId
173 * o.status
173 * o.status
174 * o.statusText
174 * o.statusText
175 * o.getResponseHeader[ ]
175 * o.getResponseHeader[ ]
176 * o.getAllResponseHeaders
176 * o.getAllResponseHeaders
177 * o.responseText
177 * o.responseText
178 * o.responseXML
178 * o.responseXML
179 * o.argument
179 * o.argument
180 * @param f_call: failure callback
180 * @param f_call: failure callback
181 * @param args arguments
181 * @param args arguments
182 */
182 */
183 function ypjax(url,container,s_call,f_call,args){
183 function ypjax(url,container,s_call,f_call,args){
184 var method='GET';
184 var method='GET';
185 if(args===undefined){
185 if(args===undefined){
186 args=null;
186 args=null;
187 }
187 }
188
188
189 // Set special header for partial ajax == HTTP_X_PARTIAL_XHR
189 // Set special header for partial ajax == HTTP_X_PARTIAL_XHR
190 YUC.initHeader('X-PARTIAL-XHR',true);
190 YUC.initHeader('X-PARTIAL-XHR',true);
191
191
192 // wrapper of passed callback
192 // wrapper of passed callback
193 var s_wrapper = (function(o){
193 var s_wrapper = (function(o){
194 return function(o){
194 return function(o){
195 YUD.get(container).innerHTML=o.responseText;
195 YUD.get(container).innerHTML=o.responseText;
196 YUD.setStyle(container,'opacity','1.0');
196 YUD.setStyle(container,'opacity','1.0');
197 //execute the given original callback
197 //execute the given original callback
198 if (s_call !== undefined){
198 if (s_call !== undefined){
199 s_call(o);
199 s_call(o);
200 }
200 }
201 }
201 }
202 })()
202 })()
203 YUD.setStyle(container,'opacity','0.3');
203 YUD.setStyle(container,'opacity','0.3');
204 YUC.asyncRequest(method,url,{
204 YUC.asyncRequest(method,url,{
205 success:s_wrapper,
205 success:s_wrapper,
206 failure:function(o){
206 failure:function(o){
207 console.log(o);
207 console.log(o);
208 YUD.get(container).innerHTML='ERROR '+o.status;
208 YUD.get(container).innerHTML='ERROR '+o.status;
209 YUD.setStyle(container,'opacity','1.0');
209 YUD.setStyle(container,'opacity','1.0');
210 YUD.setStyle(container,'color','red');
210 YUD.setStyle(container,'color','red');
211 }
211 }
212 },args);
212 },args);
213
213
214 };
214 };
215
215
216 var ajaxPOST = function(url,postData,success) {
216 var ajaxPOST = function(url,postData,success) {
217 // Set special header for ajax == HTTP_X_PARTIAL_XHR
217 // Set special header for ajax == HTTP_X_PARTIAL_XHR
218 YUC.initHeader('X-PARTIAL-XHR',true);
218 YUC.initHeader('X-PARTIAL-XHR',true);
219
219
220 var toQueryString = function(o) {
220 var toQueryString = function(o) {
221 if(typeof o !== 'object') {
221 if(typeof o !== 'object') {
222 return false;
222 return false;
223 }
223 }
224 var _p, _qs = [];
224 var _p, _qs = [];
225 for(_p in o) {
225 for(_p in o) {
226 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
226 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
227 }
227 }
228 return _qs.join('&');
228 return _qs.join('&');
229 };
229 };
230
230
231 var sUrl = url;
231 var sUrl = url;
232 var callback = {
232 var callback = {
233 success: success,
233 success: success,
234 failure: function (o) {
234 failure: function (o) {
235 alert("error");
235 alert("error");
236 },
236 },
237 };
237 };
238 var postData = toQueryString(postData);
238 var postData = toQueryString(postData);
239 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
239 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
240 return request;
240 return request;
241 };
241 };
242
242
243
243
244 /**
244 /**
245 * tooltip activate
245 * tooltip activate
246 */
246 */
247 var tooltip_activate = function(){
247 var tooltip_activate = function(){
248 function toolTipsId(){
248 function toolTipsId(){
249 var ids = [];
249 var ids = [];
250 var tts = YUQ('.tooltip');
250 var tts = YUQ('.tooltip');
251 for (var i = 0; i < tts.length; i++) {
251 for (var i = 0; i < tts.length; i++) {
252 // if element doesn't not have and id
252 // if element doesn't not have and id
253 // autogenerate one for tooltip
253 // autogenerate one for tooltip
254 if (!tts[i].id){
254 if (!tts[i].id){
255 tts[i].id='tt'+((i*100)+tts.length);
255 tts[i].id='tt'+((i*100)+tts.length);
256 }
256 }
257 ids.push(tts[i].id);
257 ids.push(tts[i].id);
258 }
258 }
259 return ids
259 return ids
260 };
260 };
261 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
261 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
262 context: [[toolTipsId()],"tl","bl",null,[0,5]],
262 context: [[toolTipsId()],"tl","bl",null,[0,5]],
263 monitorresize:false,
263 monitorresize:false,
264 xyoffset :[0,0],
264 xyoffset :[0,0],
265 autodismissdelay:300000,
265 autodismissdelay:300000,
266 hidedelay:5,
266 hidedelay:5,
267 showdelay:20,
267 showdelay:20,
268 });
268 });
269 };
269 };
270
270
271 /**
271 /**
272 * show more
272 * show more
273 */
273 */
274 var show_more_event = function(){
274 var show_more_event = function(){
275 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
275 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
276 var el = e.target;
276 var el = e.target;
277 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
277 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
278 YUD.setStyle(el.parentNode,'display','none');
278 YUD.setStyle(el.parentNode,'display','none');
279 });
279 });
280 };
280 };
281
281
282
282
283 /**
283 /**
284 * Quick filter widget
284 * Quick filter widget
285 *
285 *
286 * @param target: filter input target
286 * @param target: filter input target
287 * @param nodes: list of nodes in html we want to filter.
287 * @param nodes: list of nodes in html we want to filter.
288 * @param display_element function that takes current node from nodes and
288 * @param display_element function that takes current node from nodes and
289 * does hide or show based on the node
289 * does hide or show based on the node
290 *
290 *
291 */
291 */
292 var q_filter = function(target,nodes,display_element){
292 var q_filter = function(target,nodes,display_element){
293
293
294 var nodes = nodes;
294 var nodes = nodes;
295 var q_filter_field = YUD.get(target);
295 var q_filter_field = YUD.get(target);
296 var F = YAHOO.namespace(target);
296 var F = YAHOO.namespace(target);
297
297
298 YUE.on(q_filter_field,'click',function(){
298 YUE.on(q_filter_field,'click',function(){
299 q_filter_field.value = '';
299 q_filter_field.value = '';
300 });
300 });
301
301
302 YUE.on(q_filter_field,'keyup',function(e){
302 YUE.on(q_filter_field,'keyup',function(e){
303 clearTimeout(F.filterTimeout);
303 clearTimeout(F.filterTimeout);
304 F.filterTimeout = setTimeout(F.updateFilter,600);
304 F.filterTimeout = setTimeout(F.updateFilter,600);
305 });
305 });
306
306
307 F.filterTimeout = null;
307 F.filterTimeout = null;
308
308
309 var show_node = function(node){
309 var show_node = function(node){
310 YUD.setStyle(node,'display','')
310 YUD.setStyle(node,'display','')
311 }
311 }
312 var hide_node = function(node){
312 var hide_node = function(node){
313 YUD.setStyle(node,'display','none');
313 YUD.setStyle(node,'display','none');
314 }
314 }
315
315
316 F.updateFilter = function() {
316 F.updateFilter = function() {
317 // Reset timeout
317 // Reset timeout
318 F.filterTimeout = null;
318 F.filterTimeout = null;
319
319
320 var obsolete = [];
320 var obsolete = [];
321
321
322 var req = q_filter_field.value.toLowerCase();
322 var req = q_filter_field.value.toLowerCase();
323
323
324 var l = nodes.length;
324 var l = nodes.length;
325 var i;
325 var i;
326 var showing = 0;
326 var showing = 0;
327
327
328 for (i=0;i<l;i++ ){
328 for (i=0;i<l;i++ ){
329 var n = nodes[i];
329 var n = nodes[i];
330 var target_element = display_element(n)
330 var target_element = display_element(n)
331 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
331 if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
332 hide_node(target_element);
332 hide_node(target_element);
333 }
333 }
334 else{
334 else{
335 show_node(target_element);
335 show_node(target_element);
336 showing+=1;
336 showing+=1;
337 }
337 }
338 }
338 }
339
339
340 // if repo_count is set update the number
340 // if repo_count is set update the number
341 var cnt = YUD.get('repo_count');
341 var cnt = YUD.get('repo_count');
342 if(cnt){
342 if(cnt){
343 YUD.get('repo_count').innerHTML = showing;
343 YUD.get('repo_count').innerHTML = showing;
344 }
344 }
345
345
346 }
346 }
347 };
347 };
348
348
349 var tableTr = function(cls,body){
349 var tableTr = function(cls,body){
350 var tr = document.createElement('tr');
350 var tr = document.createElement('tr');
351 YUD.addClass(tr, cls);
351 YUD.addClass(tr, cls);
352
352
353
353
354 var cont = new YAHOO.util.Element(body);
354 var cont = new YAHOO.util.Element(body);
355 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
355 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
356 tr.id = 'comment-tr-{0}'.format(comment_id);
356 tr.id = 'comment-tr-{0}'.format(comment_id);
357 tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
357 tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
358 '<td class="lineno-inline old-inline"></td>'+
358 '<td class="lineno-inline old-inline"></td>'+
359 '<td>{0}</td>'.format(body);
359 '<td>{0}</td>'.format(body);
360 return tr;
360 return tr;
361 };
361 };
362
362
363 /** comments **/
363 /** comments **/
364 var removeInlineForm = function(form) {
364 var removeInlineForm = function(form) {
365 form.parentNode.removeChild(form);
365 form.parentNode.removeChild(form);
366 };
366 };
367
367
368 var createInlineForm = function(parent_tr, f_path, line) {
368 var createInlineForm = function(parent_tr, f_path, line) {
369 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
369 var tmpl = YUD.get('comment-inline-form-template').innerHTML;
370 tmpl = tmpl.format(f_path, line);
370 tmpl = tmpl.format(f_path, line);
371 var form = tableTr('comment-form-inline',tmpl)
371 var form = tableTr('comment-form-inline',tmpl)
372
372
373 // create event for hide button
373 // create event for hide button
374 form = new YAHOO.util.Element(form);
374 form = new YAHOO.util.Element(form);
375 var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
375 var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
376 form_hide_button.on('click', function(e) {
376 form_hide_button.on('click', function(e) {
377 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
377 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
378 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
378 if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
379 YUD.setStyle(newtr.nextElementSibling,'display','');
379 YUD.setStyle(newtr.nextElementSibling,'display','');
380 }
380 }
381 removeInlineForm(newtr);
381 removeInlineForm(newtr);
382 YUD.removeClass(parent_tr, 'form-open');
382 YUD.removeClass(parent_tr, 'form-open');
383
383
384 });
384 });
385
385
386 return form
386 return form
387 };
387 };
388
388
389 /**
389 /**
390 * Inject inline comment for on given TR this tr should be always an .line
390 * Inject inline comment for on given TR this tr should be always an .line
391 * tr containing the line. Code will detect comment, and always put the comment
391 * tr containing the line. Code will detect comment, and always put the comment
392 * block at the very bottom
392 * block at the very bottom
393 */
393 */
394 var injectInlineForm = function(tr){
394 var injectInlineForm = function(tr){
395 if(!YUD.hasClass(tr, 'line')){
395 if(!YUD.hasClass(tr, 'line')){
396 return
396 return
397 }
397 }
398 var submit_url = AJAX_COMMENT_URL;
398 var submit_url = AJAX_COMMENT_URL;
399 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){
399 var _td = tr.getElementsByClassName('code')[0];
400 if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){
400 return
401 return
401 }
402 }
402 YUD.addClass(tr,'form-open');
403 YUD.addClass(tr,'form-open');
403 var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
404 var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
404 var f_path = YUD.getAttribute(node,'path');
405 var f_path = YUD.getAttribute(node,'path');
405 var lineno = getLineNo(tr);
406 var lineno = getLineNo(tr);
406 var form = createInlineForm(tr, f_path, lineno, submit_url);
407 var form = createInlineForm(tr, f_path, lineno, submit_url);
407
408
408 var parent = tr;
409 var parent = tr;
409 while (1){
410 while (1){
410 var n = parent.nextElementSibling;
411 var n = parent.nextElementSibling;
411 // next element are comments !
412 // next element are comments !
412 if(YUD.hasClass(n,'inline-comments')){
413 if(YUD.hasClass(n,'inline-comments')){
413 parent = n;
414 parent = n;
414 }
415 }
415 else{
416 else{
416 break;
417 break;
417 }
418 }
418 }
419 }
419 YUD.insertAfter(form,parent);
420 YUD.insertAfter(form,parent);
420
421
421 var f = YUD.get(form);
422 var f = YUD.get(form);
422
423
423 var overlay = f.getElementsByClassName('overlay')[0];
424 var overlay = f.getElementsByClassName('overlay')[0];
424 var _form = f.getElementsByClassName('inline-form')[0];
425 var _form = f.getElementsByClassName('inline-form')[0];
425
426
426 form.on('submit',function(e){
427 form.on('submit',function(e){
427 YUE.preventDefault(e);
428 YUE.preventDefault(e);
428
429
429 //ajax submit
430 //ajax submit
430 var text = YUD.get('text_'+lineno).value;
431 var text = YUD.get('text_'+lineno).value;
431 var postData = {
432 var postData = {
432 'text':text,
433 'text':text,
433 'f_path':f_path,
434 'f_path':f_path,
434 'line':lineno
435 'line':lineno
435 };
436 };
436
437
437 if(lineno === undefined){
438 if(lineno === undefined){
438 alert('missing line !');
439 alert('missing line !');
439 return
440 return
440 }
441 }
441 if(f_path === undefined){
442 if(f_path === undefined){
442 alert('missing file path !');
443 alert('missing file path !');
443 return
444 return
444 }
445 }
445
446
446 if(text == ""){
447 if(text == ""){
447 return
448 return
448 }
449 }
449
450
450 var success = function(o){
451 var success = function(o){
451 YUD.removeClass(tr, 'form-open');
452 YUD.removeClass(tr, 'form-open');
452 removeInlineForm(f);
453 removeInlineForm(f);
453 var json_data = JSON.parse(o.responseText);
454 var json_data = JSON.parse(o.responseText);
454 renderInlineComment(json_data);
455 renderInlineComment(json_data);
455 };
456 };
456
457
457 if (YUD.hasClass(overlay,'overlay')){
458 if (YUD.hasClass(overlay,'overlay')){
458 var w = _form.offsetWidth;
459 var w = _form.offsetWidth;
459 var h = _form.offsetHeight;
460 var h = _form.offsetHeight;
460 YUD.setStyle(overlay,'width',w+'px');
461 YUD.setStyle(overlay,'width',w+'px');
461 YUD.setStyle(overlay,'height',h+'px');
462 YUD.setStyle(overlay,'height',h+'px');
462 }
463 }
463 YUD.addClass(overlay, 'submitting');
464 YUD.addClass(overlay, 'submitting');
464
465
465 ajaxPOST(submit_url, postData, success);
466 ajaxPOST(submit_url, postData, success);
466 });
467 });
467
468
468 setTimeout(function(){
469 setTimeout(function(){
469 // callbacks
470 // callbacks
470 tooltip_activate();
471 tooltip_activate();
471 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
472 MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno,
472 _USERS_AC_DATA, _GROUPS_AC_DATA);
473 _USERS_AC_DATA, _GROUPS_AC_DATA);
473 YUD.get('text_'+lineno).focus();
474 YUD.get('text_'+lineno).focus();
474 },10)
475 },10)
475 };
476 };
476
477
477 var deleteComment = function(comment_id){
478 var deleteComment = function(comment_id){
478 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
479 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
479 var postData = {'_method':'delete'};
480 var postData = {'_method':'delete'};
480 var success = function(o){
481 var success = function(o){
481 var n = YUD.get('comment-tr-'+comment_id);
482 var n = YUD.get('comment-tr-'+comment_id);
482 var root = n.previousElementSibling.previousElementSibling;
483 var root = n.previousElementSibling.previousElementSibling;
483 n.parentNode.removeChild(n);
484 n.parentNode.removeChild(n);
484
485
485 // scann nodes, and attach add button to last one
486 // scann nodes, and attach add button to last one
486 placeAddButton(root);
487 placeAddButton(root);
487 }
488 }
488 ajaxPOST(url,postData,success);
489 ajaxPOST(url,postData,success);
489 }
490 }
490
491
491
492
492 var createInlineAddButton = function(tr){
493 var createInlineAddButton = function(tr){
493
494
494 var label = TRANSLATION_MAP['add another comment'];
495 var label = TRANSLATION_MAP['add another comment'];
495
496
496 var html_el = document.createElement('div');
497 var html_el = document.createElement('div');
497 YUD.addClass(html_el, 'add-comment');
498 YUD.addClass(html_el, 'add-comment');
498 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
499 html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
499
500
500 var add = new YAHOO.util.Element(html_el);
501 var add = new YAHOO.util.Element(html_el);
501 add.on('click', function(e) {
502 add.on('click', function(e) {
502 injectInlineForm(tr);
503 injectInlineForm(tr);
503 });
504 });
504 return add;
505 return add;
505 };
506 };
506
507
507 var getLineNo = function(tr) {
508 var getLineNo = function(tr) {
508 var line;
509 var line;
509 var o = tr.children[0].id.split('_');
510 var o = tr.children[0].id.split('_');
510 var n = tr.children[1].id.split('_');
511 var n = tr.children[1].id.split('_');
511
512
512 if (n.length >= 2) {
513 if (n.length >= 2) {
513 line = n[n.length-1];
514 line = n[n.length-1];
514 } else if (o.length >= 2) {
515 } else if (o.length >= 2) {
515 line = o[o.length-1];
516 line = o[o.length-1];
516 }
517 }
517
518
518 return line
519 return line
519 };
520 };
520
521
521 var placeAddButton = function(target_tr){
522 var placeAddButton = function(target_tr){
522 if(!target_tr){
523 if(!target_tr){
523 return
524 return
524 }
525 }
525 var last_node = target_tr;
526 var last_node = target_tr;
526 //scann
527 //scann
527 while (1){
528 while (1){
528 var n = last_node.nextElementSibling;
529 var n = last_node.nextElementSibling;
529 // next element are comments !
530 // next element are comments !
530 if(YUD.hasClass(n,'inline-comments')){
531 if(YUD.hasClass(n,'inline-comments')){
531 last_node = n;
532 last_node = n;
532 //also remove the comment button from previos
533 //also remove the comment button from previos
533 var comment_add_buttons = last_node.getElementsByClassName('add-comment');
534 var comment_add_buttons = last_node.getElementsByClassName('add-comment');
534 for(var i=0;i<comment_add_buttons.length;i++){
535 for(var i=0;i<comment_add_buttons.length;i++){
535 var b = comment_add_buttons[i];
536 var b = comment_add_buttons[i];
536 b.parentNode.removeChild(b);
537 b.parentNode.removeChild(b);
537 }
538 }
538 }
539 }
539 else{
540 else{
540 break;
541 break;
541 }
542 }
542 }
543 }
543
544
544 var add = createInlineAddButton(target_tr);
545 var add = createInlineAddButton(target_tr);
545 // get the comment div
546 // get the comment div
546 var comment_block = last_node.getElementsByClassName('comment')[0];
547 var comment_block = last_node.getElementsByClassName('comment')[0];
547 // attach add button
548 // attach add button
548 YUD.insertAfter(add,comment_block);
549 YUD.insertAfter(add,comment_block);
549 }
550 }
550
551
551 /**
552 /**
552 * Places the inline comment into the changeset block in proper line position
553 * Places the inline comment into the changeset block in proper line position
553 */
554 */
554 var placeInline = function(target_container,lineno,html){
555 var placeInline = function(target_container,lineno,html){
555 var lineid = "{0}_{1}".format(target_container,lineno);
556 var lineid = "{0}_{1}".format(target_container,lineno);
556 var target_line = YUD.get(lineid);
557 var target_line = YUD.get(lineid);
557 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
558 var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
558
559
559 // check if there are comments already !
560 // check if there are comments already !
560 var parent = target_line.parentNode;
561 var parent = target_line.parentNode;
561 var root_parent = parent;
562 var root_parent = parent;
562 while (1){
563 while (1){
563 var n = parent.nextElementSibling;
564 var n = parent.nextElementSibling;
564 // next element are comments !
565 // next element are comments !
565 if(YUD.hasClass(n,'inline-comments')){
566 if(YUD.hasClass(n,'inline-comments')){
566 parent = n;
567 parent = n;
567 }
568 }
568 else{
569 else{
569 break;
570 break;
570 }
571 }
571 }
572 }
572 // put in the comment at the bottom
573 // put in the comment at the bottom
573 YUD.insertAfter(comment,parent);
574 YUD.insertAfter(comment,parent);
574
575
575 // scann nodes, and attach add button to last one
576 // scann nodes, and attach add button to last one
576 placeAddButton(root_parent);
577 placeAddButton(root_parent);
577
578
578 return target_line;
579 return target_line;
579 }
580 }
580
581
581 /**
582 /**
582 * make a single inline comment and place it inside
583 * make a single inline comment and place it inside
583 */
584 */
584 var renderInlineComment = function(json_data){
585 var renderInlineComment = function(json_data){
585 try{
586 try{
586 var html = json_data['rendered_text'];
587 var html = json_data['rendered_text'];
587 var lineno = json_data['line_no'];
588 var lineno = json_data['line_no'];
588 var target_id = json_data['target_id'];
589 var target_id = json_data['target_id'];
589 placeInline(target_id, lineno, html);
590 placeInline(target_id, lineno, html);
590
591
591 }catch(e){
592 }catch(e){
592 console.log(e);
593 console.log(e);
593 }
594 }
594 }
595 }
595
596
596 /**
597 /**
597 * Iterates over all the inlines, and places them inside proper blocks of data
598 * Iterates over all the inlines, and places them inside proper blocks of data
598 */
599 */
599 var renderInlineComments = function(file_comments){
600 var renderInlineComments = function(file_comments){
600 for (f in file_comments){
601 for (f in file_comments){
601 // holding all comments for a FILE
602 // holding all comments for a FILE
602 var box = file_comments[f];
603 var box = file_comments[f];
603
604
604 var target_id = YUD.getAttribute(box,'target_id');
605 var target_id = YUD.getAttribute(box,'target_id');
605 // actually comments with line numbers
606 // actually comments with line numbers
606 var comments = box.children;
607 var comments = box.children;
607 for(var i=0; i<comments.length; i++){
608 for(var i=0; i<comments.length; i++){
608 var data = {
609 var data = {
609 'rendered_text': comments[i].outerHTML,
610 'rendered_text': comments[i].outerHTML,
610 'line_no': YUD.getAttribute(comments[i],'line'),
611 'line_no': YUD.getAttribute(comments[i],'line'),
611 'target_id': target_id
612 'target_id': target_id
612 }
613 }
613 renderInlineComment(data);
614 renderInlineComment(data);
614 }
615 }
615 }
616 }
616 }
617 }
617
618
618
619
619 var fileBrowserListeners = function(current_url, node_list_url, url_base){
620 var fileBrowserListeners = function(current_url, node_list_url, url_base){
620
621
621 var current_url_branch = +"?branch=__BRANCH__";
622 var current_url_branch = +"?branch=__BRANCH__";
622 var url = url_base;
623 var url = url_base;
623 var node_url = node_list_url;
624 var node_url = node_list_url;
624
625
625 YUE.on('stay_at_branch','click',function(e){
626 YUE.on('stay_at_branch','click',function(e){
626 if(e.target.checked){
627 if(e.target.checked){
627 var uri = current_url_branch;
628 var uri = current_url_branch;
628 uri = uri.replace('__BRANCH__',e.target.value);
629 uri = uri.replace('__BRANCH__',e.target.value);
629 window.location = uri;
630 window.location = uri;
630 }
631 }
631 else{
632 else{
632 window.location = current_url;
633 window.location = current_url;
633 }
634 }
634 })
635 })
635
636
636 var n_filter = YUD.get('node_filter');
637 var n_filter = YUD.get('node_filter');
637 var F = YAHOO.namespace('node_filter');
638 var F = YAHOO.namespace('node_filter');
638
639
639 F.filterTimeout = null;
640 F.filterTimeout = null;
640 var nodes = null;
641 var nodes = null;
641
642
642 F.initFilter = function(){
643 F.initFilter = function(){
643 YUD.setStyle('node_filter_box_loading','display','');
644 YUD.setStyle('node_filter_box_loading','display','');
644 YUD.setStyle('search_activate_id','display','none');
645 YUD.setStyle('search_activate_id','display','none');
645 YUD.setStyle('add_node_id','display','none');
646 YUD.setStyle('add_node_id','display','none');
646 YUC.initHeader('X-PARTIAL-XHR',true);
647 YUC.initHeader('X-PARTIAL-XHR',true);
647 YUC.asyncRequest('GET',url,{
648 YUC.asyncRequest('GET',url,{
648 success:function(o){
649 success:function(o){
649 nodes = JSON.parse(o.responseText).nodes;
650 nodes = JSON.parse(o.responseText).nodes;
650 YUD.setStyle('node_filter_box_loading','display','none');
651 YUD.setStyle('node_filter_box_loading','display','none');
651 YUD.setStyle('node_filter_box','display','');
652 YUD.setStyle('node_filter_box','display','');
652 n_filter.focus();
653 n_filter.focus();
653 if(YUD.hasClass(n_filter,'init')){
654 if(YUD.hasClass(n_filter,'init')){
654 n_filter.value = '';
655 n_filter.value = '';
655 YUD.removeClass(n_filter,'init');
656 YUD.removeClass(n_filter,'init');
656 }
657 }
657 },
658 },
658 failure:function(o){
659 failure:function(o){
659 console.log('failed to load');
660 console.log('failed to load');
660 }
661 }
661 },null);
662 },null);
662 }
663 }
663
664
664 F.updateFilter = function(e) {
665 F.updateFilter = function(e) {
665
666
666 return function(){
667 return function(){
667 // Reset timeout
668 // Reset timeout
668 F.filterTimeout = null;
669 F.filterTimeout = null;
669 var query = e.target.value.toLowerCase();
670 var query = e.target.value.toLowerCase();
670 var match = [];
671 var match = [];
671 var matches = 0;
672 var matches = 0;
672 var matches_max = 20;
673 var matches_max = 20;
673 if (query != ""){
674 if (query != ""){
674 for(var i=0;i<nodes.length;i++){
675 for(var i=0;i<nodes.length;i++){
675
676
676 var pos = nodes[i].name.toLowerCase().indexOf(query)
677 var pos = nodes[i].name.toLowerCase().indexOf(query)
677 if(query && pos != -1){
678 if(query && pos != -1){
678
679
679 matches++
680 matches++
680 //show only certain amount to not kill browser
681 //show only certain amount to not kill browser
681 if (matches > matches_max){
682 if (matches > matches_max){
682 break;
683 break;
683 }
684 }
684
685
685 var n = nodes[i].name;
686 var n = nodes[i].name;
686 var t = nodes[i].type;
687 var t = nodes[i].type;
687 var n_hl = n.substring(0,pos)
688 var n_hl = n.substring(0,pos)
688 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
689 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
689 +n.substring(pos+query.length)
690 +n.substring(pos+query.length)
690 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url.replace('__FPATH__',n),n_hl));
691 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url.replace('__FPATH__',n),n_hl));
691 }
692 }
692 if(match.length >= matches_max){
693 if(match.length >= matches_max){
693 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['search truncated']));
694 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['search truncated']));
694 }
695 }
695 }
696 }
696 }
697 }
697 if(query != ""){
698 if(query != ""){
698 YUD.setStyle('tbody','display','none');
699 YUD.setStyle('tbody','display','none');
699 YUD.setStyle('tbody_filtered','display','');
700 YUD.setStyle('tbody_filtered','display','');
700
701
701 if (match.length==0){
702 if (match.length==0){
702 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['no matching files']));
703 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['no matching files']));
703 }
704 }
704
705
705 YUD.get('tbody_filtered').innerHTML = match.join("");
706 YUD.get('tbody_filtered').innerHTML = match.join("");
706 }
707 }
707 else{
708 else{
708 YUD.setStyle('tbody','display','');
709 YUD.setStyle('tbody','display','');
709 YUD.setStyle('tbody_filtered','display','none');
710 YUD.setStyle('tbody_filtered','display','none');
710 }
711 }
711
712
712 }
713 }
713 };
714 };
714
715
715 YUE.on(YUD.get('filter_activate'),'click',function(){
716 YUE.on(YUD.get('filter_activate'),'click',function(){
716 F.initFilter();
717 F.initFilter();
717 })
718 })
718 YUE.on(n_filter,'click',function(){
719 YUE.on(n_filter,'click',function(){
719 if(YUD.hasClass(n_filter,'init')){
720 if(YUD.hasClass(n_filter,'init')){
720 n_filter.value = '';
721 n_filter.value = '';
721 YUD.removeClass(n_filter,'init');
722 YUD.removeClass(n_filter,'init');
722 }
723 }
723 });
724 });
724 YUE.on(n_filter,'keyup',function(e){
725 YUE.on(n_filter,'keyup',function(e){
725 clearTimeout(F.filterTimeout);
726 clearTimeout(F.filterTimeout);
726 F.filterTimeout = setTimeout(F.updateFilter(e),600);
727 F.filterTimeout = setTimeout(F.updateFilter(e),600);
727 });
728 });
728 };
729 };
729
730
730
731
731 var initCodeMirror = function(textAreadId,resetUrl){
732 var initCodeMirror = function(textAreadId,resetUrl){
732 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
733 var myCodeMirror = CodeMirror.fromTextArea(YUD.get(textAreadId),{
733 mode: "null",
734 mode: "null",
734 lineNumbers:true
735 lineNumbers:true
735 });
736 });
736 YUE.on('reset','click',function(e){
737 YUE.on('reset','click',function(e){
737 window.location=resetUrl
738 window.location=resetUrl
738 });
739 });
739
740
740 YUE.on('file_enable','click',function(){
741 YUE.on('file_enable','click',function(){
741 YUD.setStyle('editor_container','display','');
742 YUD.setStyle('editor_container','display','');
742 YUD.setStyle('upload_file_container','display','none');
743 YUD.setStyle('upload_file_container','display','none');
743 YUD.setStyle('filename_container','display','');
744 YUD.setStyle('filename_container','display','');
744 });
745 });
745
746
746 YUE.on('upload_file_enable','click',function(){
747 YUE.on('upload_file_enable','click',function(){
747 YUD.setStyle('editor_container','display','none');
748 YUD.setStyle('editor_container','display','none');
748 YUD.setStyle('upload_file_container','display','');
749 YUD.setStyle('upload_file_container','display','');
749 YUD.setStyle('filename_container','display','none');
750 YUD.setStyle('filename_container','display','none');
750 });
751 });
751 };
752 };
752
753
753
754
754
755
755 var getIdentNode = function(n){
756 var getIdentNode = function(n){
756 //iterate thru nodes untill matched interesting node !
757 //iterate thru nodes untill matched interesting node !
757
758
758 if (typeof n == 'undefined'){
759 if (typeof n == 'undefined'){
759 return -1
760 return -1
760 }
761 }
761
762
762 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
763 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
763 return n
764 return n
764 }
765 }
765 else{
766 else{
766 return getIdentNode(n.parentNode);
767 return getIdentNode(n.parentNode);
767 }
768 }
768 };
769 };
769
770
770 var getSelectionLink = function(selection_link_label) {
771 var getSelectionLink = function(selection_link_label) {
771 return function(){
772 return function(){
772 //get selection from start/to nodes
773 //get selection from start/to nodes
773 if (typeof window.getSelection != "undefined") {
774 if (typeof window.getSelection != "undefined") {
774 s = window.getSelection();
775 s = window.getSelection();
775
776
776 from = getIdentNode(s.anchorNode);
777 from = getIdentNode(s.anchorNode);
777 till = getIdentNode(s.focusNode);
778 till = getIdentNode(s.focusNode);
778
779
779 f_int = parseInt(from.id.replace('L',''));
780 f_int = parseInt(from.id.replace('L',''));
780 t_int = parseInt(till.id.replace('L',''));
781 t_int = parseInt(till.id.replace('L',''));
781
782
782 if (f_int > t_int){
783 if (f_int > t_int){
783 //highlight from bottom
784 //highlight from bottom
784 offset = -35;
785 offset = -35;
785 ranges = [t_int,f_int];
786 ranges = [t_int,f_int];
786
787
787 }
788 }
788 else{
789 else{
789 //highligth from top
790 //highligth from top
790 offset = 35;
791 offset = 35;
791 ranges = [f_int,t_int];
792 ranges = [f_int,t_int];
792 }
793 }
793
794
794 if (ranges[0] != ranges[1]){
795 if (ranges[0] != ranges[1]){
795 if(YUD.get('linktt') == null){
796 if(YUD.get('linktt') == null){
796 hl_div = document.createElement('div');
797 hl_div = document.createElement('div');
797 hl_div.id = 'linktt';
798 hl_div.id = 'linktt';
798 }
799 }
799 anchor = '#L'+ranges[0]+'-'+ranges[1];
800 anchor = '#L'+ranges[0]+'-'+ranges[1];
800 hl_div.innerHTML = '';
801 hl_div.innerHTML = '';
801 l = document.createElement('a');
802 l = document.createElement('a');
802 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
803 l.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
803 l.innerHTML = selection_link_label;
804 l.innerHTML = selection_link_label;
804 hl_div.appendChild(l);
805 hl_div.appendChild(l);
805
806
806 YUD.get('body').appendChild(hl_div);
807 YUD.get('body').appendChild(hl_div);
807
808
808 xy = YUD.getXY(till.id);
809 xy = YUD.getXY(till.id);
809
810
810 YUD.addClass('linktt','yui-tt');
811 YUD.addClass('linktt','yui-tt');
811 YUD.setStyle('linktt','top',xy[1]+offset+'px');
812 YUD.setStyle('linktt','top',xy[1]+offset+'px');
812 YUD.setStyle('linktt','left',xy[0]+'px');
813 YUD.setStyle('linktt','left',xy[0]+'px');
813 YUD.setStyle('linktt','visibility','visible');
814 YUD.setStyle('linktt','visibility','visible');
814 }
815 }
815 else{
816 else{
816 YUD.setStyle('linktt','visibility','hidden');
817 YUD.setStyle('linktt','visibility','hidden');
817 }
818 }
818 }
819 }
819 }
820 }
820 };
821 };
821
822
822 var deleteNotification = function(url, notification_id,callbacks){
823 var deleteNotification = function(url, notification_id,callbacks){
823 var callback = {
824 var callback = {
824 success:function(o){
825 success:function(o){
825 var obj = YUD.get(String("notification_"+notification_id));
826 var obj = YUD.get(String("notification_"+notification_id));
826 if(obj.parentNode !== undefined){
827 if(obj.parentNode !== undefined){
827 obj.parentNode.removeChild(obj);
828 obj.parentNode.removeChild(obj);
828 }
829 }
829 _run_callbacks(callbacks);
830 _run_callbacks(callbacks);
830 },
831 },
831 failure:function(o){
832 failure:function(o){
832 alert("error");
833 alert("error");
833 },
834 },
834 };
835 };
835 var postData = '_method=delete';
836 var postData = '_method=delete';
836 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
837 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
837 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
838 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl,
838 callback, postData);
839 callback, postData);
839 };
840 };
840
841
841
842
842 /** MEMBERS AUTOCOMPLETE WIDGET **/
843 /** MEMBERS AUTOCOMPLETE WIDGET **/
843
844
844 var MembersAutoComplete = function (users_list, groups_list) {
845 var MembersAutoComplete = function (users_list, groups_list) {
845 var myUsers = users_list;
846 var myUsers = users_list;
846 var myGroups = groups_list;
847 var myGroups = groups_list;
847
848
848 // Define a custom search function for the DataSource of users
849 // Define a custom search function for the DataSource of users
849 var matchUsers = function (sQuery) {
850 var matchUsers = function (sQuery) {
850 // Case insensitive matching
851 // Case insensitive matching
851 var query = sQuery.toLowerCase();
852 var query = sQuery.toLowerCase();
852 var i = 0;
853 var i = 0;
853 var l = myUsers.length;
854 var l = myUsers.length;
854 var matches = [];
855 var matches = [];
855
856
856 // Match against each name of each contact
857 // Match against each name of each contact
857 for (; i < l; i++) {
858 for (; i < l; i++) {
858 contact = myUsers[i];
859 contact = myUsers[i];
859 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
860 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
860 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
861 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
861 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
862 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
862 matches[matches.length] = contact;
863 matches[matches.length] = contact;
863 }
864 }
864 }
865 }
865 return matches;
866 return matches;
866 };
867 };
867
868
868 // Define a custom search function for the DataSource of usersGroups
869 // Define a custom search function for the DataSource of usersGroups
869 var matchGroups = function (sQuery) {
870 var matchGroups = function (sQuery) {
870 // Case insensitive matching
871 // Case insensitive matching
871 var query = sQuery.toLowerCase();
872 var query = sQuery.toLowerCase();
872 var i = 0;
873 var i = 0;
873 var l = myGroups.length;
874 var l = myGroups.length;
874 var matches = [];
875 var matches = [];
875
876
876 // Match against each name of each contact
877 // Match against each name of each contact
877 for (; i < l; i++) {
878 for (; i < l; i++) {
878 matched_group = myGroups[i];
879 matched_group = myGroups[i];
879 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
880 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
880 matches[matches.length] = matched_group;
881 matches[matches.length] = matched_group;
881 }
882 }
882 }
883 }
883 return matches;
884 return matches;
884 };
885 };
885
886
886 //match all
887 //match all
887 var matchAll = function (sQuery) {
888 var matchAll = function (sQuery) {
888 u = matchUsers(sQuery);
889 u = matchUsers(sQuery);
889 g = matchGroups(sQuery);
890 g = matchGroups(sQuery);
890 return u.concat(g);
891 return u.concat(g);
891 };
892 };
892
893
893 // DataScheme for members
894 // DataScheme for members
894 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
895 var memberDS = new YAHOO.util.FunctionDataSource(matchAll);
895 memberDS.responseSchema = {
896 memberDS.responseSchema = {
896 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
897 fields: ["id", "fname", "lname", "nname", "grname", "grmembers", "gravatar_lnk"]
897 };
898 };
898
899
899 // DataScheme for owner
900 // DataScheme for owner
900 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
901 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
901 ownerDS.responseSchema = {
902 ownerDS.responseSchema = {
902 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
903 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
903 };
904 };
904
905
905 // Instantiate AutoComplete for perms
906 // Instantiate AutoComplete for perms
906 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
907 var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS);
907 membersAC.useShadow = false;
908 membersAC.useShadow = false;
908 membersAC.resultTypeList = false;
909 membersAC.resultTypeList = false;
909
910
910 // Instantiate AutoComplete for owner
911 // Instantiate AutoComplete for owner
911 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
912 var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS);
912 ownerAC.useShadow = false;
913 ownerAC.useShadow = false;
913 ownerAC.resultTypeList = false;
914 ownerAC.resultTypeList = false;
914
915
915
916
916 // Helper highlight function for the formatter
917 // Helper highlight function for the formatter
917 var highlightMatch = function (full, snippet, matchindex) {
918 var highlightMatch = function (full, snippet, matchindex) {
918 return full.substring(0, matchindex)
919 return full.substring(0, matchindex)
919 + "<span class='match'>"
920 + "<span class='match'>"
920 + full.substr(matchindex, snippet.length)
921 + full.substr(matchindex, snippet.length)
921 + "</span>" + full.substring(matchindex + snippet.length);
922 + "</span>" + full.substring(matchindex + snippet.length);
922 };
923 };
923
924
924 // Custom formatter to highlight the matching letters
925 // Custom formatter to highlight the matching letters
925 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
926 var custom_formatter = function (oResultData, sQuery, sResultMatch) {
926 var query = sQuery.toLowerCase();
927 var query = sQuery.toLowerCase();
927 var _gravatar = function(res, em, group){
928 var _gravatar = function(res, em, group){
928 if (group !== undefined){
929 if (group !== undefined){
929 em = '/images/icons/group.png'
930 em = '/images/icons/group.png'
930 }
931 }
931 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
932 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
932 return tmpl.format(em,res)
933 return tmpl.format(em,res)
933 }
934 }
934 // group
935 // group
935 if (oResultData.grname != undefined) {
936 if (oResultData.grname != undefined) {
936 var grname = oResultData.grname;
937 var grname = oResultData.grname;
937 var grmembers = oResultData.grmembers;
938 var grmembers = oResultData.grmembers;
938 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
939 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
939 var grprefix = "{0}: ".format(_TM['Group']);
940 var grprefix = "{0}: ".format(_TM['Group']);
940 var grsuffix = " (" + grmembers + " )";
941 var grsuffix = " (" + grmembers + " )";
941 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
942 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
942
943
943 if (grnameMatchIndex > -1) {
944 if (grnameMatchIndex > -1) {
944 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
945 return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true);
945 }
946 }
946 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
947 return _gravatar(grprefix + oResultData.grname + grsuffix, null,true);
947 // Users
948 // Users
948 } else if (oResultData.nname != undefined) {
949 } else if (oResultData.nname != undefined) {
949 var fname = oResultData.fname || "";
950 var fname = oResultData.fname || "";
950 var lname = oResultData.lname || "";
951 var lname = oResultData.lname || "";
951 var nname = oResultData.nname;
952 var nname = oResultData.nname;
952
953
953 // Guard against null value
954 // Guard against null value
954 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
955 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
955 lnameMatchIndex = lname.toLowerCase().indexOf(query),
956 lnameMatchIndex = lname.toLowerCase().indexOf(query),
956 nnameMatchIndex = nname.toLowerCase().indexOf(query),
957 nnameMatchIndex = nname.toLowerCase().indexOf(query),
957 displayfname, displaylname, displaynname;
958 displayfname, displaylname, displaynname;
958
959
959 if (fnameMatchIndex > -1) {
960 if (fnameMatchIndex > -1) {
960 displayfname = highlightMatch(fname, query, fnameMatchIndex);
961 displayfname = highlightMatch(fname, query, fnameMatchIndex);
961 } else {
962 } else {
962 displayfname = fname;
963 displayfname = fname;
963 }
964 }
964
965
965 if (lnameMatchIndex > -1) {
966 if (lnameMatchIndex > -1) {
966 displaylname = highlightMatch(lname, query, lnameMatchIndex);
967 displaylname = highlightMatch(lname, query, lnameMatchIndex);
967 } else {
968 } else {
968 displaylname = lname;
969 displaylname = lname;
969 }
970 }
970
971
971 if (nnameMatchIndex > -1) {
972 if (nnameMatchIndex > -1) {
972 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
973 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
973 } else {
974 } else {
974 displaynname = nname ? "(" + nname + ")" : "";
975 displaynname = nname ? "(" + nname + ")" : "";
975 }
976 }
976
977
977 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
978 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
978 } else {
979 } else {
979 return '';
980 return '';
980 }
981 }
981 };
982 };
982 membersAC.formatResult = custom_formatter;
983 membersAC.formatResult = custom_formatter;
983 ownerAC.formatResult = custom_formatter;
984 ownerAC.formatResult = custom_formatter;
984
985
985 var myHandler = function (sType, aArgs) {
986 var myHandler = function (sType, aArgs) {
986
987
987 var myAC = aArgs[0]; // reference back to the AC instance
988 var myAC = aArgs[0]; // reference back to the AC instance
988 var elLI = aArgs[1]; // reference to the selected LI element
989 var elLI = aArgs[1]; // reference to the selected LI element
989 var oData = aArgs[2]; // object literal of selected item's result data
990 var oData = aArgs[2]; // object literal of selected item's result data
990 //fill the autocomplete with value
991 //fill the autocomplete with value
991 if (oData.nname != undefined) {
992 if (oData.nname != undefined) {
992 //users
993 //users
993 myAC.getInputEl().value = oData.nname;
994 myAC.getInputEl().value = oData.nname;
994 YUD.get('perm_new_member_type').value = 'user';
995 YUD.get('perm_new_member_type').value = 'user';
995 } else {
996 } else {
996 //groups
997 //groups
997 myAC.getInputEl().value = oData.grname;
998 myAC.getInputEl().value = oData.grname;
998 YUD.get('perm_new_member_type').value = 'users_group';
999 YUD.get('perm_new_member_type').value = 'users_group';
999 }
1000 }
1000 };
1001 };
1001
1002
1002 membersAC.itemSelectEvent.subscribe(myHandler);
1003 membersAC.itemSelectEvent.subscribe(myHandler);
1003 if(ownerAC.itemSelectEvent){
1004 if(ownerAC.itemSelectEvent){
1004 ownerAC.itemSelectEvent.subscribe(myHandler);
1005 ownerAC.itemSelectEvent.subscribe(myHandler);
1005 }
1006 }
1006
1007
1007 return {
1008 return {
1008 memberDS: memberDS,
1009 memberDS: memberDS,
1009 ownerDS: ownerDS,
1010 ownerDS: ownerDS,
1010 membersAC: membersAC,
1011 membersAC: membersAC,
1011 ownerAC: ownerAC,
1012 ownerAC: ownerAC,
1012 };
1013 };
1013 }
1014 }
1014
1015
1015
1016
1016 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1017 var MentionsAutoComplete = function (divid, cont, users_list, groups_list) {
1017 var myUsers = users_list;
1018 var myUsers = users_list;
1018 var myGroups = groups_list;
1019 var myGroups = groups_list;
1019
1020
1020 // Define a custom search function for the DataSource of users
1021 // Define a custom search function for the DataSource of users
1021 var matchUsers = function (sQuery) {
1022 var matchUsers = function (sQuery) {
1022 var org_sQuery = sQuery;
1023 var org_sQuery = sQuery;
1023 if(this.mentionQuery == null){
1024 if(this.mentionQuery == null){
1024 return []
1025 return []
1025 }
1026 }
1026 sQuery = this.mentionQuery;
1027 sQuery = this.mentionQuery;
1027 // Case insensitive matching
1028 // Case insensitive matching
1028 var query = sQuery.toLowerCase();
1029 var query = sQuery.toLowerCase();
1029 var i = 0;
1030 var i = 0;
1030 var l = myUsers.length;
1031 var l = myUsers.length;
1031 var matches = [];
1032 var matches = [];
1032
1033
1033 // Match against each name of each contact
1034 // Match against each name of each contact
1034 for (; i < l; i++) {
1035 for (; i < l; i++) {
1035 contact = myUsers[i];
1036 contact = myUsers[i];
1036 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1037 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1037 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1038 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1038 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1039 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1039 matches[matches.length] = contact;
1040 matches[matches.length] = contact;
1040 }
1041 }
1041 }
1042 }
1042 return matches
1043 return matches
1043 };
1044 };
1044
1045
1045 //match all
1046 //match all
1046 var matchAll = function (sQuery) {
1047 var matchAll = function (sQuery) {
1047 u = matchUsers(sQuery);
1048 u = matchUsers(sQuery);
1048 return u
1049 return u
1049 };
1050 };
1050
1051
1051 // DataScheme for owner
1052 // DataScheme for owner
1052 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1053 var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers);
1053
1054
1054 ownerDS.responseSchema = {
1055 ownerDS.responseSchema = {
1055 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1056 fields: ["id", "fname", "lname", "nname", "gravatar_lnk"]
1056 };
1057 };
1057
1058
1058 // Instantiate AutoComplete for mentions
1059 // Instantiate AutoComplete for mentions
1059 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1060 var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS);
1060 ownerAC.useShadow = false;
1061 ownerAC.useShadow = false;
1061 ownerAC.resultTypeList = false;
1062 ownerAC.resultTypeList = false;
1062 ownerAC.suppressInputUpdate = true;
1063 ownerAC.suppressInputUpdate = true;
1063
1064
1064 // Helper highlight function for the formatter
1065 // Helper highlight function for the formatter
1065 var highlightMatch = function (full, snippet, matchindex) {
1066 var highlightMatch = function (full, snippet, matchindex) {
1066 return full.substring(0, matchindex)
1067 return full.substring(0, matchindex)
1067 + "<span class='match'>"
1068 + "<span class='match'>"
1068 + full.substr(matchindex, snippet.length)
1069 + full.substr(matchindex, snippet.length)
1069 + "</span>" + full.substring(matchindex + snippet.length);
1070 + "</span>" + full.substring(matchindex + snippet.length);
1070 };
1071 };
1071
1072
1072 // Custom formatter to highlight the matching letters
1073 // Custom formatter to highlight the matching letters
1073 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1074 ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1074 var org_sQuery = sQuery;
1075 var org_sQuery = sQuery;
1075 if(this.dataSource.mentionQuery != null){
1076 if(this.dataSource.mentionQuery != null){
1076 sQuery = this.dataSource.mentionQuery;
1077 sQuery = this.dataSource.mentionQuery;
1077 }
1078 }
1078
1079
1079 var query = sQuery.toLowerCase();
1080 var query = sQuery.toLowerCase();
1080 var _gravatar = function(res, em, group){
1081 var _gravatar = function(res, em, group){
1081 if (group !== undefined){
1082 if (group !== undefined){
1082 em = '/images/icons/group.png'
1083 em = '/images/icons/group.png'
1083 }
1084 }
1084 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1085 tmpl = '<div class="ac-container-wrap"><img class="perm-gravatar-ac" src="{0}"/>{1}</div>'
1085 return tmpl.format(em,res)
1086 return tmpl.format(em,res)
1086 }
1087 }
1087 if (oResultData.nname != undefined) {
1088 if (oResultData.nname != undefined) {
1088 var fname = oResultData.fname || "";
1089 var fname = oResultData.fname || "";
1089 var lname = oResultData.lname || "";
1090 var lname = oResultData.lname || "";
1090 var nname = oResultData.nname;
1091 var nname = oResultData.nname;
1091
1092
1092 // Guard against null value
1093 // Guard against null value
1093 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1094 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1094 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1095 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1095 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1096 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1096 displayfname, displaylname, displaynname;
1097 displayfname, displaylname, displaynname;
1097
1098
1098 if (fnameMatchIndex > -1) {
1099 if (fnameMatchIndex > -1) {
1099 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1100 displayfname = highlightMatch(fname, query, fnameMatchIndex);
1100 } else {
1101 } else {
1101 displayfname = fname;
1102 displayfname = fname;
1102 }
1103 }
1103
1104
1104 if (lnameMatchIndex > -1) {
1105 if (lnameMatchIndex > -1) {
1105 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1106 displaylname = highlightMatch(lname, query, lnameMatchIndex);
1106 } else {
1107 } else {
1107 displaylname = lname;
1108 displaylname = lname;
1108 }
1109 }
1109
1110
1110 if (nnameMatchIndex > -1) {
1111 if (nnameMatchIndex > -1) {
1111 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1112 displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")";
1112 } else {
1113 } else {
1113 displaynname = nname ? "(" + nname + ")" : "";
1114 displaynname = nname ? "(" + nname + ")" : "";
1114 }
1115 }
1115
1116
1116 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1117 return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk);
1117 } else {
1118 } else {
1118 return '';
1119 return '';
1119 }
1120 }
1120 };
1121 };
1121
1122
1122 if(ownerAC.itemSelectEvent){
1123 if(ownerAC.itemSelectEvent){
1123 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1124 ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1124
1125
1125 var myAC = aArgs[0]; // reference back to the AC instance
1126 var myAC = aArgs[0]; // reference back to the AC instance
1126 var elLI = aArgs[1]; // reference to the selected LI element
1127 var elLI = aArgs[1]; // reference to the selected LI element
1127 var oData = aArgs[2]; // object literal of selected item's result data
1128 var oData = aArgs[2]; // object literal of selected item's result data
1128 //fill the autocomplete with value
1129 //fill the autocomplete with value
1129 if (oData.nname != undefined) {
1130 if (oData.nname != undefined) {
1130 //users
1131 //users
1131 //Replace the mention name with replaced
1132 //Replace the mention name with replaced
1132 var re = new RegExp();
1133 var re = new RegExp();
1133 var org = myAC.getInputEl().value;
1134 var org = myAC.getInputEl().value;
1134 var chunks = myAC.dataSource.chunks
1135 var chunks = myAC.dataSource.chunks
1135 // replace middle chunk(the search term) with actuall match
1136 // replace middle chunk(the search term) with actuall match
1136 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1137 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1137 '@'+oData.nname+' ');
1138 '@'+oData.nname+' ');
1138 myAC.getInputEl().value = chunks.join('')
1139 myAC.getInputEl().value = chunks.join('')
1139 YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
1140 YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !?
1140 } else {
1141 } else {
1141 //groups
1142 //groups
1142 myAC.getInputEl().value = oData.grname;
1143 myAC.getInputEl().value = oData.grname;
1143 YUD.get('perm_new_member_type').value = 'users_group';
1144 YUD.get('perm_new_member_type').value = 'users_group';
1144 }
1145 }
1145 });
1146 });
1146 }
1147 }
1147
1148
1148 // in this keybuffer we will gather current value of search !
1149 // in this keybuffer we will gather current value of search !
1149 // since we need to get this just when someone does `@` then we do the
1150 // since we need to get this just when someone does `@` then we do the
1150 // search
1151 // search
1151 ownerAC.dataSource.chunks = [];
1152 ownerAC.dataSource.chunks = [];
1152 ownerAC.dataSource.mentionQuery = null;
1153 ownerAC.dataSource.mentionQuery = null;
1153
1154
1154 ownerAC.get_mention = function(msg, max_pos) {
1155 ownerAC.get_mention = function(msg, max_pos) {
1155 var org = msg;
1156 var org = msg;
1156 var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
1157 var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$')
1157 var chunks = [];
1158 var chunks = [];
1158
1159
1159
1160
1160 // cut first chunk until curret pos
1161 // cut first chunk until curret pos
1161 var to_max = msg.substr(0, max_pos);
1162 var to_max = msg.substr(0, max_pos);
1162 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1163 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1163 var msg2 = to_max.substr(at_pos);
1164 var msg2 = to_max.substr(at_pos);
1164
1165
1165 chunks.push(org.substr(0,at_pos))// prefix chunk
1166 chunks.push(org.substr(0,at_pos))// prefix chunk
1166 chunks.push(msg2) // search chunk
1167 chunks.push(msg2) // search chunk
1167 chunks.push(org.substr(max_pos)) // postfix chunk
1168 chunks.push(org.substr(max_pos)) // postfix chunk
1168
1169
1169 // clean up msg2 for filtering and regex match
1170 // clean up msg2 for filtering and regex match
1170 var msg2 = msg2.lstrip(' ').lstrip('\n');
1171 var msg2 = msg2.lstrip(' ').lstrip('\n');
1171
1172
1172 if(re.test(msg2)){
1173 if(re.test(msg2)){
1173 var unam = re.exec(msg2)[1];
1174 var unam = re.exec(msg2)[1];
1174 return [unam, chunks];
1175 return [unam, chunks];
1175 }
1176 }
1176 return [null, null];
1177 return [null, null];
1177 };
1178 };
1178 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1179 ownerAC.textboxKeyUpEvent.subscribe(function(type, args){
1179
1180
1180 var ac_obj = args[0];
1181 var ac_obj = args[0];
1181 var currentMessage = args[1];
1182 var currentMessage = args[1];
1182 var currentCaretPosition = args[0]._elTextbox.selectionStart;
1183 var currentCaretPosition = args[0]._elTextbox.selectionStart;
1183
1184
1184 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1185 var unam = ownerAC.get_mention(currentMessage, currentCaretPosition);
1185 var curr_search = null;
1186 var curr_search = null;
1186 if(unam[0]){
1187 if(unam[0]){
1187 curr_search = unam[0];
1188 curr_search = unam[0];
1188 }
1189 }
1189
1190
1190 ownerAC.dataSource.chunks = unam[1];
1191 ownerAC.dataSource.chunks = unam[1];
1191 ownerAC.dataSource.mentionQuery = curr_search;
1192 ownerAC.dataSource.mentionQuery = curr_search;
1192
1193
1193 })
1194 })
1194
1195
1195 return {
1196 return {
1196 ownerDS: ownerDS,
1197 ownerDS: ownerDS,
1197 ownerAC: ownerAC,
1198 ownerAC: ownerAC,
1198 };
1199 };
1199 }
1200 }
1200
1201
1201
1202
1202 /**
1203 /**
1203 * QUICK REPO MENU
1204 * QUICK REPO MENU
1204 */
1205 */
1205 var quick_repo_menu = function(){
1206 var quick_repo_menu = function(){
1206 YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
1207 YUE.on(YUQ('.quick_repo_menu'),'mouseenter',function(e){
1207 var menu = e.currentTarget.firstElementChild.firstElementChild;
1208 var menu = e.currentTarget.firstElementChild.firstElementChild;
1208 if(YUD.hasClass(menu,'hidden')){
1209 if(YUD.hasClass(menu,'hidden')){
1209 YUD.replaceClass(e.currentTarget,'hidden', 'active');
1210 YUD.replaceClass(e.currentTarget,'hidden', 'active');
1210 YUD.replaceClass(menu, 'hidden', 'active');
1211 YUD.replaceClass(menu, 'hidden', 'active');
1211 }
1212 }
1212 })
1213 })
1213 YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
1214 YUE.on(YUQ('.quick_repo_menu'),'mouseleave',function(e){
1214 var menu = e.currentTarget.firstElementChild.firstElementChild;
1215 var menu = e.currentTarget.firstElementChild.firstElementChild;
1215 if(YUD.hasClass(menu,'active')){
1216 if(YUD.hasClass(menu,'active')){
1216 YUD.replaceClass(e.currentTarget, 'active', 'hidden');
1217 YUD.replaceClass(e.currentTarget, 'active', 'hidden');
1217 YUD.replaceClass(menu, 'active', 'hidden');
1218 YUD.replaceClass(menu, 'active', 'hidden');
1218 }
1219 }
1219 })
1220 })
1220 };
1221 };
1221
1222
1222
1223
1223 /**
1224 /**
1224 * TABLE SORTING
1225 * TABLE SORTING
1225 */
1226 */
1226
1227
1227 // returns a node from given html;
1228 // returns a node from given html;
1228 var fromHTML = function(html){
1229 var fromHTML = function(html){
1229 var _html = document.createElement('element');
1230 var _html = document.createElement('element');
1230 _html.innerHTML = html;
1231 _html.innerHTML = html;
1231 return _html;
1232 return _html;
1232 }
1233 }
1233 var get_rev = function(node){
1234 var get_rev = function(node){
1234 var n = node.firstElementChild.firstElementChild;
1235 var n = node.firstElementChild.firstElementChild;
1235
1236
1236 if (n===null){
1237 if (n===null){
1237 return -1
1238 return -1
1238 }
1239 }
1239 else{
1240 else{
1240 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
1241 out = n.firstElementChild.innerHTML.split(':')[0].replace('r','');
1241 return parseInt(out);
1242 return parseInt(out);
1242 }
1243 }
1243 }
1244 }
1244
1245
1245 var get_name = function(node){
1246 var get_name = function(node){
1246 var name = node.firstElementChild.children[2].innerHTML;
1247 var name = node.firstElementChild.children[2].innerHTML;
1247 return name
1248 return name
1248 }
1249 }
1249 var get_group_name = function(node){
1250 var get_group_name = function(node){
1250 var name = node.firstElementChild.children[1].innerHTML;
1251 var name = node.firstElementChild.children[1].innerHTML;
1251 return name
1252 return name
1252 }
1253 }
1253 var get_date = function(node){
1254 var get_date = function(node){
1254 var date_ = YUD.getAttribute(node.firstElementChild,'date');
1255 var date_ = YUD.getAttribute(node.firstElementChild,'date');
1255 return date_
1256 return date_
1256 }
1257 }
1257
1258
1258 var get_age = function(node){
1259 var get_age = function(node){
1259 console.log(node);
1260 console.log(node);
1260 return node
1261 return node
1261 }
1262 }
1262
1263
1263 var revisionSort = function(a, b, desc, field) {
1264 var revisionSort = function(a, b, desc, field) {
1264
1265
1265 var a_ = fromHTML(a.getData(field));
1266 var a_ = fromHTML(a.getData(field));
1266 var b_ = fromHTML(b.getData(field));
1267 var b_ = fromHTML(b.getData(field));
1267
1268
1268 // extract revisions from string nodes
1269 // extract revisions from string nodes
1269 a_ = get_rev(a_)
1270 a_ = get_rev(a_)
1270 b_ = get_rev(b_)
1271 b_ = get_rev(b_)
1271
1272
1272 var comp = YAHOO.util.Sort.compare;
1273 var comp = YAHOO.util.Sort.compare;
1273 var compState = comp(a_, b_, desc);
1274 var compState = comp(a_, b_, desc);
1274 return compState;
1275 return compState;
1275 };
1276 };
1276 var ageSort = function(a, b, desc, field) {
1277 var ageSort = function(a, b, desc, field) {
1277 var a_ = fromHTML(a.getData(field));
1278 var a_ = fromHTML(a.getData(field));
1278 var b_ = fromHTML(b.getData(field));
1279 var b_ = fromHTML(b.getData(field));
1279
1280
1280 // extract name from table
1281 // extract name from table
1281 a_ = get_date(a_)
1282 a_ = get_date(a_)
1282 b_ = get_date(b_)
1283 b_ = get_date(b_)
1283
1284
1284 var comp = YAHOO.util.Sort.compare;
1285 var comp = YAHOO.util.Sort.compare;
1285 var compState = comp(a_, b_, desc);
1286 var compState = comp(a_, b_, desc);
1286 return compState;
1287 return compState;
1287 };
1288 };
1288
1289
1289 var nameSort = function(a, b, desc, field) {
1290 var nameSort = function(a, b, desc, field) {
1290 var a_ = fromHTML(a.getData(field));
1291 var a_ = fromHTML(a.getData(field));
1291 var b_ = fromHTML(b.getData(field));
1292 var b_ = fromHTML(b.getData(field));
1292
1293
1293 // extract name from table
1294 // extract name from table
1294 a_ = get_name(a_)
1295 a_ = get_name(a_)
1295 b_ = get_name(b_)
1296 b_ = get_name(b_)
1296
1297
1297 var comp = YAHOO.util.Sort.compare;
1298 var comp = YAHOO.util.Sort.compare;
1298 var compState = comp(a_, b_, desc);
1299 var compState = comp(a_, b_, desc);
1299 return compState;
1300 return compState;
1300 };
1301 };
1301
1302
1302 var permNameSort = function(a, b, desc, field) {
1303 var permNameSort = function(a, b, desc, field) {
1303 var a_ = fromHTML(a.getData(field));
1304 var a_ = fromHTML(a.getData(field));
1304 var b_ = fromHTML(b.getData(field));
1305 var b_ = fromHTML(b.getData(field));
1305 // extract name from table
1306 // extract name from table
1306
1307
1307 a_ = a_.children[0].innerHTML;
1308 a_ = a_.children[0].innerHTML;
1308 b_ = b_.children[0].innerHTML;
1309 b_ = b_.children[0].innerHTML;
1309
1310
1310 var comp = YAHOO.util.Sort.compare;
1311 var comp = YAHOO.util.Sort.compare;
1311 var compState = comp(a_, b_, desc);
1312 var compState = comp(a_, b_, desc);
1312 return compState;
1313 return compState;
1313 };
1314 };
1314
1315
1315 var groupNameSort = function(a, b, desc, field) {
1316 var groupNameSort = function(a, b, desc, field) {
1316 var a_ = fromHTML(a.getData(field));
1317 var a_ = fromHTML(a.getData(field));
1317 var b_ = fromHTML(b.getData(field));
1318 var b_ = fromHTML(b.getData(field));
1318
1319
1319 // extract name from table
1320 // extract name from table
1320 a_ = get_group_name(a_)
1321 a_ = get_group_name(a_)
1321 b_ = get_group_name(b_)
1322 b_ = get_group_name(b_)
1322
1323
1323 var comp = YAHOO.util.Sort.compare;
1324 var comp = YAHOO.util.Sort.compare;
1324 var compState = comp(a_, b_, desc);
1325 var compState = comp(a_, b_, desc);
1325 return compState;
1326 return compState;
1326 };
1327 };
1327 var dateSort = function(a, b, desc, field) {
1328 var dateSort = function(a, b, desc, field) {
1328 var a_ = fromHTML(a.getData(field));
1329 var a_ = fromHTML(a.getData(field));
1329 var b_ = fromHTML(b.getData(field));
1330 var b_ = fromHTML(b.getData(field));
1330
1331
1331 // extract name from table
1332 // extract name from table
1332 a_ = get_date(a_)
1333 a_ = get_date(a_)
1333 b_ = get_date(b_)
1334 b_ = get_date(b_)
1334
1335
1335 var comp = YAHOO.util.Sort.compare;
1336 var comp = YAHOO.util.Sort.compare;
1336 var compState = comp(a_, b_, desc);
1337 var compState = comp(a_, b_, desc);
1337 return compState;
1338 return compState;
1338 };
1339 };
1339
1340
1340
1341
1341
1342
1342 /* Multi selectors */
1343 /* Multi selectors */
1343
1344
1344 var MultiSelectWidget = function(selected_id, available_id, form_id){
1345 var MultiSelectWidget = function(selected_id, available_id, form_id){
1345
1346
1346
1347
1347 //definition of containers ID's
1348 //definition of containers ID's
1348 var selected_container = selected_id;
1349 var selected_container = selected_id;
1349 var available_container = available_id;
1350 var available_container = available_id;
1350
1351
1351 //temp container for selected storage.
1352 //temp container for selected storage.
1352 var cache = new Array();
1353 var cache = new Array();
1353 var av_cache = new Array();
1354 var av_cache = new Array();
1354 var c = YUD.get(selected_container);
1355 var c = YUD.get(selected_container);
1355 var ac = YUD.get(available_container);
1356 var ac = YUD.get(available_container);
1356
1357
1357 //get only selected options for further fullfilment
1358 //get only selected options for further fullfilment
1358 for(var i = 0;node =c.options[i];i++){
1359 for(var i = 0;node =c.options[i];i++){
1359 if(node.selected){
1360 if(node.selected){
1360 //push selected to my temp storage left overs :)
1361 //push selected to my temp storage left overs :)
1361 cache.push(node);
1362 cache.push(node);
1362 }
1363 }
1363 }
1364 }
1364
1365
1365 //get all available options to cache
1366 //get all available options to cache
1366 for(var i = 0;node =ac.options[i];i++){
1367 for(var i = 0;node =ac.options[i];i++){
1367 //push selected to my temp storage left overs :)
1368 //push selected to my temp storage left overs :)
1368 av_cache.push(node);
1369 av_cache.push(node);
1369 }
1370 }
1370
1371
1371 //fill available only with those not in choosen
1372 //fill available only with those not in choosen
1372 ac.options.length=0;
1373 ac.options.length=0;
1373 tmp_cache = new Array();
1374 tmp_cache = new Array();
1374
1375
1375 for(var i = 0;node = av_cache[i];i++){
1376 for(var i = 0;node = av_cache[i];i++){
1376 var add = true;
1377 var add = true;
1377 for(var i2 = 0;node_2 = cache[i2];i2++){
1378 for(var i2 = 0;node_2 = cache[i2];i2++){
1378 if(node.value == node_2.value){
1379 if(node.value == node_2.value){
1379 add=false;
1380 add=false;
1380 break;
1381 break;
1381 }
1382 }
1382 }
1383 }
1383 if(add){
1384 if(add){
1384 tmp_cache.push(new Option(node.text, node.value, false, false));
1385 tmp_cache.push(new Option(node.text, node.value, false, false));
1385 }
1386 }
1386 }
1387 }
1387
1388
1388 for(var i = 0;node = tmp_cache[i];i++){
1389 for(var i = 0;node = tmp_cache[i];i++){
1389 ac.options[i] = node;
1390 ac.options[i] = node;
1390 }
1391 }
1391
1392
1392 function prompts_action_callback(e){
1393 function prompts_action_callback(e){
1393
1394
1394 var choosen = YUD.get(selected_container);
1395 var choosen = YUD.get(selected_container);
1395 var available = YUD.get(available_container);
1396 var available = YUD.get(available_container);
1396
1397
1397 //get checked and unchecked options from field
1398 //get checked and unchecked options from field
1398 function get_checked(from_field){
1399 function get_checked(from_field){
1399 //temp container for storage.
1400 //temp container for storage.
1400 var sel_cache = new Array();
1401 var sel_cache = new Array();
1401 var oth_cache = new Array();
1402 var oth_cache = new Array();
1402
1403
1403 for(var i = 0;node = from_field.options[i];i++){
1404 for(var i = 0;node = from_field.options[i];i++){
1404 if(node.selected){
1405 if(node.selected){
1405 //push selected fields :)
1406 //push selected fields :)
1406 sel_cache.push(node);
1407 sel_cache.push(node);
1407 }
1408 }
1408 else{
1409 else{
1409 oth_cache.push(node)
1410 oth_cache.push(node)
1410 }
1411 }
1411 }
1412 }
1412
1413
1413 return [sel_cache,oth_cache]
1414 return [sel_cache,oth_cache]
1414 }
1415 }
1415
1416
1416 //fill the field with given options
1417 //fill the field with given options
1417 function fill_with(field,options){
1418 function fill_with(field,options){
1418 //clear firtst
1419 //clear firtst
1419 field.options.length=0;
1420 field.options.length=0;
1420 for(var i = 0;node = options[i];i++){
1421 for(var i = 0;node = options[i];i++){
1421 field.options[i]=new Option(node.text, node.value,
1422 field.options[i]=new Option(node.text, node.value,
1422 false, false);
1423 false, false);
1423 }
1424 }
1424
1425
1425 }
1426 }
1426 //adds to current field
1427 //adds to current field
1427 function add_to(field,options){
1428 function add_to(field,options){
1428 for(var i = 0;node = options[i];i++){
1429 for(var i = 0;node = options[i];i++){
1429 field.appendChild(new Option(node.text, node.value,
1430 field.appendChild(new Option(node.text, node.value,
1430 false, false));
1431 false, false));
1431 }
1432 }
1432 }
1433 }
1433
1434
1434 // add action
1435 // add action
1435 if (this.id=='add_element'){
1436 if (this.id=='add_element'){
1436 var c = get_checked(available);
1437 var c = get_checked(available);
1437 add_to(choosen,c[0]);
1438 add_to(choosen,c[0]);
1438 fill_with(available,c[1]);
1439 fill_with(available,c[1]);
1439 }
1440 }
1440 // remove action
1441 // remove action
1441 if (this.id=='remove_element'){
1442 if (this.id=='remove_element'){
1442 var c = get_checked(choosen);
1443 var c = get_checked(choosen);
1443 add_to(available,c[0]);
1444 add_to(available,c[0]);
1444 fill_with(choosen,c[1]);
1445 fill_with(choosen,c[1]);
1445 }
1446 }
1446 // add all elements
1447 // add all elements
1447 if(this.id=='add_all_elements'){
1448 if(this.id=='add_all_elements'){
1448 for(var i=0; node = available.options[i];i++){
1449 for(var i=0; node = available.options[i];i++){
1449 choosen.appendChild(new Option(node.text,
1450 choosen.appendChild(new Option(node.text,
1450 node.value, false, false));
1451 node.value, false, false));
1451 }
1452 }
1452 available.options.length = 0;
1453 available.options.length = 0;
1453 }
1454 }
1454 //remove all elements
1455 //remove all elements
1455 if(this.id=='remove_all_elements'){
1456 if(this.id=='remove_all_elements'){
1456 for(var i=0; node = choosen.options[i];i++){
1457 for(var i=0; node = choosen.options[i];i++){
1457 available.appendChild(new Option(node.text,
1458 available.appendChild(new Option(node.text,
1458 node.value, false, false));
1459 node.value, false, false));
1459 }
1460 }
1460 choosen.options.length = 0;
1461 choosen.options.length = 0;
1461 }
1462 }
1462
1463
1463 }
1464 }
1464
1465
1465 YUE.addListener(['add_element','remove_element',
1466 YUE.addListener(['add_element','remove_element',
1466 'add_all_elements','remove_all_elements'],'click',
1467 'add_all_elements','remove_all_elements'],'click',
1467 prompts_action_callback)
1468 prompts_action_callback)
1468 if (form_id !== undefined) {
1469 if (form_id !== undefined) {
1469 YUE.addListener(form_id,'submit',function(){
1470 YUE.addListener(form_id,'submit',function(){
1470 var choosen = YUD.get(selected_container);
1471 var choosen = YUD.get(selected_container);
1471 for (var i = 0; i < choosen.options.length; i++) {
1472 for (var i = 0; i < choosen.options.length; i++) {
1472 choosen.options[i].selected = 'selected';
1473 choosen.options[i].selected = 'selected';
1473 }
1474 }
1474 });
1475 });
1475 }
1476 }
1476 } No newline at end of file
1477 }
@@ -1,176 +1,179 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_(u'Home'),h.url('/'))}
10 ${h.link_to(_(u'Home'),h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
15 </%def>
15 </%def>
16
16
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('changelog')}
18 ${self.menu('changelog')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <div class="table">
27 <div class="table">
28 <div class="diffblock">
28 <div class="diffblock">
29 <div class="code-header">
29 <div class="code-header">
30 <div class="hash">
30 <div class="hash">
31 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
31 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
32 </div>
32 </div>
33 <div class="date">
33 <div class="date">
34 ${h.fmt_date(c.changeset.date)}
34 ${h.fmt_date(c.changeset.date)}
35 </div>
35 </div>
36 <div class="changeset-status-container">
36 <div class="changeset-status-container">
37 %if c.statuses:
37 %if c.statuses:
38 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
38 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
39 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
39 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
40 %endif
40 %endif
41 </div>
41 </div>
42 <div class="diff-actions">
42 <div class="diff-actions">
43 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
43 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
44 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
44 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
45 ${c.ignorews_url(request.GET)}
45 ${c.ignorews_url(request.GET)}
46 ${c.context_url(request.GET)}
46 ${c.context_url(request.GET)}
47 </div>
47 </div>
48 <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
48 <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
49 </div>
49 </div>
50 </div>
50 </div>
51 <div id="changeset_content">
51 <div id="changeset_content">
52 <div class="container">
52 <div class="container">
53 <div class="left">
53 <div class="left">
54 <div class="author">
54 <div class="author">
55 <div class="gravatar">
55 <div class="gravatar">
56 <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
56 <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
57 </div>
57 </div>
58 <span>${h.person(c.changeset.author)}</span><br/>
58 <span>${h.person(c.changeset.author)}</span><br/>
59 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
59 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
60 </div>
60 </div>
61 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
61 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
62 </div>
62 </div>
63 <div class="right">
63 <div class="right">
64 <div class="changes">
64 <div class="changes">
65 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
65 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
66 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
66 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
67 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
67 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
68 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
68 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
69 % else:
69 % else:
70 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
70 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
71 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
71 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
72 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
72 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
73 % endif
73 % endif
74 </div>
74 </div>
75
75
76 %if c.changeset.parents:
76 %if c.changeset.parents:
77 %for p_cs in reversed(c.changeset.parents):
77 %for p_cs in reversed(c.changeset.parents):
78 <div class="parent">${_('Parent')}
78 <div class="parent">${_('Parent')}
79 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
79 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
80 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
80 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
81 </div>
81 </div>
82 %endfor
82 %endfor
83 %else:
83 %else:
84 <div class="parent">${_('No parents')}</div>
84 <div class="parent">${_('No parents')}</div>
85 %endif
85 %endif
86 <span class="logtags">
86 <span class="logtags">
87 %if len(c.changeset.parents)>1:
87 %if len(c.changeset.parents)>1:
88 <span class="merge">${_('merge')}</span>
88 <span class="merge">${_('merge')}</span>
89 %endif
89 %endif
90 %if c.changeset.branch:
90 %if c.changeset.branch:
91 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
91 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
92 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
92 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
93 </span>
93 </span>
94 %endif
94 %endif
95 %for tag in c.changeset.tags:
95 %for tag in c.changeset.tags:
96 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
96 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
97 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
97 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
98 %endfor
98 %endfor
99 </span>
99 </span>
100 </div>
100 </div>
101 </div>
101 </div>
102 <span>
102 <span>
103 ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
103 ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
104 </span>
104 </span>
105 <div class="cs_files">
105 <div class="cs_files">
106 %for change,filenode,diff,cs1,cs2,stat in c.changes:
106 %for change,filenode,diff,cs1,cs2,stat in c.changes:
107 <div class="cs_${change}">
107 <div class="cs_${change}">
108 <div class="node">
108 <div class="node">
109 %if change != 'removed':
109 %if change != 'removed':
110 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")}
110 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")}
111 %else:
111 %else:
112 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
112 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
113 %endif
113 %endif
114 </div>
114 </div>
115 <div class="changes">${h.fancy_file_stats(stat)}</div>
115 <div class="changes">${h.fancy_file_stats(stat)}</div>
116 </div>
116 </div>
117 %endfor
117 %endfor
118 % if c.cut_off:
118 % if c.cut_off:
119 ${_('Changeset was too big and was cut off...')}
119 ${_('Changeset was too big and was cut off...')}
120 % endif
120 % endif
121 </div>
121 </div>
122 </div>
122 </div>
123
123
124 </div>
124 </div>
125 <script>
125 <script>
126 var _USERS_AC_DATA = ${c.users_array|n};
126 var _USERS_AC_DATA = ${c.users_array|n};
127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
128 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
128 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
129 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
129 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
130 </script>
130 </script>
131 ## diff block
131 ## diff block
132 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
132 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
133 ${diff_block.diff_block(c.changes)}
133 ${diff_block.diff_block(c.changes)}
134
134
135 ## template for inline comment form
135 ## template for inline comment form
136 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
136 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
137 ${comment.comment_inline_form()}
137 ${comment.comment_inline_form()}
138
138
139 ## render comments main comments form and it status
139 ## render comments and inlines
140 ${comment.generate_comments()}
141
142 ## main comment form and it status
140 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
143 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
141 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
144 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
142
145
143 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
146 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
144 <script type="text/javascript">
147 <script type="text/javascript">
145 YUE.onDOMReady(function(){
148 YUE.onDOMReady(function(){
146 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
149 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
147 var show = 'none';
150 var show = 'none';
148 var target = e.currentTarget;
151 var target = e.currentTarget;
149 if(target.checked){
152 if(target.checked){
150 var show = ''
153 var show = ''
151 }
154 }
152 var boxid = YUD.getAttribute(target,'id_for');
155 var boxid = YUD.getAttribute(target,'id_for');
153 var comments = YUQ('#{0} .inline-comments'.format(boxid));
156 var comments = YUQ('#{0} .inline-comments'.format(boxid));
154 for(c in comments){
157 for(c in comments){
155 YUD.setStyle(comments[c],'display',show);
158 YUD.setStyle(comments[c],'display',show);
156 }
159 }
157 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
160 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
158 for(c in btns){
161 for(c in btns){
159 YUD.setStyle(btns[c],'display',show);
162 YUD.setStyle(btns[c],'display',show);
160 }
163 }
161 })
164 })
162
165
163 YUE.on(YUQ('.line'),'click',function(e){
166 YUE.on(YUQ('.line'),'click',function(e){
164 var tr = e.currentTarget;
167 var tr = e.currentTarget;
165 injectInlineForm(tr);
168 injectInlineForm(tr);
166 });
169 });
167
170
168 // inject comments into they proper positions
171 // inject comments into they proper positions
169 var file_comments = YUQ('.inline-comment-placeholder');
172 var file_comments = YUQ('.inline-comment-placeholder');
170 renderInlineComments(file_comments);
173 renderInlineComments(file_comments);
171 })
174 })
172
175
173 </script>
176 </script>
174
177
175 </div>
178 </div>
176 </%def>
179 </%def>
@@ -1,154 +1,163 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
4 ## ${comment.comment_block(co)}
4 ## ${comment.comment_block(co)}
5 ##
5 ##
6 <%def name="comment_block(co)">
6 <%def name="comment_block(co)">
7 <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
7 <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
8 <div class="comment-wrapp">
8 <div class="comment-wrapp">
9 <div class="meta">
9 <div class="meta">
10 <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div>
10 <div style="float:left"> <img src="${h.gravatar_url(co.author.email, 20)}" /> </div>
11 <div class="user">
11 <div class="user">
12 ${co.author.username}
12 ${co.author.username}
13 </div>
13 </div>
14 <div class="date">
14 <div class="date">
15 ${h.age(co.modified_at)}
15 ${h.age(co.modified_at)}
16 </div>
16 </div>
17 %if co.status_change:
17 %if co.status_change:
18 <div style="float:left" class="changeset-status-container">
18 <div style="float:left" class="changeset-status-container">
19 <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
19 <div style="float:left;padding:0px 2px 0px 2px"><span style="font-size: 18px;">&rsaquo;</span></div>
20 <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change.status_lbl}</div>
20 <div title="${_('Changeset status')}" class="changeset-status-lbl"> ${co.status_change.status_lbl}</div>
21 <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change.status))}" /></div>
21 <div class="changeset-status-ico"><img src="${h.url(str('/images/icons/flag_status_%s.png' % co.status_change.status))}" /></div>
22 </div>
22 </div>
23 %endif
23 %endif
24 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
24 %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
25 <div class="buttons">
25 <div class="buttons">
26 <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
26 <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
27 </div>
27 </div>
28 %endif
28 %endif
29 </div>
29 </div>
30 <div class="text">
30 <div class="text">
31 ${h.rst_w_mentions(co.text)|n}
31 ${h.rst_w_mentions(co.text)|n}
32 </div>
32 </div>
33 </div>
33 </div>
34 </div>
34 </div>
35 </%def>
35 </%def>
36
36
37
37
38 <%def name="comment_inline_form()">
38 <%def name="comment_inline_form()">
39 <div id='comment-inline-form-template' style="display:none">
39 <div id='comment-inline-form-template' style="display:none">
40 <div class="comment-inline-form ac">
40 <div class="comment-inline-form ac">
41 %if c.rhodecode_user.username != 'default':
41 %if c.rhodecode_user.username != 'default':
42 <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
42 <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
43 ${h.form('#', class_='inline-form')}
43 ${h.form('#', class_='inline-form')}
44 <div class="clearfix">
44 <div class="clearfix">
45 <div class="comment-help">${_('Commenting on line {1}.')}
45 <div class="comment-help">${_('Commenting on line {1}.')}
46 ${(_('Comments parsed using %s syntax with %s support.') % (
46 ${(_('Comments parsed using %s syntax with %s support.') % (
47 ('<a href="%s">RST</a>' % h.url('rst_help')),
47 ('<a href="%s">RST</a>' % h.url('rst_help')),
48 ('<span style="color:#003367" class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
48 ('<span style="color:#003367" class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
49 )
49 )
50 )|n
50 )|n
51 }
51 }
52 </div>
52 </div>
53 <div class="mentions-container" id="mentions_container_{1}"></div>
53 <div class="mentions-container" id="mentions_container_{1}"></div>
54 <textarea id="text_{1}" name="text" class="yui-ac-input"></textarea>
54 <textarea id="text_{1}" name="text" class="yui-ac-input"></textarea>
55 </div>
55 </div>
56 <div class="comment-button">
56 <div class="comment-button">
57 <input type="hidden" name="f_path" value="{0}">
57 <input type="hidden" name="f_path" value="{0}">
58 <input type="hidden" name="line" value="{1}">
58 <input type="hidden" name="line" value="{1}">
59 ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
59 ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
60 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
60 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
61 </div>
61 </div>
62 ${h.end_form()}
62 ${h.end_form()}
63 %else:
63 %else:
64 ${h.form('')}
64 ${h.form('')}
65 <div class="clearfix">
65 <div class="clearfix">
66 <div class="comment-help">
66 <div class="comment-help">
67 ${_('You need to be logged in to comment.')} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
67 ${_('You need to be logged in to comment.')} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
68 </div>
68 </div>
69 </div>
69 </div>
70 <div class="comment-button">
70 <div class="comment-button">
71 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
71 ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
72 </div>
72 </div>
73 ${h.end_form()}
73 ${h.end_form()}
74 %endif
74 %endif
75 </div>
75 </div>
76 </div>
76 </div>
77 </%def>
77 </%def>
78
78
79
79
80 ## generates inlines taken from c.comments var
80 ## generates inlines taken from c.comments var
81 <%def name="inlines()">
81 <%def name="inlines()">
82 <div class="comments-number">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
82 <div class="comments-number">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
83 %for path, lines in c.inline_comments:
83 %for path, lines in c.inline_comments:
84 % for line,comments in lines.iteritems():
84 % for line,comments in lines.iteritems():
85 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
85 <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
86 %for co in comments:
86 %for co in comments:
87 ${comment_block(co)}
87 ${comment_block(co)}
88 %endfor
88 %endfor
89 </div>
89 </div>
90 %endfor
90 %endfor
91 %endfor
91 %endfor
92
92
93 </%def>
93 </%def>
94
94
95 ## MAIN COMMENT FORM
95 ## generate inline comments and the main ones
96 <%def name="comments(post_url, cur_status)">
96 <%def name="generate_comments()">
97
98 <div class="comments">
97 <div class="comments">
99 <div id="inline-comments-container">
98 <div id="inline-comments-container">
100 ## generate inlines for this changeset
99 ## generate inlines for this changeset
101 ${inlines()}
100 ${inlines()}
102 </div>
101 </div>
103
102
104 %for co in c.comments:
103 %for co in c.comments:
105 <div id="comment-tr-${co.comment_id}">
104 <div id="comment-tr-${co.comment_id}">
106 ${comment_block(co)}
105 ${comment_block(co)}
107 </div>
106 </div>
108 %endfor
107 %endfor
108 </div>
109 </%def>
110
111 ## MAIN COMMENT FORM
112 <%def name="comments(post_url, cur_status, close_btn=False)">
113
114 <div class="comments">
109 %if c.rhodecode_user.username != 'default':
115 %if c.rhodecode_user.username != 'default':
110 <div class="comment-form ac">
116 <div class="comment-form ac">
111 ${h.form(post_url)}
117 ${h.form(post_url)}
112 <strong>${_('Leave a comment')}</strong>
118 <strong>${_('Leave a comment')}</strong>
113 <div class="clearfix">
119 <div class="clearfix">
114 <div class="comment-help">
120 <div class="comment-help">
115 ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')),
121 ${(_('Comments parsed using %s syntax with %s support.') % (('<a href="%s">RST</a>' % h.url('rst_help')),
116 '<span style="color:#003367" class="tooltip" title="%s">@mention</span>' %
122 '<span style="color:#003367" class="tooltip" title="%s">@mention</span>' %
117 _('Use @username inside this text to send notification to this RhodeCode user')))|n}
123 _('Use @username inside this text to send notification to this RhodeCode user')))|n}
118 | <label for="show_changeset_status_box" class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')}</label>
124 | <label for="show_changeset_status_box" class="tooltip" title="${_('Check this to change current status of code-review for this changeset')}"> ${_('change status')}</label>
119 <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" />
125 <input style="vertical-align: bottom;margin-bottom:-2px" id="show_changeset_status_box" type="checkbox" name="change_changeset_status" />
120 </div>
126 </div>
121 <div id="status_block_container" class="status-block" style="display:none">
127 <div id="status_block_container" class="status-block" style="display:none">
122 %for status,lbl in c.changeset_statuses:
128 %for status,lbl in c.changeset_statuses:
123 <div class="">
129 <div class="">
124 <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" name="changeset_status" value="${status}"> <label>${lbl}</label>
130 <img src="${h.url('/images/icons/flag_status_%s.png' % status)}" /> <input ${'checked="checked"' if status == cur_status else ''}" type="radio" name="changeset_status" value="${status}"> <label>${lbl}</label>
125 </div>
131 </div>
126 %endfor
132 %endfor
127 </div>
133 </div>
128 <div class="mentions-container" id="mentions_container"></div>
134 <div class="mentions-container" id="mentions_container"></div>
129 ${h.textarea('text')}
135 ${h.textarea('text')}
130 </div>
136 </div>
131 <div class="comment-button">
137 <div class="comment-button">
132 ${h.submit('save', _('Comment'), class_='ui-button')}
138 ${h.submit('save', _('Comment'), class_="ui-btn large")}
139 %if close_btn:
140 ${h.submit('save_close', _('Comment and close'), class_='ui-btn blue large')}
141 %endif
133 </div>
142 </div>
134 ${h.end_form()}
143 ${h.end_form()}
135 </div>
144 </div>
136 %endif
145 %endif
137 </div>
146 </div>
138 <script>
147 <script>
139 YUE.onDOMReady(function () {
148 YUE.onDOMReady(function () {
140 MentionsAutoComplete('text', 'mentions_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
149 MentionsAutoComplete('text', 'mentions_container', _USERS_AC_DATA, _GROUPS_AC_DATA);
141
150
142 // changeset status box listener
151 // changeset status box listener
143 YUE.on(YUD.get('show_changeset_status_box'),'change',function(e){
152 YUE.on(YUD.get('show_changeset_status_box'),'change',function(e){
144 if(e.currentTarget.checked){
153 if(e.currentTarget.checked){
145 YUD.setStyle('status_block_container','display','');
154 YUD.setStyle('status_block_container','display','');
146 }
155 }
147 else{
156 else{
148 YUD.setStyle('status_block_container','display','none');
157 YUD.setStyle('status_block_container','display','none');
149 }
158 }
150 })
159 })
151
160
152 });
161 });
153 </script>
162 </script>
154 </%def>
163 </%def>
@@ -1,140 +1,146 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 %if c.pull_request.is_closed():
23 <div style="padding:10px; font-size:22px;width:100%;text-align: center; color:#88D882">${_('Closed %s') % (h.age(c.pull_request.updated_on))}</div>
24 %endif
23 <h3>${_('Title')}: ${c.pull_request.title}
25 <h3>${_('Title')}: ${c.pull_request.title}
24 <div class="changeset-status-container" style="float:none">
26 <div class="changeset-status-container" style="float:none">
25 %if c.current_changeset_status:
27 %if c.current_changeset_status:
26 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
28 <div title="${_('Pull request status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.current_changeset_status)}]</div>
27 <div class="changeset-status-ico" style="padding:4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
29 <div class="changeset-status-ico" style="padding:4px"><img src="${h.url('/images/icons/flag_status_%s.png' % c.current_changeset_status)}" /></div>
28 %endif
30 %endif
29 </div>
31 </div>
30 </h3>
32 </h3>
31 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
33 <div style="white-space:pre-wrap;padding:3px 3px 5px 20px">${h.literal(c.pull_request.description)}</div>
32 <div style="padding:4px 4px 10px 20px">
34 <div style="padding:4px 4px 10px 20px">
33 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
35 <div>${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}</div>
34 </div>
36 </div>
35
37
36 ## REVIEWERS
38 ## REVIEWERS
37 <div>
39 <div>
38 <div class="table" style="float:right;width:46%;clear:none">
40 <div class="table" style="float:right;width:46%;clear:none">
39 <div id="body" class="diffblock">
41 <div id="body" class="diffblock">
40 <div style="white-space:pre-wrap;padding:5px">${_('Pull request reviewers')}</div>
42 <div style="white-space:pre-wrap;padding:5px">${_('Pull request reviewers')}</div>
41 </div>
43 </div>
42 <div style="border: 1px solid #CCC">
44 <div style="border: 1px solid #CCC">
43 <div class="group_members_wrap">
45 <div class="group_members_wrap">
44 <ul class="group_members">
46 <ul class="group_members">
45 %for user,status in c.pull_request_reviewers:
47 %for user,status in c.pull_request_reviewers:
46 <li>
48 <li>
47 <div class="group_member">
49 <div class="group_member">
48 <div style="float:left;padding:3px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
50 <div style="float:left;padding:3px" class="tooltip" title="${h.tooltip(h.changeset_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
49 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
51 <img src="${h.url(str('/images/icons/flag_status_%s.png' % (status[0][1].status if status else 'not_reviewed')))}"/>
50 </div>
52 </div>
51 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,20)}"/> </div>
53 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(user.email,20)}"/> </div>
52 <div style="float:left">${user.username}</div>
54 <div style="float:left">${user.username}</div>
53 </div>
55 </div>
54 </li>
56 </li>
55 %endfor
57 %endfor
56 </ul>
58 </ul>
57 </div>
59 </div>
58 </div>
60 </div>
59 </div>
61 </div>
60 ##DIFF
62 ##DIFF
61 <div class="table" style="float:left;width:46%;clear:none">
63 <div class="table" style="float:left;width:46%;clear:none">
62 <div id="body" class="diffblock">
64 <div id="body" class="diffblock">
63 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
65 <div style="white-space:pre-wrap;padding:5px">${_('Compare view')}</div>
64 </div>
66 </div>
65 <div id="changeset_compare_view_content">
67 <div id="changeset_compare_view_content">
66 ##CS
68 ##CS
67 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
69 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Incoming changesets')}</div>
68 <%include file="/compare/compare_cs.html" />
70 <%include file="/compare/compare_cs.html" />
69
71
70 ## FILES
72 ## FILES
71 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
73 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
72 <div class="cs_files">
74 <div class="cs_files">
73 %for fid, change, f, stat in c.files:
75 %for fid, change, f, stat in c.files:
74 <div class="cs_${change}">
76 <div class="cs_${change}">
75 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
77 <div class="node">${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}</div>
76 <div class="changes">${h.fancy_file_stats(stat)}</div>
78 <div class="changes">${h.fancy_file_stats(stat)}</div>
77 </div>
79 </div>
78 %endfor
80 %endfor
79 </div>
81 </div>
80 </div>
82 </div>
81 </div>
83 </div>
82 </div>
84 </div>
83 <script>
85 <script>
84 var _USERS_AC_DATA = ${c.users_array|n};
86 var _USERS_AC_DATA = ${c.users_array|n};
85 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
87 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
86 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
88 AJAX_COMMENT_URL = "${url('pullrequest_comment',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id)}";
87 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
89 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
88 </script>
90 </script>
89
91
90 ## diff block
92 ## diff block
91 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
93 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
92 %for fid, change, f, stat in c.files:
94 %for fid, change, f, stat in c.files:
93 ${diff_block.diff_block_simple([c.changes[fid]])}
95 ${diff_block.diff_block_simple([c.changes[fid]])}
94 %endfor
96 %endfor
95
97
96 ## template for inline comment form
98 ## template for inline comment form
97 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
99 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
98 ${comment.comment_inline_form()}
100 ${comment.comment_inline_form()}
99
101
100 ## render comments main comments form and it status
102 ## render comments and inlines
101 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id),
103 ${comment.generate_comments()}
102 c.current_changeset_status)}
104
103
105 % if not c.pull_request.is_closed():
106 ## main comment form and it status
107 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
108 pull_request_id=c.pull_request.pull_request_id),
109 c.current_changeset_status,
110 close_btn=True)}
111 %endif
104
112
105 <script type="text/javascript">
113 <script type="text/javascript">
106 YUE.onDOMReady(function(){
114 YUE.onDOMReady(function(){
107
115
108 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
116 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
109 var show = 'none';
117 var show = 'none';
110 var target = e.currentTarget;
118 var target = e.currentTarget;
111 if(target.checked){
119 if(target.checked){
112 var show = ''
120 var show = ''
113 }
121 }
114 var boxid = YUD.getAttribute(target,'id_for');
122 var boxid = YUD.getAttribute(target,'id_for');
115 var comments = YUQ('#{0} .inline-comments'.format(boxid));
123 var comments = YUQ('#{0} .inline-comments'.format(boxid));
116 for(c in comments){
124 for(c in comments){
117 YUD.setStyle(comments[c],'display',show);
125 YUD.setStyle(comments[c],'display',show);
118 }
126 }
119 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
127 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
120 for(c in btns){
128 for(c in btns){
121 YUD.setStyle(btns[c],'display',show);
129 YUD.setStyle(btns[c],'display',show);
122 }
130 }
123 })
131 })
124
132
125 YUE.on(YUQ('.line'),'click',function(e){
133 YUE.on(YUQ('.line'),'click',function(e){
126 var tr = e.currentTarget;
134 var tr = e.currentTarget;
127 injectInlineForm(tr);
135 injectInlineForm(tr);
128 });
136 });
129
137
130 // inject comments into they proper positions
138 // inject comments into they proper positions
131 var file_comments = YUQ('.inline-comment-placeholder');
139 var file_comments = YUQ('.inline-comment-placeholder');
132 renderInlineComments(file_comments);
140 renderInlineComments(file_comments);
133 })
141 })
134
135 </script>
142 </script>
136
143
137
138 </div>
144 </div>
139
145
140 </%def>
146 </%def>
@@ -1,38 +1,42 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} ${_('All pull requests')}
4 ${c.repo_name} ${_('all pull requests')}
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 ${_('All pull requests')}
12 ${_('All pull requests')}
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 %for pr in c.pull_requests:
23 %for pr in c.pull_requests:
24 <div>
24 <div>
25 <h4><a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">
25 <h4>
26 %if pr.is_closed():
27 <img src="${h.url('/images/icons/tick.png')}" alt="${_('Closed')}" />
28 %endif
29 <a href="${h.url('pullrequest_show',repo_name=c.repo_name,pull_request_id=pr.pull_request_id)}">
26 ${_('Pull request #%s opened by %s on %s') % (pr.pull_request_id, pr.author.full_name, h.fmt_date(pr.created_on))}
30 ${_('Pull request #%s opened by %s on %s') % (pr.pull_request_id, pr.author.full_name, h.fmt_date(pr.created_on))}
27 </a>
31 </a>
28 </h4>
32 </h4>
29 <h5 style="border:0px;padding-bottom:0px">${_('Title')}: ${pr.title}</h5>
33 <h5 style="border:0px;padding-bottom:0px">${_('Title')}: ${pr.title}</h5>
30 <div style="padding:0px 24px">${pr.description}</div>
34 <div style="padding:0px 24px">${pr.description}</div>
31 </div>
35 </div>
32 %endfor
36 %endfor
33
37
34 </div>
38 </div>
35
39
36 <script type="text/javascript"></script>
40 <script type="text/javascript"></script>
37
41
38 </%def>
42 </%def>
General Comments 0
You need to be logged in to leave comments. Login now