##// END OF EJS Templates
Readme renderer now uses landing_rev parameter to render the readme based on...
marcink -
r2603:370ed782 beta
parent child Browse files
Show More
@@ -1,442 +1,446 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.repos
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Repositories controller for RhodeCode
7 7
8 8 :created_on: Apr 7, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import logging
27 27 import traceback
28 28 import formencode
29 29 from formencode import htmlfill
30 30
31 31 from webob.exc import HTTPInternalServerError
32 32 from pylons import request, session, tmpl_context as c, url
33 33 from pylons.controllers.util import redirect
34 34 from pylons.i18n.translation import _
35 35 from sqlalchemy.exc import IntegrityError
36 36
37 37 from rhodecode.lib import helpers as h
38 38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 39 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
40 40 from rhodecode.lib.base import BaseController, render
41 41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 42 from rhodecode.lib.helpers import get_token
43 43 from rhodecode.model.meta import Session
44 44 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 45 from rhodecode.model.forms import RepoForm
46 46 from rhodecode.model.scm import ScmModel
47 47 from rhodecode.model.repo import RepoModel
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51
52 52 class ReposController(BaseController):
53 53 """
54 54 REST Controller styled on the Atom Publishing Protocol"""
55 55 # To properly map this controller, ensure your config/routing.py
56 56 # file has a resource setup:
57 57 # map.resource('repo', 'repos')
58 58
59 59 @LoginRequired()
60 60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 61 def __before__(self):
62 62 c.admin_user = session.get('admin_user')
63 63 c.admin_username = session.get('admin_username')
64 64 super(ReposController, self).__before__()
65 65
66 66 def __load_defaults(self):
67 67 c.repo_groups = RepoGroup.groups_choices()
68 68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69 69
70 70 repo_model = RepoModel()
71 71 c.users_array = repo_model.get_users_js()
72 72 c.users_groups_array = repo_model.get_users_groups_js()
73 73 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
74 74 c.landing_revs_choices = choices
75 75
76 76 def __load_data(self, repo_name=None):
77 77 """
78 78 Load defaults settings for edit, and update
79 79
80 80 :param repo_name:
81 81 """
82 82 self.__load_defaults()
83 83
84 84 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
85 85 repo = db_repo.scm_instance
86 86
87 87 if c.repo_info is None:
88 88 h.flash(_('%s repository is not mapped to db perhaps'
89 89 ' it was created or renamed from the filesystem'
90 90 ' please run the application again'
91 91 ' in order to rescan repositories') % repo_name,
92 92 category='error')
93 93
94 94 return redirect(url('repos'))
95 95
96 96 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
97 97 c.landing_revs_choices = choices
98 98
99 99 c.default_user_id = User.get_by_username('default').user_id
100 100 c.in_public_journal = UserFollowing.query()\
101 101 .filter(UserFollowing.user_id == c.default_user_id)\
102 102 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
103 103
104 104 if c.repo_info.stats:
105 105 # this is on what revision we ended up so we add +1 for count
106 106 last_rev = c.repo_info.stats.stat_on_revision + 1
107 107 else:
108 108 last_rev = 0
109 109 c.stats_revision = last_rev
110 110
111 111 c.repo_last_rev = repo.count() if repo.revisions else 0
112 112
113 113 if last_rev == 0 or c.repo_last_rev == 0:
114 114 c.stats_percentage = 0
115 115 else:
116 116 c.stats_percentage = '%.2f' % ((float((last_rev)) /
117 117 c.repo_last_rev) * 100)
118 118
119 119 defaults = RepoModel()._get_defaults(repo_name)
120 120
121 121 c.repos_list = [('', _('--REMOVE FORK--'))]
122 122 c.repos_list += [(x.repo_id, x.repo_name) for x in
123 123 Repository.query().order_by(Repository.repo_name).all()]
124 124
125 125 return defaults
126 126
127 127 @HasPermissionAllDecorator('hg.admin')
128 128 def index(self, format='html'):
129 129 """GET /repos: All items in the collection"""
130 130 # url('repos')
131 131
132 132 c.repos_list = ScmModel().get_repos(Repository.query()
133 133 .order_by(Repository.repo_name)
134 134 .all(), sort_key='name_sort')
135 135 return render('admin/repos/repos.html')
136 136
137 137 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
138 138 def create(self):
139 139 """
140 140 POST /repos: Create a new item"""
141 141 # url('repos')
142 142
143 143 self.__load_defaults()
144 144 form_result = {}
145 145 try:
146 146 form_result = RepoForm(repo_groups=c.repo_groups_choices,
147 147 landing_revs=c.landing_revs_choices)()\
148 148 .to_python(dict(request.POST))
149 149 RepoModel().create(form_result, self.rhodecode_user.user_id)
150 150 if form_result['clone_uri']:
151 151 h.flash(_('created repository %s from %s') \
152 152 % (form_result['repo_name'], form_result['clone_uri']),
153 153 category='success')
154 154 else:
155 155 h.flash(_('created repository %s') % form_result['repo_name'],
156 156 category='success')
157 157
158 158 if request.POST.get('user_created'):
159 159 # created by regular non admin user
160 160 action_logger(self.rhodecode_user, 'user_created_repo',
161 161 form_result['repo_name_full'], self.ip_addr,
162 162 self.sa)
163 163 else:
164 164 action_logger(self.rhodecode_user, 'admin_created_repo',
165 165 form_result['repo_name_full'], self.ip_addr,
166 166 self.sa)
167 167 Session.commit()
168 168 except formencode.Invalid, errors:
169 169
170 170 c.new_repo = errors.value['repo_name']
171 171
172 172 if request.POST.get('user_created'):
173 173 r = render('admin/repos/repo_add_create_repository.html')
174 174 else:
175 175 r = render('admin/repos/repo_add.html')
176 176
177 177 return htmlfill.render(
178 178 r,
179 179 defaults=errors.value,
180 180 errors=errors.error_dict or {},
181 181 prefix_error=False,
182 182 encoding="UTF-8")
183 183
184 184 except Exception:
185 185 log.error(traceback.format_exc())
186 186 msg = _('error occurred during creation of repository %s') \
187 187 % form_result.get('repo_name')
188 188 h.flash(msg, category='error')
189 189 if request.POST.get('user_created'):
190 190 return redirect(url('home'))
191 191 return redirect(url('repos'))
192 192
193 193 @HasPermissionAllDecorator('hg.admin')
194 194 def new(self, format='html'):
195 195 """GET /repos/new: Form to create a new item"""
196 196 new_repo = request.GET.get('repo', '')
197 197 c.new_repo = repo_name_slug(new_repo)
198 198 self.__load_defaults()
199 199 return render('admin/repos/repo_add.html')
200 200
201 201 @HasPermissionAllDecorator('hg.admin')
202 202 def update(self, repo_name):
203 203 """
204 204 PUT /repos/repo_name: Update an existing item"""
205 205 # Forms posted to this method should contain a hidden field:
206 206 # <input type="hidden" name="_method" value="PUT" />
207 207 # Or using helpers:
208 208 # h.form(url('repo', repo_name=ID),
209 209 # method='put')
210 210 # url('repo', repo_name=ID)
211 211 self.__load_defaults()
212 212 repo_model = RepoModel()
213 213 changed_name = repo_name
214 #override the choices with extracted revisions !
215 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
216 c.landing_revs_choices = choices
217
214 218 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
215 219 repo_groups=c.repo_groups_choices,
216 220 landing_revs=c.landing_revs_choices)()
217 221 try:
218 222 form_result = _form.to_python(dict(request.POST))
219 223 repo = repo_model.update(repo_name, form_result)
220 224 invalidate_cache('get_repo_cached_%s' % repo_name)
221 225 h.flash(_('Repository %s updated successfully') % repo_name,
222 226 category='success')
223 227 changed_name = repo.repo_name
224 228 action_logger(self.rhodecode_user, 'admin_updated_repo',
225 229 changed_name, self.ip_addr, self.sa)
226 230 Session.commit()
227 231 except formencode.Invalid, errors:
228 232 defaults = self.__load_data(repo_name)
229 233 defaults.update(errors.value)
230 234 return htmlfill.render(
231 235 render('admin/repos/repo_edit.html'),
232 236 defaults=defaults,
233 237 errors=errors.error_dict or {},
234 238 prefix_error=False,
235 239 encoding="UTF-8")
236 240
237 241 except Exception:
238 242 log.error(traceback.format_exc())
239 243 h.flash(_('error occurred during update of repository %s') \
240 244 % repo_name, category='error')
241 245 return redirect(url('edit_repo', repo_name=changed_name))
242 246
243 247 @HasPermissionAllDecorator('hg.admin')
244 248 def delete(self, repo_name):
245 249 """
246 250 DELETE /repos/repo_name: Delete an existing item"""
247 251 # Forms posted to this method should contain a hidden field:
248 252 # <input type="hidden" name="_method" value="DELETE" />
249 253 # Or using helpers:
250 254 # h.form(url('repo', repo_name=ID),
251 255 # method='delete')
252 256 # url('repo', repo_name=ID)
253 257
254 258 repo_model = RepoModel()
255 259 repo = repo_model.get_by_repo_name(repo_name)
256 260 if not repo:
257 261 h.flash(_('%s repository is not mapped to db perhaps'
258 262 ' it was moved or renamed from the filesystem'
259 263 ' please run the application again'
260 264 ' in order to rescan repositories') % repo_name,
261 265 category='error')
262 266
263 267 return redirect(url('repos'))
264 268 try:
265 269 action_logger(self.rhodecode_user, 'admin_deleted_repo',
266 270 repo_name, self.ip_addr, self.sa)
267 271 repo_model.delete(repo)
268 272 invalidate_cache('get_repo_cached_%s' % repo_name)
269 273 h.flash(_('deleted repository %s') % repo_name, category='success')
270 274 Session.commit()
271 275 except IntegrityError, e:
272 276 if e.message.find('repositories_fork_id_fkey') != -1:
273 277 log.error(traceback.format_exc())
274 278 h.flash(_('Cannot delete %s it still contains attached '
275 279 'forks') % repo_name,
276 280 category='warning')
277 281 else:
278 282 log.error(traceback.format_exc())
279 283 h.flash(_('An error occurred during '
280 284 'deletion of %s') % repo_name,
281 285 category='error')
282 286
283 287 except Exception, e:
284 288 log.error(traceback.format_exc())
285 289 h.flash(_('An error occurred during deletion of %s') % repo_name,
286 290 category='error')
287 291
288 292 return redirect(url('repos'))
289 293
290 294 @HasRepoPermissionAllDecorator('repository.admin')
291 295 def delete_perm_user(self, repo_name):
292 296 """
293 297 DELETE an existing repository permission user
294 298
295 299 :param repo_name:
296 300 """
297 301 try:
298 302 RepoModel().revoke_user_permission(repo=repo_name,
299 303 user=request.POST['user_id'])
300 304 Session.commit()
301 305 except Exception:
302 306 log.error(traceback.format_exc())
303 307 h.flash(_('An error occurred during deletion of repository user'),
304 308 category='error')
305 309 raise HTTPInternalServerError()
306 310
307 311 @HasRepoPermissionAllDecorator('repository.admin')
308 312 def delete_perm_users_group(self, repo_name):
309 313 """
310 314 DELETE an existing repository permission users group
311 315
312 316 :param repo_name:
313 317 """
314 318
315 319 try:
316 320 RepoModel().revoke_users_group_permission(
317 321 repo=repo_name, group_name=request.POST['users_group_id']
318 322 )
319 323 Session.commit()
320 324 except Exception:
321 325 log.error(traceback.format_exc())
322 326 h.flash(_('An error occurred during deletion of repository'
323 327 ' users groups'),
324 328 category='error')
325 329 raise HTTPInternalServerError()
326 330
327 331 @HasPermissionAllDecorator('hg.admin')
328 332 def repo_stats(self, repo_name):
329 333 """
330 334 DELETE an existing repository statistics
331 335
332 336 :param repo_name:
333 337 """
334 338
335 339 try:
336 340 RepoModel().delete_stats(repo_name)
337 341 Session.commit()
338 342 except Exception, e:
339 343 h.flash(_('An error occurred during deletion of repository stats'),
340 344 category='error')
341 345 return redirect(url('edit_repo', repo_name=repo_name))
342 346
343 347 @HasPermissionAllDecorator('hg.admin')
344 348 def repo_cache(self, repo_name):
345 349 """
346 350 INVALIDATE existing repository cache
347 351
348 352 :param repo_name:
349 353 """
350 354
351 355 try:
352 356 ScmModel().mark_for_invalidation(repo_name)
353 357 Session.commit()
354 358 except Exception, e:
355 359 h.flash(_('An error occurred during cache invalidation'),
356 360 category='error')
357 361 return redirect(url('edit_repo', repo_name=repo_name))
358 362
359 363 @HasPermissionAllDecorator('hg.admin')
360 364 def repo_public_journal(self, repo_name):
361 365 """
362 366 Set's this repository to be visible in public journal,
363 367 in other words assing default user to follow this repo
364 368
365 369 :param repo_name:
366 370 """
367 371
368 372 cur_token = request.POST.get('auth_token')
369 373 token = get_token()
370 374 if cur_token == token:
371 375 try:
372 376 repo_id = Repository.get_by_repo_name(repo_name).repo_id
373 377 user_id = User.get_by_username('default').user_id
374 378 self.scm_model.toggle_following_repo(repo_id, user_id)
375 379 h.flash(_('Updated repository visibility in public journal'),
376 380 category='success')
377 381 Session.commit()
378 382 except:
379 383 h.flash(_('An error occurred during setting this'
380 384 ' repository in public journal'),
381 385 category='error')
382 386
383 387 else:
384 388 h.flash(_('Token mismatch'), category='error')
385 389 return redirect(url('edit_repo', repo_name=repo_name))
386 390
387 391 @HasPermissionAllDecorator('hg.admin')
388 392 def repo_pull(self, repo_name):
389 393 """
390 394 Runs task to update given repository with remote changes,
391 395 ie. make pull on remote location
392 396
393 397 :param repo_name:
394 398 """
395 399 try:
396 400 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
397 401 h.flash(_('Pulled from remote location'), category='success')
398 402 except Exception, e:
399 403 h.flash(_('An error occurred during pull from remote location'),
400 404 category='error')
401 405
402 406 return redirect(url('edit_repo', repo_name=repo_name))
403 407
404 408 @HasPermissionAllDecorator('hg.admin')
405 409 def repo_as_fork(self, repo_name):
406 410 """
407 411 Mark given repository as a fork of another
408 412
409 413 :param repo_name:
410 414 """
411 415 try:
412 416 fork_id = request.POST.get('id_fork_of')
413 417 repo = ScmModel().mark_as_fork(repo_name, fork_id,
414 418 self.rhodecode_user.username)
415 419 fork = repo.fork.repo_name if repo.fork else _('Nothing')
416 420 Session.commit()
417 421 h.flash(_('Marked repo %s as fork of %s') % (repo_name,fork),
418 422 category='success')
419 423 except Exception, e:
420 424 raise
421 425 h.flash(_('An error occurred during this operation'),
422 426 category='error')
423 427
424 428 return redirect(url('edit_repo', repo_name=repo_name))
425 429
426 430 @HasPermissionAllDecorator('hg.admin')
427 431 def show(self, repo_name, format='html'):
428 432 """GET /repos/repo_name: Show a specific item"""
429 433 # url('repo', repo_name=ID)
430 434
431 435 @HasPermissionAllDecorator('hg.admin')
432 436 def edit(self, repo_name, format='html'):
433 437 """GET /repos/repo_name/edit: Form to edit an existing item"""
434 438 # url('edit_repo', repo_name=ID)
435 439 defaults = self.__load_data(repo_name)
436 440
437 441 return htmlfill.render(
438 442 render('admin/repos/repo_edit.html'),
439 443 defaults=defaults,
440 444 encoding="UTF-8",
441 445 force_defaults=False
442 446 )
@@ -1,243 +1,244 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.summary
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Summary controller for Rhodecode
7 7
8 8 :created_on: Apr 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import traceback
27 27 import calendar
28 28 import logging
29 29 import urllib
30 30 from time import mktime
31 31 from datetime import timedelta, date
32 32 from urlparse import urlparse
33 33 from rhodecode.lib.compat import product
34 34
35 35 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
36 36 NodeDoesNotExistError
37 37
38 38 from pylons import tmpl_context as c, request, url, config
39 39 from pylons.i18n.translation import _
40 40
41 41 from beaker.cache import cache_region, region_invalidate
42 42
43 43 from rhodecode.config.conf import ALL_READMES, ALL_EXTS, LANGUAGES_EXTENSIONS_MAP
44 44 from rhodecode.model.db import Statistics, CacheInvalidation
45 45 from rhodecode.lib.utils2 import safe_unicode
46 46 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
47 47 from rhodecode.lib.base import BaseRepoController, render
48 48 from rhodecode.lib.utils import EmptyChangeset
49 49 from rhodecode.lib.markup_renderer import MarkupRenderer
50 50 from rhodecode.lib.celerylib import run_task
51 51 from rhodecode.lib.celerylib.tasks import get_commits_stats
52 52 from rhodecode.lib.helpers import RepoPage
53 53 from rhodecode.lib.compat import json, OrderedDict
54 54 from rhodecode.lib.vcs.nodes import FileNode
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58 README_FILES = [''.join([x[0][0], x[1][0]]) for x in
59 59 sorted(list(product(ALL_READMES, ALL_EXTS)),
60 60 key=lambda y:y[0][1] + y[1][1])]
61 61
62 62
63 63 class SummaryController(BaseRepoController):
64 64
65 65 @LoginRequired()
66 66 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
67 67 'repository.admin')
68 68 def __before__(self):
69 69 super(SummaryController, self).__before__()
70 70
71 71 def index(self, repo_name):
72 72 c.dbrepo = dbrepo = c.rhodecode_db_repo
73 73 c.following = self.scm_model.is_following_repo(repo_name,
74 74 self.rhodecode_user.user_id)
75 75
76 76 def url_generator(**kw):
77 77 return url('shortlog_home', repo_name=repo_name, size=10, **kw)
78 78
79 79 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1,
80 80 items_per_page=10, url=url_generator)
81 81
82 82 if self.rhodecode_user.username == 'default':
83 83 # for default(anonymous) user we don't need to pass credentials
84 84 username = ''
85 85 password = ''
86 86 else:
87 87 username = str(self.rhodecode_user.username)
88 88 password = '@'
89 89
90 90 parsed_url = urlparse(url.current(qualified=True))
91 91
92 92 default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
93 93
94 94 uri_tmpl = config.get('clone_uri', default_clone_uri)
95 95 uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
96 96 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
97 97 uri_dict = {
98 98 'user': username,
99 99 'pass': password,
100 100 'scheme': parsed_url.scheme,
101 101 'netloc': parsed_url.netloc,
102 102 'path': decoded_path
103 103 }
104 104
105 105 uri = uri_tmpl % uri_dict
106 106 # generate another clone url by id
107 107 uri_dict.update(
108 108 {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
109 109 )
110 110 uri_id = uri_tmpl % uri_dict
111 111
112 112 c.clone_repo_url = uri
113 113 c.clone_repo_url_id = uri_id
114 114 c.repo_tags = OrderedDict()
115 115 for name, hash_ in c.rhodecode_repo.tags.items()[:10]:
116 116 try:
117 117 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash_)
118 118 except ChangesetError:
119 119 c.repo_tags[name] = EmptyChangeset(hash_)
120 120
121 121 c.repo_branches = OrderedDict()
122 122 for name, hash_ in c.rhodecode_repo.branches.items()[:10]:
123 123 try:
124 124 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_)
125 125 except ChangesetError:
126 126 c.repo_branches[name] = EmptyChangeset(hash_)
127 127
128 128 td = date.today() + timedelta(days=1)
129 129 td_1m = td - timedelta(days=calendar.mdays[td.month])
130 130 td_1y = td - timedelta(days=365)
131 131
132 132 ts_min_m = mktime(td_1m.timetuple())
133 133 ts_min_y = mktime(td_1y.timetuple())
134 134 ts_max_y = mktime(td.timetuple())
135 135
136 136 if dbrepo.enable_statistics:
137 137 c.show_stats = True
138 138 c.no_data_msg = _('No data loaded yet')
139 139 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
140 140 else:
141 141 c.show_stats = False
142 142 c.no_data_msg = _('Statistics are disabled for this repository')
143 143 c.ts_min = ts_min_m
144 144 c.ts_max = ts_max_y
145 145
146 146 stats = self.sa.query(Statistics)\
147 147 .filter(Statistics.repository == dbrepo)\
148 148 .scalar()
149 149
150 150 c.stats_percentage = 0
151 151
152 152 if stats and stats.languages:
153 153 c.no_data = False is dbrepo.enable_statistics
154 154 lang_stats_d = json.loads(stats.languages)
155 155 c.commit_data = stats.commit_activity
156 156 c.overview_data = stats.commit_activity_combined
157 157
158 158 lang_stats = ((x, {"count": y,
159 159 "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
160 160 for x, y in lang_stats_d.items())
161 161
162 162 c.trending_languages = json.dumps(
163 163 sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
164 164 )
165 165 last_rev = stats.stat_on_revision + 1
166 166 c.repo_last_rev = c.rhodecode_repo.count()\
167 167 if c.rhodecode_repo.revisions else 0
168 168 if last_rev == 0 or c.repo_last_rev == 0:
169 169 pass
170 170 else:
171 171 c.stats_percentage = '%.2f' % ((float((last_rev)) /
172 172 c.repo_last_rev) * 100)
173 173 else:
174 174 c.commit_data = json.dumps({})
175 175 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
176 176 c.trending_languages = json.dumps({})
177 177 c.no_data = True
178 178
179 179 c.enable_downloads = dbrepo.enable_downloads
180 180 if c.enable_downloads:
181 181 c.download_options = self._get_download_links(c.rhodecode_repo)
182 182
183 c.readme_data, c.readme_file = self.__get_readme_data(
184 c.rhodecode_db_repo.repo_name, c.rhodecode_repo
185 )
183 c.readme_data, c.readme_file = \
184 self.__get_readme_data(c.rhodecode_db_repo)
186 185 return render('summary/summary.html')
187 186
188 def __get_readme_data(self, repo_name, repo):
187 def __get_readme_data(self, db_repo):
188 repo_name = db_repo.repo_name
189 189
190 190 @cache_region('long_term')
191 191 def _get_readme_from_cache(key):
192 192 readme_data = None
193 193 readme_file = None
194 194 log.debug('Fetching readme file')
195 195 try:
196 cs = repo.get_changeset() # fetches TIP
196 # get's the landing revision! or tip if fails
197 cs = db_repo.get_landing_changeset()
197 198 renderer = MarkupRenderer()
198 199 for f in README_FILES:
199 200 try:
200 201 readme = cs.get_node(f)
201 202 if not isinstance(readme, FileNode):
202 203 continue
203 204 readme_file = f
204 205 readme_data = renderer.render(readme.content, f)
205 206 log.debug('Found readme %s' % readme_file)
206 207 break
207 208 except NodeDoesNotExistError:
208 209 continue
209 210 except ChangesetError:
210 211 log.error(traceback.format_exc())
211 212 pass
212 213 except EmptyRepositoryError:
213 214 pass
214 215 except Exception:
215 216 log.error(traceback.format_exc())
216 217
217 218 return readme_data, readme_file
218 219
219 220 key = repo_name + '_README'
220 221 inv = CacheInvalidation.invalidate(key)
221 222 if inv is not None:
222 223 region_invalidate(_get_readme_from_cache, None, key)
223 224 CacheInvalidation.set_valid(inv.cache_key)
224 225 return _get_readme_from_cache(key)
225 226
226 227 def _get_download_links(self, repo):
227 228
228 229 download_l = []
229 230
230 231 branches_group = ([], _("Branches"))
231 232 tags_group = ([], _("Tags"))
232 233
233 234 for name, chs in c.rhodecode_repo.branches.items():
234 235 #chs = chs.split(':')[-1]
235 236 branches_group[0].append((chs, name),)
236 237 download_l.append(branches_group)
237 238
238 239 for name, chs in c.rhodecode_repo.tags.items():
239 240 #chs = chs.split(':')[-1]
240 241 tags_group[0].append((chs, name),)
241 242 download_l.append(tags_group)
242 243
243 244 return download_l
@@ -1,1646 +1,1653 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 import hashlib
31 31 from collections import defaultdict
32 32
33 33 from sqlalchemy import *
34 34 from sqlalchemy.ext.hybrid import hybrid_property
35 35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 36 from sqlalchemy.exc import DatabaseError
37 37 from beaker.cache import cache_region, region_invalidate
38 38 from webob.exc import HTTPNotFound
39 39
40 40 from pylons.i18n.translation import lazy_ugettext as _
41 41
42 42 from rhodecode.lib.vcs import get_backend
43 43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 44 from rhodecode.lib.vcs.exceptions import VCSError
45 45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 46
47 47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 48 safe_unicode
49 49 from rhodecode.lib.compat import json
50 50 from rhodecode.lib.caching_query import FromCache
51 51
52 52 from rhodecode.model.meta import Base, Session
53 53
54 54 URL_SEP = '/'
55 55 log = logging.getLogger(__name__)
56 56
57 57 #==============================================================================
58 58 # BASE CLASSES
59 59 #==============================================================================
60 60
61 61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 62
63 63
64 64 class BaseModel(object):
65 65 """
66 66 Base Model for all classess
67 67 """
68 68
69 69 @classmethod
70 70 def _get_keys(cls):
71 71 """return column names for this model """
72 72 return class_mapper(cls).c.keys()
73 73
74 74 def get_dict(self):
75 75 """
76 76 return dict with keys and values corresponding
77 77 to this model data """
78 78
79 79 d = {}
80 80 for k in self._get_keys():
81 81 d[k] = getattr(self, k)
82 82
83 83 # also use __json__() if present to get additional fields
84 84 _json_attr = getattr(self, '__json__', None)
85 85 if _json_attr:
86 86 # update with attributes from __json__
87 87 if callable(_json_attr):
88 88 _json_attr = _json_attr()
89 89 for k, val in _json_attr.iteritems():
90 90 d[k] = val
91 91 return d
92 92
93 93 def get_appstruct(self):
94 94 """return list with keys and values tupples corresponding
95 95 to this model data """
96 96
97 97 l = []
98 98 for k in self._get_keys():
99 99 l.append((k, getattr(self, k),))
100 100 return l
101 101
102 102 def populate_obj(self, populate_dict):
103 103 """populate model with data from given populate_dict"""
104 104
105 105 for k in self._get_keys():
106 106 if k in populate_dict:
107 107 setattr(self, k, populate_dict[k])
108 108
109 109 @classmethod
110 110 def query(cls):
111 111 return Session().query(cls)
112 112
113 113 @classmethod
114 114 def get(cls, id_):
115 115 if id_:
116 116 return cls.query().get(id_)
117 117
118 118 @classmethod
119 119 def get_or_404(cls, id_):
120 120 if id_:
121 121 res = cls.query().get(id_)
122 122 if not res:
123 123 raise HTTPNotFound
124 124 return res
125 125
126 126 @classmethod
127 127 def getAll(cls):
128 128 return cls.query().all()
129 129
130 130 @classmethod
131 131 def delete(cls, id_):
132 132 obj = cls.query().get(id_)
133 133 Session().delete(obj)
134 134
135 135 def __repr__(self):
136 136 if hasattr(self, '__unicode__'):
137 137 # python repr needs to return str
138 138 return safe_str(self.__unicode__())
139 139 return '<DB:%s>' % (self.__class__.__name__)
140 140
141 141
142 142 class RhodeCodeSetting(Base, BaseModel):
143 143 __tablename__ = 'rhodecode_settings'
144 144 __table_args__ = (
145 145 UniqueConstraint('app_settings_name'),
146 146 {'extend_existing': True, 'mysql_engine': 'InnoDB',
147 147 'mysql_charset': 'utf8'}
148 148 )
149 149 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
150 150 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 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 153 def __init__(self, k='', v=''):
154 154 self.app_settings_name = k
155 155 self.app_settings_value = v
156 156
157 157 @validates('_app_settings_value')
158 158 def validate_settings_value(self, key, val):
159 159 assert type(val) == unicode
160 160 return val
161 161
162 162 @hybrid_property
163 163 def app_settings_value(self):
164 164 v = self._app_settings_value
165 165 if self.app_settings_name == 'ldap_active':
166 166 v = str2bool(v)
167 167 return v
168 168
169 169 @app_settings_value.setter
170 170 def app_settings_value(self, val):
171 171 """
172 172 Setter that will always make sure we use unicode in app_settings_value
173 173
174 174 :param val:
175 175 """
176 176 self._app_settings_value = safe_unicode(val)
177 177
178 178 def __unicode__(self):
179 179 return u"<%s('%s:%s')>" % (
180 180 self.__class__.__name__,
181 181 self.app_settings_name, self.app_settings_value
182 182 )
183 183
184 184 @classmethod
185 185 def get_by_name(cls, ldap_key):
186 186 return cls.query()\
187 187 .filter(cls.app_settings_name == ldap_key).scalar()
188 188
189 189 @classmethod
190 190 def get_app_settings(cls, cache=False):
191 191
192 192 ret = cls.query()
193 193
194 194 if cache:
195 195 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
196 196
197 197 if not ret:
198 198 raise Exception('Could not get application settings !')
199 199 settings = {}
200 200 for each in ret:
201 201 settings['rhodecode_' + each.app_settings_name] = \
202 202 each.app_settings_value
203 203
204 204 return settings
205 205
206 206 @classmethod
207 207 def get_ldap_settings(cls, cache=False):
208 208 ret = cls.query()\
209 209 .filter(cls.app_settings_name.startswith('ldap_')).all()
210 210 fd = {}
211 211 for row in ret:
212 212 fd.update({row.app_settings_name: row.app_settings_value})
213 213
214 214 return fd
215 215
216 216
217 217 class RhodeCodeUi(Base, BaseModel):
218 218 __tablename__ = 'rhodecode_ui'
219 219 __table_args__ = (
220 220 UniqueConstraint('ui_key'),
221 221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
222 222 'mysql_charset': 'utf8'}
223 223 )
224 224
225 225 HOOK_UPDATE = 'changegroup.update'
226 226 HOOK_REPO_SIZE = 'changegroup.repo_size'
227 227 HOOK_PUSH = 'changegroup.push_logger'
228 228 HOOK_PULL = 'preoutgoing.pull_logger'
229 229
230 230 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
231 231 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
232 232 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
233 233 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 234 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
235 235
236 236 @classmethod
237 237 def get_by_key(cls, key):
238 238 return cls.query().filter(cls.ui_key == key)
239 239
240 240 @classmethod
241 241 def get_builtin_hooks(cls):
242 242 q = cls.query()
243 243 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
244 244 cls.HOOK_REPO_SIZE,
245 245 cls.HOOK_PUSH, cls.HOOK_PULL]))
246 246 return q.all()
247 247
248 248 @classmethod
249 249 def get_custom_hooks(cls):
250 250 q = cls.query()
251 251 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
252 252 cls.HOOK_REPO_SIZE,
253 253 cls.HOOK_PUSH, cls.HOOK_PULL]))
254 254 q = q.filter(cls.ui_section == 'hooks')
255 255 return q.all()
256 256
257 257 @classmethod
258 258 def get_repos_location(cls):
259 259 return cls.get_by_key('/').one().ui_value
260 260
261 261 @classmethod
262 262 def create_or_update_hook(cls, key, val):
263 263 new_ui = cls.get_by_key(key).scalar() or cls()
264 264 new_ui.ui_section = 'hooks'
265 265 new_ui.ui_active = True
266 266 new_ui.ui_key = key
267 267 new_ui.ui_value = val
268 268
269 269 Session().add(new_ui)
270 270
271 271
272 272 class User(Base, BaseModel):
273 273 __tablename__ = 'users'
274 274 __table_args__ = (
275 275 UniqueConstraint('username'), UniqueConstraint('email'),
276 276 {'extend_existing': True, 'mysql_engine': 'InnoDB',
277 277 'mysql_charset': 'utf8'}
278 278 )
279 279 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
280 280 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
281 281 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 282 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
283 283 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
284 284 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 285 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 286 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287 287 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
288 288 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 289 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 290
291 291 user_log = relationship('UserLog', cascade='all')
292 292 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
293 293
294 294 repositories = relationship('Repository')
295 295 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
296 296 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
297 297 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
298 298
299 299 group_member = relationship('UsersGroupMember', cascade='all')
300 300
301 301 notifications = relationship('UserNotification', cascade='all')
302 302 # notifications assigned to this user
303 303 user_created_notifications = relationship('Notification', cascade='all')
304 304 # comments created by this user
305 305 user_comments = relationship('ChangesetComment', cascade='all')
306 306 #extra emails for this user
307 307 user_emails = relationship('UserEmailMap', cascade='all')
308 308
309 309 @hybrid_property
310 310 def email(self):
311 311 return self._email
312 312
313 313 @email.setter
314 314 def email(self, val):
315 315 self._email = val.lower() if val else None
316 316
317 317 @property
318 318 def emails(self):
319 319 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
320 320 return [self.email] + [x.email for x in other]
321 321
322 322 @property
323 323 def full_name(self):
324 324 return '%s %s' % (self.name, self.lastname)
325 325
326 326 @property
327 327 def full_name_or_username(self):
328 328 return ('%s %s' % (self.name, self.lastname)
329 329 if (self.name and self.lastname) else self.username)
330 330
331 331 @property
332 332 def full_contact(self):
333 333 return '%s %s <%s>' % (self.name, self.lastname, self.email)
334 334
335 335 @property
336 336 def short_contact(self):
337 337 return '%s %s' % (self.name, self.lastname)
338 338
339 339 @property
340 340 def is_admin(self):
341 341 return self.admin
342 342
343 343 def __unicode__(self):
344 344 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
345 345 self.user_id, self.username)
346 346
347 347 @classmethod
348 348 def get_by_username(cls, username, case_insensitive=False, cache=False):
349 349 if case_insensitive:
350 350 q = cls.query().filter(cls.username.ilike(username))
351 351 else:
352 352 q = cls.query().filter(cls.username == username)
353 353
354 354 if cache:
355 355 q = q.options(FromCache(
356 356 "sql_cache_short",
357 357 "get_user_%s" % _hash_key(username)
358 358 )
359 359 )
360 360 return q.scalar()
361 361
362 362 @classmethod
363 363 def get_by_api_key(cls, api_key, cache=False):
364 364 q = cls.query().filter(cls.api_key == api_key)
365 365
366 366 if cache:
367 367 q = q.options(FromCache("sql_cache_short",
368 368 "get_api_key_%s" % api_key))
369 369 return q.scalar()
370 370
371 371 @classmethod
372 372 def get_by_email(cls, email, case_insensitive=False, cache=False):
373 373 if case_insensitive:
374 374 q = cls.query().filter(cls.email.ilike(email))
375 375 else:
376 376 q = cls.query().filter(cls.email == email)
377 377
378 378 if cache:
379 379 q = q.options(FromCache("sql_cache_short",
380 380 "get_email_key_%s" % email))
381 381
382 382 ret = q.scalar()
383 383 if ret is None:
384 384 q = UserEmailMap.query()
385 385 # try fetching in alternate email map
386 386 if case_insensitive:
387 387 q = q.filter(UserEmailMap.email.ilike(email))
388 388 else:
389 389 q = q.filter(UserEmailMap.email == email)
390 390 q = q.options(joinedload(UserEmailMap.user))
391 391 if cache:
392 392 q = q.options(FromCache("sql_cache_short",
393 393 "get_email_map_key_%s" % email))
394 394 ret = getattr(q.scalar(), 'user', None)
395 395
396 396 return ret
397 397
398 398 def update_lastlogin(self):
399 399 """Update user lastlogin"""
400 400 self.last_login = datetime.datetime.now()
401 401 Session().add(self)
402 402 log.debug('updated user %s lastlogin' % self.username)
403 403
404 404 def get_api_data(self):
405 405 """
406 406 Common function for generating user related data for API
407 407 """
408 408 user = self
409 409 data = dict(
410 410 user_id=user.user_id,
411 411 username=user.username,
412 412 firstname=user.name,
413 413 lastname=user.lastname,
414 414 email=user.email,
415 415 emails=user.emails,
416 416 api_key=user.api_key,
417 417 active=user.active,
418 418 admin=user.admin,
419 419 ldap_dn=user.ldap_dn,
420 420 last_login=user.last_login,
421 421 )
422 422 return data
423 423
424 424 def __json__(self):
425 425 data = dict(
426 426 full_name=self.full_name,
427 427 full_name_or_username=self.full_name_or_username,
428 428 short_contact=self.short_contact,
429 429 full_contact=self.full_contact
430 430 )
431 431 data.update(self.get_api_data())
432 432 return data
433 433
434 434
435 435 class UserEmailMap(Base, BaseModel):
436 436 __tablename__ = 'user_email_map'
437 437 __table_args__ = (
438 438 Index('uem_email_idx', 'email'),
439 439 UniqueConstraint('email'),
440 440 {'extend_existing': True, 'mysql_engine': 'InnoDB',
441 441 'mysql_charset': 'utf8'}
442 442 )
443 443 __mapper_args__ = {}
444 444
445 445 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
446 446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
447 447 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
448 448
449 449 user = relationship('User', lazy='joined')
450 450
451 451 @validates('_email')
452 452 def validate_email(self, key, email):
453 453 # check if this email is not main one
454 454 main_email = Session().query(User).filter(User.email == email).scalar()
455 455 if main_email is not None:
456 456 raise AttributeError('email %s is present is user table' % email)
457 457 return email
458 458
459 459 @hybrid_property
460 460 def email(self):
461 461 return self._email
462 462
463 463 @email.setter
464 464 def email(self, val):
465 465 self._email = val.lower() if val else None
466 466
467 467
468 468 class UserLog(Base, BaseModel):
469 469 __tablename__ = 'user_logs'
470 470 __table_args__ = (
471 471 {'extend_existing': True, 'mysql_engine': 'InnoDB',
472 472 'mysql_charset': 'utf8'},
473 473 )
474 474 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
475 475 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
476 476 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
477 477 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
478 478 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
479 479 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
480 480 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
481 481
482 482 @property
483 483 def action_as_day(self):
484 484 return datetime.date(*self.action_date.timetuple()[:3])
485 485
486 486 user = relationship('User')
487 487 repository = relationship('Repository', cascade='')
488 488
489 489
490 490 class UsersGroup(Base, BaseModel):
491 491 __tablename__ = 'users_groups'
492 492 __table_args__ = (
493 493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
494 494 'mysql_charset': 'utf8'},
495 495 )
496 496
497 497 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
498 498 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
499 499 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
500 500
501 501 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
502 502 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
503 503 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
504 504
505 505 def __unicode__(self):
506 506 return u'<userGroup(%s)>' % (self.users_group_name)
507 507
508 508 @classmethod
509 509 def get_by_group_name(cls, group_name, cache=False,
510 510 case_insensitive=False):
511 511 if case_insensitive:
512 512 q = cls.query().filter(cls.users_group_name.ilike(group_name))
513 513 else:
514 514 q = cls.query().filter(cls.users_group_name == group_name)
515 515 if cache:
516 516 q = q.options(FromCache(
517 517 "sql_cache_short",
518 518 "get_user_%s" % _hash_key(group_name)
519 519 )
520 520 )
521 521 return q.scalar()
522 522
523 523 @classmethod
524 524 def get(cls, users_group_id, cache=False):
525 525 users_group = cls.query()
526 526 if cache:
527 527 users_group = users_group.options(FromCache("sql_cache_short",
528 528 "get_users_group_%s" % users_group_id))
529 529 return users_group.get(users_group_id)
530 530
531 531 def get_api_data(self):
532 532 users_group = self
533 533
534 534 data = dict(
535 535 users_group_id=users_group.users_group_id,
536 536 group_name=users_group.users_group_name,
537 537 active=users_group.users_group_active,
538 538 )
539 539
540 540 return data
541 541
542 542
543 543 class UsersGroupMember(Base, BaseModel):
544 544 __tablename__ = 'users_groups_members'
545 545 __table_args__ = (
546 546 {'extend_existing': True, 'mysql_engine': 'InnoDB',
547 547 'mysql_charset': 'utf8'},
548 548 )
549 549
550 550 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
551 551 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
552 552 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
553 553
554 554 user = relationship('User', lazy='joined')
555 555 users_group = relationship('UsersGroup')
556 556
557 557 def __init__(self, gr_id='', u_id=''):
558 558 self.users_group_id = gr_id
559 559 self.user_id = u_id
560 560
561 561
562 562 class Repository(Base, BaseModel):
563 563 __tablename__ = 'repositories'
564 564 __table_args__ = (
565 565 UniqueConstraint('repo_name'),
566 566 {'extend_existing': True, 'mysql_engine': 'InnoDB',
567 567 'mysql_charset': 'utf8'},
568 568 )
569 569
570 570 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
571 571 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
572 572 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
573 573 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
574 574 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
575 575 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
576 576 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
577 577 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
578 578 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
579 579 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
580 580 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
581 581
582 582 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
583 583 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
584 584
585 585 user = relationship('User')
586 586 fork = relationship('Repository', remote_side=repo_id)
587 587 group = relationship('RepoGroup')
588 588 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
589 589 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
590 590 stats = relationship('Statistics', cascade='all', uselist=False)
591 591
592 592 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
593 593
594 594 logs = relationship('UserLog')
595 595 comments = relationship('ChangesetComment')
596 596
597 597 def __unicode__(self):
598 598 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
599 599 self.repo_name)
600 600
601 601 @classmethod
602 602 def url_sep(cls):
603 603 return URL_SEP
604 604
605 605 @classmethod
606 606 def get_by_repo_name(cls, repo_name):
607 607 q = Session().query(cls).filter(cls.repo_name == repo_name)
608 608 q = q.options(joinedload(Repository.fork))\
609 609 .options(joinedload(Repository.user))\
610 610 .options(joinedload(Repository.group))
611 611 return q.scalar()
612 612
613 613 @classmethod
614 614 def get_by_full_path(cls, repo_full_path):
615 615 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
616 616 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
617 617
618 618 @classmethod
619 619 def get_repo_forks(cls, repo_id):
620 620 return cls.query().filter(Repository.fork_id == repo_id)
621 621
622 622 @classmethod
623 623 def base_path(cls):
624 624 """
625 625 Returns base path when all repos are stored
626 626
627 627 :param cls:
628 628 """
629 629 q = Session().query(RhodeCodeUi)\
630 630 .filter(RhodeCodeUi.ui_key == cls.url_sep())
631 631 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
632 632 return q.one().ui_value
633 633
634 634 @property
635 635 def forks(self):
636 636 """
637 637 Return forks of this repo
638 638 """
639 639 return Repository.get_repo_forks(self.repo_id)
640 640
641 641 @property
642 642 def parent(self):
643 643 """
644 644 Returns fork parent
645 645 """
646 646 return self.fork
647 647
648 648 @property
649 649 def just_name(self):
650 650 return self.repo_name.split(Repository.url_sep())[-1]
651 651
652 652 @property
653 653 def groups_with_parents(self):
654 654 groups = []
655 655 if self.group is None:
656 656 return groups
657 657
658 658 cur_gr = self.group
659 659 groups.insert(0, cur_gr)
660 660 while 1:
661 661 gr = getattr(cur_gr, 'parent_group', None)
662 662 cur_gr = cur_gr.parent_group
663 663 if gr is None:
664 664 break
665 665 groups.insert(0, gr)
666 666
667 667 return groups
668 668
669 669 @property
670 670 def groups_and_repo(self):
671 671 return self.groups_with_parents, self.just_name
672 672
673 673 @LazyProperty
674 674 def repo_path(self):
675 675 """
676 676 Returns base full path for that repository means where it actually
677 677 exists on a filesystem
678 678 """
679 679 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
680 680 Repository.url_sep())
681 681 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
682 682 return q.one().ui_value
683 683
684 684 @property
685 685 def repo_full_path(self):
686 686 p = [self.repo_path]
687 687 # we need to split the name by / since this is how we store the
688 688 # names in the database, but that eventually needs to be converted
689 689 # into a valid system path
690 690 p += self.repo_name.split(Repository.url_sep())
691 691 return os.path.join(*p)
692 692
693 693 def get_new_name(self, repo_name):
694 694 """
695 695 returns new full repository name based on assigned group and new new
696 696
697 697 :param group_name:
698 698 """
699 699 path_prefix = self.group.full_path_splitted if self.group else []
700 700 return Repository.url_sep().join(path_prefix + [repo_name])
701 701
702 702 @property
703 703 def _ui(self):
704 704 """
705 705 Creates an db based ui object for this repository
706 706 """
707 707 from mercurial import ui
708 708 from mercurial import config
709 709 baseui = ui.ui()
710 710
711 711 #clean the baseui object
712 712 baseui._ocfg = config.config()
713 713 baseui._ucfg = config.config()
714 714 baseui._tcfg = config.config()
715 715
716 716 ret = RhodeCodeUi.query()\
717 717 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
718 718
719 719 hg_ui = ret
720 720 for ui_ in hg_ui:
721 721 if ui_.ui_active:
722 722 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
723 723 ui_.ui_key, ui_.ui_value)
724 724 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
725 725
726 726 return baseui
727 727
728 728 @classmethod
729 729 def inject_ui(cls, repo, extras={}):
730 730 from rhodecode.lib.vcs.backends.hg import MercurialRepository
731 731 from rhodecode.lib.vcs.backends.git import GitRepository
732 732 required = (MercurialRepository, GitRepository)
733 733 if not isinstance(repo, required):
734 734 raise Exception('repo must be instance of %s' % required)
735 735
736 736 # inject ui extra param to log this action via push logger
737 737 for k, v in extras.items():
738 738 repo._repo.ui.setconfig('rhodecode_extras', k, v)
739 739
740 740 @classmethod
741 741 def is_valid(cls, repo_name):
742 742 """
743 743 returns True if given repo name is a valid filesystem repository
744 744
745 745 :param cls:
746 746 :param repo_name:
747 747 """
748 748 from rhodecode.lib.utils import is_valid_repo
749 749
750 750 return is_valid_repo(repo_name, cls.base_path())
751 751
752 752 def get_api_data(self):
753 753 """
754 754 Common function for generating repo api data
755 755
756 756 """
757 757 repo = self
758 758 data = dict(
759 759 repo_id=repo.repo_id,
760 760 repo_name=repo.repo_name,
761 761 repo_type=repo.repo_type,
762 762 clone_uri=repo.clone_uri,
763 763 private=repo.private,
764 764 created_on=repo.created_on,
765 765 description=repo.description,
766 766 landing_rev=repo.landing_rev,
767 767 owner=repo.user.username,
768 768 fork_of=repo.fork.repo_name if repo.fork else None
769 769 )
770 770
771 771 return data
772 772
773 773 #==========================================================================
774 774 # SCM PROPERTIES
775 775 #==========================================================================
776 776
777 777 def get_changeset(self, rev=None):
778 778 return get_changeset_safe(self.scm_instance, rev)
779 779
780 def get_landing_changeset(self):
781 """
782 Returns landing changeset, or if that doesn't exist returns the tip
783 """
784 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
785 return cs
786
780 787 @property
781 788 def tip(self):
782 789 return self.get_changeset('tip')
783 790
784 791 @property
785 792 def author(self):
786 793 return self.tip.author
787 794
788 795 @property
789 796 def last_change(self):
790 797 return self.scm_instance.last_change
791 798
792 799 def get_comments(self, revisions=None):
793 800 """
794 801 Returns comments for this repository grouped by revisions
795 802
796 803 :param revisions: filter query by revisions only
797 804 """
798 805 cmts = ChangesetComment.query()\
799 806 .filter(ChangesetComment.repo == self)
800 807 if revisions:
801 808 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
802 809 grouped = defaultdict(list)
803 810 for cmt in cmts.all():
804 811 grouped[cmt.revision].append(cmt)
805 812 return grouped
806 813
807 814 def statuses(self, revisions=None):
808 815 """
809 816 Returns statuses for this repository
810 817
811 818 :param revisions: list of revisions to get statuses for
812 819 :type revisions: list
813 820 """
814 821
815 822 statuses = ChangesetStatus.query()\
816 823 .filter(ChangesetStatus.repo == self)\
817 824 .filter(ChangesetStatus.version == 0)
818 825 if revisions:
819 826 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
820 827 grouped = {}
821 828
822 829 #maybe we have open new pullrequest without a status ?
823 830 stat = ChangesetStatus.STATUS_UNDER_REVIEW
824 831 status_lbl = ChangesetStatus.get_status_lbl(stat)
825 832 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
826 833 for rev in pr.revisions:
827 834 pr_id = pr.pull_request_id
828 835 pr_repo = pr.other_repo.repo_name
829 836 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
830 837
831 838 for stat in statuses.all():
832 839 pr_id = pr_repo = None
833 840 if stat.pull_request:
834 841 pr_id = stat.pull_request.pull_request_id
835 842 pr_repo = stat.pull_request.other_repo.repo_name
836 843 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
837 844 pr_id, pr_repo]
838 845 return grouped
839 846
840 847 #==========================================================================
841 848 # SCM CACHE INSTANCE
842 849 #==========================================================================
843 850
844 851 @property
845 852 def invalidate(self):
846 853 return CacheInvalidation.invalidate(self.repo_name)
847 854
848 855 def set_invalidate(self):
849 856 """
850 857 set a cache for invalidation for this instance
851 858 """
852 859 CacheInvalidation.set_invalidate(self.repo_name)
853 860
854 861 @LazyProperty
855 862 def scm_instance(self):
856 863 return self.__get_instance()
857 864
858 865 def scm_instance_cached(self, cache_map=None):
859 866 @cache_region('long_term')
860 867 def _c(repo_name):
861 868 return self.__get_instance()
862 869 rn = self.repo_name
863 870 log.debug('Getting cached instance of repo')
864 871
865 872 if cache_map:
866 873 # get using prefilled cache_map
867 874 invalidate_repo = cache_map[self.repo_name]
868 875 if invalidate_repo:
869 876 invalidate_repo = (None if invalidate_repo.cache_active
870 877 else invalidate_repo)
871 878 else:
872 879 # get from invalidate
873 880 invalidate_repo = self.invalidate
874 881
875 882 if invalidate_repo is not None:
876 883 region_invalidate(_c, None, rn)
877 884 # update our cache
878 885 CacheInvalidation.set_valid(invalidate_repo.cache_key)
879 886 return _c(rn)
880 887
881 888 def __get_instance(self):
882 889 repo_full_path = self.repo_full_path
883 890 try:
884 891 alias = get_scm(repo_full_path)[0]
885 892 log.debug('Creating instance of %s repository' % alias)
886 893 backend = get_backend(alias)
887 894 except VCSError:
888 895 log.error(traceback.format_exc())
889 896 log.error('Perhaps this repository is in db and not in '
890 897 'filesystem run rescan repositories with '
891 898 '"destroy old data " option from admin panel')
892 899 return
893 900
894 901 if alias == 'hg':
895 902
896 903 repo = backend(safe_str(repo_full_path), create=False,
897 904 baseui=self._ui)
898 905 # skip hidden web repository
899 906 if repo._get_hidden():
900 907 return
901 908 else:
902 909 repo = backend(repo_full_path, create=False)
903 910
904 911 return repo
905 912
906 913
907 914 class RepoGroup(Base, BaseModel):
908 915 __tablename__ = 'groups'
909 916 __table_args__ = (
910 917 UniqueConstraint('group_name', 'group_parent_id'),
911 918 CheckConstraint('group_id != group_parent_id'),
912 919 {'extend_existing': True, 'mysql_engine': 'InnoDB',
913 920 'mysql_charset': 'utf8'},
914 921 )
915 922 __mapper_args__ = {'order_by': 'group_name'}
916 923
917 924 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
918 925 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
919 926 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
920 927 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
921 928
922 929 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
923 930 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
924 931
925 932 parent_group = relationship('RepoGroup', remote_side=group_id)
926 933
927 934 def __init__(self, group_name='', parent_group=None):
928 935 self.group_name = group_name
929 936 self.parent_group = parent_group
930 937
931 938 def __unicode__(self):
932 939 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
933 940 self.group_name)
934 941
935 942 @classmethod
936 943 def groups_choices(cls):
937 944 from webhelpers.html import literal as _literal
938 945 repo_groups = [('', '')]
939 946 sep = ' &raquo; '
940 947 _name = lambda k: _literal(sep.join(k))
941 948
942 949 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
943 950 for x in cls.query().all()])
944 951
945 952 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
946 953 return repo_groups
947 954
948 955 @classmethod
949 956 def url_sep(cls):
950 957 return URL_SEP
951 958
952 959 @classmethod
953 960 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
954 961 if case_insensitive:
955 962 gr = cls.query()\
956 963 .filter(cls.group_name.ilike(group_name))
957 964 else:
958 965 gr = cls.query()\
959 966 .filter(cls.group_name == group_name)
960 967 if cache:
961 968 gr = gr.options(FromCache(
962 969 "sql_cache_short",
963 970 "get_group_%s" % _hash_key(group_name)
964 971 )
965 972 )
966 973 return gr.scalar()
967 974
968 975 @property
969 976 def parents(self):
970 977 parents_recursion_limit = 5
971 978 groups = []
972 979 if self.parent_group is None:
973 980 return groups
974 981 cur_gr = self.parent_group
975 982 groups.insert(0, cur_gr)
976 983 cnt = 0
977 984 while 1:
978 985 cnt += 1
979 986 gr = getattr(cur_gr, 'parent_group', None)
980 987 cur_gr = cur_gr.parent_group
981 988 if gr is None:
982 989 break
983 990 if cnt == parents_recursion_limit:
984 991 # this will prevent accidental infinit loops
985 992 log.error('group nested more than %s' %
986 993 parents_recursion_limit)
987 994 break
988 995
989 996 groups.insert(0, gr)
990 997 return groups
991 998
992 999 @property
993 1000 def children(self):
994 1001 return RepoGroup.query().filter(RepoGroup.parent_group == self)
995 1002
996 1003 @property
997 1004 def name(self):
998 1005 return self.group_name.split(RepoGroup.url_sep())[-1]
999 1006
1000 1007 @property
1001 1008 def full_path(self):
1002 1009 return self.group_name
1003 1010
1004 1011 @property
1005 1012 def full_path_splitted(self):
1006 1013 return self.group_name.split(RepoGroup.url_sep())
1007 1014
1008 1015 @property
1009 1016 def repositories(self):
1010 1017 return Repository.query()\
1011 1018 .filter(Repository.group == self)\
1012 1019 .order_by(Repository.repo_name)
1013 1020
1014 1021 @property
1015 1022 def repositories_recursive_count(self):
1016 1023 cnt = self.repositories.count()
1017 1024
1018 1025 def children_count(group):
1019 1026 cnt = 0
1020 1027 for child in group.children:
1021 1028 cnt += child.repositories.count()
1022 1029 cnt += children_count(child)
1023 1030 return cnt
1024 1031
1025 1032 return cnt + children_count(self)
1026 1033
1027 1034 def get_new_name(self, group_name):
1028 1035 """
1029 1036 returns new full group name based on parent and new name
1030 1037
1031 1038 :param group_name:
1032 1039 """
1033 1040 path_prefix = (self.parent_group.full_path_splitted if
1034 1041 self.parent_group else [])
1035 1042 return RepoGroup.url_sep().join(path_prefix + [group_name])
1036 1043
1037 1044
1038 1045 class Permission(Base, BaseModel):
1039 1046 __tablename__ = 'permissions'
1040 1047 __table_args__ = (
1041 1048 Index('p_perm_name_idx', 'permission_name'),
1042 1049 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1043 1050 'mysql_charset': 'utf8'},
1044 1051 )
1045 1052 PERMS = [
1046 1053 ('repository.none', _('Repository no access')),
1047 1054 ('repository.read', _('Repository read access')),
1048 1055 ('repository.write', _('Repository write access')),
1049 1056 ('repository.admin', _('Repository admin access')),
1050 1057
1051 1058 ('group.none', _('Repositories Group no access')),
1052 1059 ('group.read', _('Repositories Group read access')),
1053 1060 ('group.write', _('Repositories Group write access')),
1054 1061 ('group.admin', _('Repositories Group admin access')),
1055 1062
1056 1063 ('hg.admin', _('RhodeCode Administrator')),
1057 1064 ('hg.create.none', _('Repository creation disabled')),
1058 1065 ('hg.create.repository', _('Repository creation enabled')),
1059 1066 ('hg.register.none', _('Register disabled')),
1060 1067 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1061 1068 'with manual activation')),
1062 1069
1063 1070 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1064 1071 'with auto activation')),
1065 1072 ]
1066 1073
1067 1074 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1068 1075 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1069 1076 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1070 1077
1071 1078 def __unicode__(self):
1072 1079 return u"<%s('%s:%s')>" % (
1073 1080 self.__class__.__name__, self.permission_id, self.permission_name
1074 1081 )
1075 1082
1076 1083 @classmethod
1077 1084 def get_by_key(cls, key):
1078 1085 return cls.query().filter(cls.permission_name == key).scalar()
1079 1086
1080 1087 @classmethod
1081 1088 def get_default_perms(cls, default_user_id):
1082 1089 q = Session().query(UserRepoToPerm, Repository, cls)\
1083 1090 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1084 1091 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1085 1092 .filter(UserRepoToPerm.user_id == default_user_id)
1086 1093
1087 1094 return q.all()
1088 1095
1089 1096 @classmethod
1090 1097 def get_default_group_perms(cls, default_user_id):
1091 1098 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1092 1099 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1093 1100 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1094 1101 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1095 1102
1096 1103 return q.all()
1097 1104
1098 1105
1099 1106 class UserRepoToPerm(Base, BaseModel):
1100 1107 __tablename__ = 'repo_to_perm'
1101 1108 __table_args__ = (
1102 1109 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1103 1110 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1104 1111 'mysql_charset': 'utf8'}
1105 1112 )
1106 1113 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1107 1114 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1108 1115 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1109 1116 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1110 1117
1111 1118 user = relationship('User')
1112 1119 repository = relationship('Repository')
1113 1120 permission = relationship('Permission')
1114 1121
1115 1122 @classmethod
1116 1123 def create(cls, user, repository, permission):
1117 1124 n = cls()
1118 1125 n.user = user
1119 1126 n.repository = repository
1120 1127 n.permission = permission
1121 1128 Session().add(n)
1122 1129 return n
1123 1130
1124 1131 def __unicode__(self):
1125 1132 return u'<user:%s => %s >' % (self.user, self.repository)
1126 1133
1127 1134
1128 1135 class UserToPerm(Base, BaseModel):
1129 1136 __tablename__ = 'user_to_perm'
1130 1137 __table_args__ = (
1131 1138 UniqueConstraint('user_id', 'permission_id'),
1132 1139 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1133 1140 'mysql_charset': 'utf8'}
1134 1141 )
1135 1142 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1136 1143 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1137 1144 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1138 1145
1139 1146 user = relationship('User')
1140 1147 permission = relationship('Permission', lazy='joined')
1141 1148
1142 1149
1143 1150 class UsersGroupRepoToPerm(Base, BaseModel):
1144 1151 __tablename__ = 'users_group_repo_to_perm'
1145 1152 __table_args__ = (
1146 1153 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1147 1154 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1148 1155 'mysql_charset': 'utf8'}
1149 1156 )
1150 1157 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1151 1158 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1152 1159 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1153 1160 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1154 1161
1155 1162 users_group = relationship('UsersGroup')
1156 1163 permission = relationship('Permission')
1157 1164 repository = relationship('Repository')
1158 1165
1159 1166 @classmethod
1160 1167 def create(cls, users_group, repository, permission):
1161 1168 n = cls()
1162 1169 n.users_group = users_group
1163 1170 n.repository = repository
1164 1171 n.permission = permission
1165 1172 Session().add(n)
1166 1173 return n
1167 1174
1168 1175 def __unicode__(self):
1169 1176 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1170 1177
1171 1178
1172 1179 class UsersGroupToPerm(Base, BaseModel):
1173 1180 __tablename__ = 'users_group_to_perm'
1174 1181 __table_args__ = (
1175 1182 UniqueConstraint('users_group_id', 'permission_id',),
1176 1183 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1177 1184 'mysql_charset': 'utf8'}
1178 1185 )
1179 1186 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1180 1187 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1181 1188 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1182 1189
1183 1190 users_group = relationship('UsersGroup')
1184 1191 permission = relationship('Permission')
1185 1192
1186 1193
1187 1194 class UserRepoGroupToPerm(Base, BaseModel):
1188 1195 __tablename__ = 'user_repo_group_to_perm'
1189 1196 __table_args__ = (
1190 1197 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1191 1198 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1192 1199 'mysql_charset': 'utf8'}
1193 1200 )
1194 1201
1195 1202 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1196 1203 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1197 1204 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1198 1205 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1199 1206
1200 1207 user = relationship('User')
1201 1208 group = relationship('RepoGroup')
1202 1209 permission = relationship('Permission')
1203 1210
1204 1211
1205 1212 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1206 1213 __tablename__ = 'users_group_repo_group_to_perm'
1207 1214 __table_args__ = (
1208 1215 UniqueConstraint('users_group_id', 'group_id'),
1209 1216 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1210 1217 'mysql_charset': 'utf8'}
1211 1218 )
1212 1219
1213 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)
1214 1221 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1215 1222 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1216 1223 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1217 1224
1218 1225 users_group = relationship('UsersGroup')
1219 1226 permission = relationship('Permission')
1220 1227 group = relationship('RepoGroup')
1221 1228
1222 1229
1223 1230 class Statistics(Base, BaseModel):
1224 1231 __tablename__ = 'statistics'
1225 1232 __table_args__ = (
1226 1233 UniqueConstraint('repository_id'),
1227 1234 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1228 1235 'mysql_charset': 'utf8'}
1229 1236 )
1230 1237 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1231 1238 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1232 1239 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1233 1240 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1234 1241 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1235 1242 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1236 1243
1237 1244 repository = relationship('Repository', single_parent=True)
1238 1245
1239 1246
1240 1247 class UserFollowing(Base, BaseModel):
1241 1248 __tablename__ = 'user_followings'
1242 1249 __table_args__ = (
1243 1250 UniqueConstraint('user_id', 'follows_repository_id'),
1244 1251 UniqueConstraint('user_id', 'follows_user_id'),
1245 1252 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1246 1253 'mysql_charset': 'utf8'}
1247 1254 )
1248 1255
1249 1256 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1250 1257 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1251 1258 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1252 1259 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1253 1260 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1254 1261
1255 1262 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1256 1263
1257 1264 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1258 1265 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1259 1266
1260 1267 @classmethod
1261 1268 def get_repo_followers(cls, repo_id):
1262 1269 return cls.query().filter(cls.follows_repo_id == repo_id)
1263 1270
1264 1271
1265 1272 class CacheInvalidation(Base, BaseModel):
1266 1273 __tablename__ = 'cache_invalidation'
1267 1274 __table_args__ = (
1268 1275 UniqueConstraint('cache_key'),
1269 1276 Index('key_idx', 'cache_key'),
1270 1277 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1271 1278 'mysql_charset': 'utf8'},
1272 1279 )
1273 1280 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1274 1281 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1275 1282 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1276 1283 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1277 1284
1278 1285 def __init__(self, cache_key, cache_args=''):
1279 1286 self.cache_key = cache_key
1280 1287 self.cache_args = cache_args
1281 1288 self.cache_active = False
1282 1289
1283 1290 def __unicode__(self):
1284 1291 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1285 1292 self.cache_id, self.cache_key)
1286 1293
1287 1294 @classmethod
1288 1295 def clear_cache(cls):
1289 1296 cls.query().delete()
1290 1297
1291 1298 @classmethod
1292 1299 def _get_key(cls, key):
1293 1300 """
1294 1301 Wrapper for generating a key, together with a prefix
1295 1302
1296 1303 :param key:
1297 1304 """
1298 1305 import rhodecode
1299 1306 prefix = ''
1300 1307 iid = rhodecode.CONFIG.get('instance_id')
1301 1308 if iid:
1302 1309 prefix = iid
1303 1310 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1304 1311
1305 1312 @classmethod
1306 1313 def get_by_key(cls, key):
1307 1314 return cls.query().filter(cls.cache_key == key).scalar()
1308 1315
1309 1316 @classmethod
1310 1317 def _get_or_create_key(cls, key, prefix, org_key):
1311 1318 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1312 1319 if not inv_obj:
1313 1320 try:
1314 1321 inv_obj = CacheInvalidation(key, org_key)
1315 1322 Session().add(inv_obj)
1316 1323 Session().commit()
1317 1324 except Exception:
1318 1325 log.error(traceback.format_exc())
1319 1326 Session().rollback()
1320 1327 return inv_obj
1321 1328
1322 1329 @classmethod
1323 1330 def invalidate(cls, key):
1324 1331 """
1325 1332 Returns Invalidation object if this given key should be invalidated
1326 1333 None otherwise. `cache_active = False` means that this cache
1327 1334 state is not valid and needs to be invalidated
1328 1335
1329 1336 :param key:
1330 1337 """
1331 1338
1332 1339 key, _prefix, _org_key = cls._get_key(key)
1333 1340 inv = cls._get_or_create_key(key, _prefix, _org_key)
1334 1341
1335 1342 if inv and inv.cache_active is False:
1336 1343 return inv
1337 1344
1338 1345 @classmethod
1339 1346 def set_invalidate(cls, key):
1340 1347 """
1341 1348 Mark this Cache key for invalidation
1342 1349
1343 1350 :param key:
1344 1351 """
1345 1352
1346 1353 key, _prefix, _org_key = cls._get_key(key)
1347 1354 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1348 1355 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1349 1356 _org_key))
1350 1357 try:
1351 1358 for inv_obj in inv_objs:
1352 1359 if inv_obj:
1353 1360 inv_obj.cache_active = False
1354 1361
1355 1362 Session().add(inv_obj)
1356 1363 Session().commit()
1357 1364 except Exception:
1358 1365 log.error(traceback.format_exc())
1359 1366 Session().rollback()
1360 1367
1361 1368 @classmethod
1362 1369 def set_valid(cls, key):
1363 1370 """
1364 1371 Mark this cache key as active and currently cached
1365 1372
1366 1373 :param key:
1367 1374 """
1368 1375 inv_obj = cls.get_by_key(key)
1369 1376 inv_obj.cache_active = True
1370 1377 Session().add(inv_obj)
1371 1378 Session().commit()
1372 1379
1373 1380 @classmethod
1374 1381 def get_cache_map(cls):
1375 1382
1376 1383 class cachemapdict(dict):
1377 1384
1378 1385 def __init__(self, *args, **kwargs):
1379 1386 fixkey = kwargs.get('fixkey')
1380 1387 if fixkey:
1381 1388 del kwargs['fixkey']
1382 1389 self.fixkey = fixkey
1383 1390 super(cachemapdict, self).__init__(*args, **kwargs)
1384 1391
1385 1392 def __getattr__(self, name):
1386 1393 key = name
1387 1394 if self.fixkey:
1388 1395 key, _prefix, _org_key = cls._get_key(key)
1389 1396 if key in self.__dict__:
1390 1397 return self.__dict__[key]
1391 1398 else:
1392 1399 return self[key]
1393 1400
1394 1401 def __getitem__(self, key):
1395 1402 if self.fixkey:
1396 1403 key, _prefix, _org_key = cls._get_key(key)
1397 1404 try:
1398 1405 return super(cachemapdict, self).__getitem__(key)
1399 1406 except KeyError:
1400 1407 return
1401 1408
1402 1409 cache_map = cachemapdict(fixkey=True)
1403 1410 for obj in cls.query().all():
1404 1411 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1405 1412 return cache_map
1406 1413
1407 1414
1408 1415 class ChangesetComment(Base, BaseModel):
1409 1416 __tablename__ = 'changeset_comments'
1410 1417 __table_args__ = (
1411 1418 Index('cc_revision_idx', 'revision'),
1412 1419 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1413 1420 'mysql_charset': 'utf8'},
1414 1421 )
1415 1422 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1416 1423 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1417 1424 revision = Column('revision', String(40), nullable=True)
1418 1425 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1419 1426 line_no = Column('line_no', Unicode(10), nullable=True)
1420 1427 f_path = Column('f_path', Unicode(1000), nullable=True)
1421 1428 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1422 1429 text = Column('text', Unicode(25000), nullable=False)
1423 1430 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1424 1431
1425 1432 author = relationship('User', lazy='joined')
1426 1433 repo = relationship('Repository')
1427 1434 status_change = relationship('ChangesetStatus', uselist=False)
1428 1435 pull_request = relationship('PullRequest', lazy='joined')
1429 1436
1430 1437 @classmethod
1431 1438 def get_users(cls, revision=None, pull_request_id=None):
1432 1439 """
1433 1440 Returns user associated with this ChangesetComment. ie those
1434 1441 who actually commented
1435 1442
1436 1443 :param cls:
1437 1444 :param revision:
1438 1445 """
1439 1446 q = Session().query(User)\
1440 1447 .join(ChangesetComment.author)
1441 1448 if revision:
1442 1449 q = q.filter(cls.revision == revision)
1443 1450 elif pull_request_id:
1444 1451 q = q.filter(cls.pull_request_id == pull_request_id)
1445 1452 return q.all()
1446 1453
1447 1454
1448 1455 class ChangesetStatus(Base, BaseModel):
1449 1456 __tablename__ = 'changeset_statuses'
1450 1457 __table_args__ = (
1451 1458 Index('cs_revision_idx', 'revision'),
1452 1459 Index('cs_version_idx', 'version'),
1453 1460 UniqueConstraint('repo_id', 'revision', 'version'),
1454 1461 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1455 1462 'mysql_charset': 'utf8'}
1456 1463 )
1457 1464 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1458 1465 STATUS_APPROVED = 'approved'
1459 1466 STATUS_REJECTED = 'rejected'
1460 1467 STATUS_UNDER_REVIEW = 'under_review'
1461 1468
1462 1469 STATUSES = [
1463 1470 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1464 1471 (STATUS_APPROVED, _("Approved")),
1465 1472 (STATUS_REJECTED, _("Rejected")),
1466 1473 (STATUS_UNDER_REVIEW, _("Under Review")),
1467 1474 ]
1468 1475
1469 1476 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1470 1477 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1471 1478 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1472 1479 revision = Column('revision', String(40), nullable=False)
1473 1480 status = Column('status', String(128), nullable=False, default=DEFAULT)
1474 1481 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1475 1482 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1476 1483 version = Column('version', Integer(), nullable=False, default=0)
1477 1484 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1478 1485
1479 1486 author = relationship('User', lazy='joined')
1480 1487 repo = relationship('Repository')
1481 1488 comment = relationship('ChangesetComment', lazy='joined')
1482 1489 pull_request = relationship('PullRequest', lazy='joined')
1483 1490
1484 1491 def __unicode__(self):
1485 1492 return u"<%s('%s:%s')>" % (
1486 1493 self.__class__.__name__,
1487 1494 self.status, self.author
1488 1495 )
1489 1496
1490 1497 @classmethod
1491 1498 def get_status_lbl(cls, value):
1492 1499 return dict(cls.STATUSES).get(value)
1493 1500
1494 1501 @property
1495 1502 def status_lbl(self):
1496 1503 return ChangesetStatus.get_status_lbl(self.status)
1497 1504
1498 1505
1499 1506 class PullRequest(Base, BaseModel):
1500 1507 __tablename__ = 'pull_requests'
1501 1508 __table_args__ = (
1502 1509 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1503 1510 'mysql_charset': 'utf8'},
1504 1511 )
1505 1512
1506 1513 STATUS_NEW = u'new'
1507 1514 STATUS_OPEN = u'open'
1508 1515 STATUS_CLOSED = u'closed'
1509 1516
1510 1517 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1511 1518 title = Column('title', Unicode(256), nullable=True)
1512 1519 description = Column('description', UnicodeText(10240), nullable=True)
1513 1520 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1514 1521 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1515 1522 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1516 1523 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1517 1524 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1518 1525 org_ref = Column('org_ref', Unicode(256), nullable=False)
1519 1526 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1520 1527 other_ref = Column('other_ref', Unicode(256), nullable=False)
1521 1528
1522 1529 @hybrid_property
1523 1530 def revisions(self):
1524 1531 return self._revisions.split(':')
1525 1532
1526 1533 @revisions.setter
1527 1534 def revisions(self, val):
1528 1535 self._revisions = ':'.join(val)
1529 1536
1530 1537 author = relationship('User', lazy='joined')
1531 1538 reviewers = relationship('PullRequestReviewers')
1532 1539 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1533 1540 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1534 1541
1535 1542 def __json__(self):
1536 1543 return dict(
1537 1544 revisions=self.revisions
1538 1545 )
1539 1546
1540 1547
1541 1548 class PullRequestReviewers(Base, BaseModel):
1542 1549 __tablename__ = 'pull_request_reviewers'
1543 1550 __table_args__ = (
1544 1551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1545 1552 'mysql_charset': 'utf8'},
1546 1553 )
1547 1554
1548 1555 def __init__(self, user=None, pull_request=None):
1549 1556 self.user = user
1550 1557 self.pull_request = pull_request
1551 1558
1552 1559 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1553 1560 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1554 1561 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1555 1562
1556 1563 user = relationship('User')
1557 1564 pull_request = relationship('PullRequest')
1558 1565
1559 1566
1560 1567 class Notification(Base, BaseModel):
1561 1568 __tablename__ = 'notifications'
1562 1569 __table_args__ = (
1563 1570 Index('notification_type_idx', 'type'),
1564 1571 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1565 1572 'mysql_charset': 'utf8'},
1566 1573 )
1567 1574
1568 1575 TYPE_CHANGESET_COMMENT = u'cs_comment'
1569 1576 TYPE_MESSAGE = u'message'
1570 1577 TYPE_MENTION = u'mention'
1571 1578 TYPE_REGISTRATION = u'registration'
1572 1579 TYPE_PULL_REQUEST = u'pull_request'
1573 1580 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1574 1581
1575 1582 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1576 1583 subject = Column('subject', Unicode(512), nullable=True)
1577 1584 body = Column('body', UnicodeText(50000), nullable=True)
1578 1585 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1579 1586 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1580 1587 type_ = Column('type', Unicode(256))
1581 1588
1582 1589 created_by_user = relationship('User')
1583 1590 notifications_to_users = relationship('UserNotification', lazy='joined',
1584 1591 cascade="all, delete, delete-orphan")
1585 1592
1586 1593 @property
1587 1594 def recipients(self):
1588 1595 return [x.user for x in UserNotification.query()\
1589 1596 .filter(UserNotification.notification == self)\
1590 1597 .order_by(UserNotification.user_id.asc()).all()]
1591 1598
1592 1599 @classmethod
1593 1600 def create(cls, created_by, subject, body, recipients, type_=None):
1594 1601 if type_ is None:
1595 1602 type_ = Notification.TYPE_MESSAGE
1596 1603
1597 1604 notification = cls()
1598 1605 notification.created_by_user = created_by
1599 1606 notification.subject = subject
1600 1607 notification.body = body
1601 1608 notification.type_ = type_
1602 1609 notification.created_on = datetime.datetime.now()
1603 1610
1604 1611 for u in recipients:
1605 1612 assoc = UserNotification()
1606 1613 assoc.notification = notification
1607 1614 u.notifications.append(assoc)
1608 1615 Session().add(notification)
1609 1616 return notification
1610 1617
1611 1618 @property
1612 1619 def description(self):
1613 1620 from rhodecode.model.notification import NotificationModel
1614 1621 return NotificationModel().make_description(self)
1615 1622
1616 1623
1617 1624 class UserNotification(Base, BaseModel):
1618 1625 __tablename__ = 'user_to_notification'
1619 1626 __table_args__ = (
1620 1627 UniqueConstraint('user_id', 'notification_id'),
1621 1628 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1622 1629 'mysql_charset': 'utf8'}
1623 1630 )
1624 1631 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1625 1632 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1626 1633 read = Column('read', Boolean, default=False)
1627 1634 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1628 1635
1629 1636 user = relationship('User', lazy="joined")
1630 1637 notification = relationship('Notification', lazy="joined",
1631 1638 order_by=lambda: Notification.created_on.desc(),)
1632 1639
1633 1640 def mark_as_read(self):
1634 1641 self.read = True
1635 1642 Session().add(self)
1636 1643
1637 1644
1638 1645 class DbMigrateVersion(Base, BaseModel):
1639 1646 __tablename__ = 'db_migrate_version'
1640 1647 __table_args__ = (
1641 1648 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1642 1649 'mysql_charset': 'utf8'},
1643 1650 )
1644 1651 repository_id = Column('repository_id', String(250), primary_key=True)
1645 1652 repository_path = Column('repository_path', Text)
1646 1653 version = Column('version', Integer)
@@ -1,703 +1,703 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Summary') % c.repo_name} - ${c.rhodecode_name}
5 5 </%def>
6 6
7 7 <%def name="breadcrumbs_links()">
8 8 ${h.link_to(_(u'Home'),h.url('/'))}
9 9 &raquo;
10 10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
11 11 &raquo;
12 12 ${_('summary')}
13 13 </%def>
14 14
15 15 <%def name="page_nav()">
16 16 ${self.menu('summary')}
17 17 </%def>
18 18
19 19 <%def name="head_extra()">
20 20 <link href="${h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('repo %s ATOM feed') % c.repo_name}" type="application/atom+xml" />
21 21 <link href="${h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('repo %s RSS feed') % c.repo_name}" type="application/rss+xml" />
22 22 </%def>
23 23
24 24 <%def name="main()">
25 25 <%
26 26 summary = lambda n:{False:'summary-short'}.get(n)
27 27 %>
28 28 %if c.show_stats:
29 29 <div class="box box-left">
30 30 %else:
31 31 <div class="box">
32 32 %endif
33 33 <!-- box / title -->
34 34 <div class="title">
35 35 ${self.breadcrumbs()}
36 36 </div>
37 37 <!-- end box / title -->
38 38 <div class="form">
39 39 <div id="summary" class="fields">
40 40
41 41 <div class="field">
42 42 <div class="label-summary">
43 43 <label>${_('Name')}:</label>
44 44 </div>
45 45 <div class="input ${summary(c.show_stats)}">
46 46 <div style="float:right;padding:5px 0px 0px 5px">
47 47 %if c.rhodecode_user.username != 'default':
48 48 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
49 49 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
50 50 %else:
51 51 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
52 52 ${h.link_to(_('ATOM'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
53 53 %endif
54 54 </div>
55 55 %if c.rhodecode_user.username != 'default':
56 56 %if c.following:
57 57 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
58 58 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
59 59 </span>
60 60 %else:
61 61 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
62 62 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
63 63 </span>
64 64 %endif
65 65 %endif:
66 66 ##REPO TYPE
67 67 %if h.is_hg(c.dbrepo):
68 68 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
69 69 %endif
70 70 %if h.is_git(c.dbrepo):
71 71 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
72 72 %endif
73 73
74 74 ##PUBLIC/PRIVATE
75 75 %if c.dbrepo.private:
76 76 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
77 77 %else:
78 78 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
79 79 %endif
80 80
81 81 ##REPO NAME
82 82 <span class="repo_name" title="${_('Non changable ID %s') % c.dbrepo.repo_id}">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
83 83
84 84 ##FORK
85 85 %if c.dbrepo.fork:
86 86 <div style="margin-top:5px;clear:both"">
87 87 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}"><img class="icon" alt="${_('public')}" title="${_('Fork of')} ${c.dbrepo.fork.repo_name}" src="${h.url('/images/icons/arrow_divide.png')}"/>
88 88 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
89 89 </a>
90 90 </div>
91 91 %endif
92 92 ##REMOTE
93 93 %if c.dbrepo.clone_uri:
94 94 <div style="margin-top:5px;clear:both">
95 95 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
96 96 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
97 97 </a>
98 98 </div>
99 99 %endif
100 100 </div>
101 101 </div>
102 102
103 103 <div class="field">
104 104 <div class="label-summary">
105 105 <label>${_('Description')}:</label>
106 106 </div>
107 107 <div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
108 108 </div>
109 109
110 110 <div class="field">
111 111 <div class="label-summary">
112 112 <label>${_('Contact')}:</label>
113 113 </div>
114 114 <div class="input ${summary(c.show_stats)}">
115 115 <div class="gravatar">
116 116 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
117 117 </div>
118 118 ${_('Username')}: ${c.dbrepo.user.username}<br/>
119 119 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
120 120 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
121 121 </div>
122 122 </div>
123 123
124 124 <div class="field">
125 125 <div class="label-summary">
126 126 <label>${_('Clone url')}:</label>
127 127 </div>
128 128 <div class="input ${summary(c.show_stats)}">
129 129 <div style="display:none" id="clone_by_name" class="ui-btn clone">${_('Show by Name')}</div>
130 130 <div id="clone_by_id" class="ui-btn clone">${_('Show by ID')}</div>
131 131 <input style="width:80%;margin-left:105px" type="text" id="clone_url" readonly="readonly" value="${c.clone_repo_url}"/>
132 132 <input style="display:none;width:80%;margin-left:105px" type="text" id="clone_url_id" readonly="readonly" value="${c.clone_repo_url_id}"/>
133 133 </div>
134 134 </div>
135 135
136 136 <div class="field">
137 137 <div class="label-summary">
138 138 <label>${_('Trending files')}:</label>
139 139 </div>
140 140 <div class="input ${summary(c.show_stats)}">
141 141 %if c.show_stats:
142 142 <div id="lang_stats"></div>
143 143 %else:
144 144 ${_('Statistics are disabled for this repository')}
145 145 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
146 146 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
147 147 %endif
148 148 %endif
149 149 </div>
150 150 </div>
151 151
152 152 <div class="field">
153 153 <div class="label-summary">
154 154 <label>${_('Download')}:</label>
155 155 </div>
156 156 <div class="input ${summary(c.show_stats)}">
157 157 %if len(c.rhodecode_repo.revisions) == 0:
158 158 ${_('There are no downloads yet')}
159 159 %elif c.enable_downloads is False:
160 160 ${_('Downloads are disabled for this repository')}
161 161 %if h.HasPermissionAll('hg.admin')('enable downloads on from summary'):
162 162 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
163 163 %endif
164 164 %else:
165 165 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
166 166 <span id="${'zip_link'}">${h.link_to(_('Download as zip'), h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")}</span>
167 167 <span style="vertical-align: bottom">
168 168 <input id="archive_subrepos" type="checkbox" name="subrepos" />
169 169 <label for="archive_subrepos" class="tooltip" title="${h.tooltip(_('Check this to download archive with subrepos'))}" >${_('with subrepos')}</label>
170 170 </span>
171 171 %endif
172 172 </div>
173 173 </div>
174 174 </div>
175 175 </div>
176 176 </div>
177 177
178 178 %if c.show_stats:
179 179 <div class="box box-right" style="min-height:455px">
180 180 <!-- box / title -->
181 181 <div class="title">
182 182 <h5>${_('Commit activity by day / author')}</h5>
183 183 </div>
184 184
185 185 <div class="graph">
186 186 <div style="padding:0 10px 10px 17px;">
187 187 %if c.no_data:
188 188 ${c.no_data_msg}
189 189 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
190 190 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-btn")}
191 191 %endif
192 192 %else:
193 193 ${_('Stats gathered: ')} ${c.stats_percentage}%
194 194 %endif
195 195 </div>
196 196 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
197 197 <div style="clear: both;height: 10px"></div>
198 198 <div id="overview" style="width:450px;height:100px;float:left"></div>
199 199
200 200 <div id="legend_data" style="clear:both;margin-top:10px;">
201 201 <div id="legend_container"></div>
202 202 <div id="legend_choices">
203 203 <table id="legend_choices_tables" class="noborder" style="font-size:smaller;color:#545454"></table>
204 204 </div>
205 205 </div>
206 206 </div>
207 207 </div>
208 208 %endif
209 209
210 210 <div class="box">
211 211 <div class="title">
212 212 <div class="breadcrumbs">
213 213 %if c.repo_changesets:
214 214 ${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}
215 215 %else:
216 216 ${_('Quick start')}
217 217 %endif
218 218 </div>
219 219 </div>
220 220 <div class="table">
221 221 <div id="shortlog_data">
222 222 <%include file='../shortlog/shortlog_data.html'/>
223 223 </div>
224 224 </div>
225 225 </div>
226 226
227 227 %if c.readme_data:
228 228 <div id="readme" class="box header-pos-fix" style="background-color: #FAFAFA">
229 <div id="readme" class="title">
229 <div id="readme" class="title" title="${_("Readme file at revision '%s'" % c.rhodecode_db_repo.landing_rev)}">
230 230 <div class="breadcrumbs"><a href="${h.url('files_home',repo_name=c.repo_name,revision='tip',f_path=c.readme_file)}">${c.readme_file}</a></div>
231 231 </div>
232 232 <div id="readme" class="readme">
233 233 <div class="readme_box">
234 234 ${c.readme_data|n}
235 235 </div>
236 236 </div>
237 237 </div>
238 238 %endif
239 239
240 240 <script type="text/javascript">
241 241 var clone_url = 'clone_url';
242 242 YUE.on(clone_url,'click',function(e){
243 243 if(YUD.hasClass(clone_url,'selected')){
244 244 return
245 245 }
246 246 else{
247 247 YUD.addClass(clone_url,'selected');
248 248 YUD.get(clone_url).select();
249 249 }
250 250 })
251 251
252 252 YUE.on('clone_by_name','click',function(e){
253 253 // show url by name and hide name button
254 254 YUD.setStyle('clone_url','display','');
255 255 YUD.setStyle('clone_by_name','display','none');
256 256
257 257 // hide url by id and show name button
258 258 YUD.setStyle('clone_by_id','display','');
259 259 YUD.setStyle('clone_url_id','display','none');
260 260
261 261 })
262 262 YUE.on('clone_by_id','click',function(e){
263 263
264 264 // show url by id and hide id button
265 265 YUD.setStyle('clone_by_id','display','none');
266 266 YUD.setStyle('clone_url_id','display','');
267 267
268 268 // hide url by name and show id button
269 269 YUD.setStyle('clone_by_name','display','');
270 270 YUD.setStyle('clone_url','display','none');
271 271 })
272 272
273 273
274 274 var tmpl_links = {};
275 275 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
276 276 tmpl_links["${archive['type']}"] = '${h.link_to('__NAME__', h.url('files_archive_home',repo_name=c.dbrepo.repo_name, fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_='archive_icon ui-btn')}';
277 277 %endfor
278 278
279 279 YUE.on(['download_options','archive_subrepos'],'change',function(e){
280 280 var sm = YUD.get('download_options');
281 281 var new_cs = sm.options[sm.selectedIndex];
282 282
283 283 for(k in tmpl_links){
284 284 var s = YUD.get(k+'_link');
285 285 if(s){
286 286 var title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
287 287 title_tmpl= title_tmpl.replace('__CS_NAME__',new_cs.text);
288 288 title_tmpl = title_tmpl.replace('__CS_EXT__',k);
289 289
290 290 var url = tmpl_links[k].replace('__CS__',new_cs.value);
291 291 var subrepos = YUD.get('archive_subrepos').checked;
292 292 url = url.replace('__SUB__',subrepos);
293 293 url = url.replace('__NAME__',title_tmpl);
294 294 s.innerHTML = url
295 295 }
296 296 }
297 297 });
298 298 </script>
299 299 %if c.show_stats:
300 300 <script type="text/javascript">
301 301 var data = ${c.trending_languages|n};
302 302 var total = 0;
303 303 var no_data = true;
304 304 var tbl = document.createElement('table');
305 305 tbl.setAttribute('class','trending_language_tbl');
306 306 var cnt = 0;
307 307 for (var i=0;i<data.length;i++){
308 308 total+= data[i][1].count;
309 309 }
310 310 for (var i=0;i<data.length;i++){
311 311 cnt += 1;
312 312 no_data = false;
313 313
314 314 var hide = cnt>2;
315 315 var tr = document.createElement('tr');
316 316 if (hide){
317 317 tr.setAttribute('style','display:none');
318 318 tr.setAttribute('class','stats_hidden');
319 319 }
320 320 var k = data[i][0];
321 321 var obj = data[i][1];
322 322 var percentage = Math.round((obj.count/total*100),2);
323 323
324 324 var td1 = document.createElement('td');
325 325 td1.width = 150;
326 326 var trending_language_label = document.createElement('div');
327 327 trending_language_label.innerHTML = obj.desc+" ("+k+")";
328 328 td1.appendChild(trending_language_label);
329 329
330 330 var td2 = document.createElement('td');
331 331 td2.setAttribute('style','padding-right:14px !important');
332 332 var trending_language = document.createElement('div');
333 333 var nr_files = obj.count+" ${_('files')}";
334 334
335 335 trending_language.title = k+" "+nr_files;
336 336
337 337 if (percentage>22){
338 338 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
339 339 }
340 340 else{
341 341 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
342 342 }
343 343
344 344 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
345 345 trending_language.style.width=percentage+"%";
346 346 td2.appendChild(trending_language);
347 347
348 348 tr.appendChild(td1);
349 349 tr.appendChild(td2);
350 350 tbl.appendChild(tr);
351 351 if(cnt == 3){
352 352 var show_more = document.createElement('tr');
353 353 var td = document.createElement('td');
354 354 lnk = document.createElement('a');
355 355
356 356 lnk.href='#';
357 357 lnk.innerHTML = "${_('show more')}";
358 358 lnk.id='code_stats_show_more';
359 359 td.appendChild(lnk);
360 360
361 361 show_more.appendChild(td);
362 362 show_more.appendChild(document.createElement('td'));
363 363 tbl.appendChild(show_more);
364 364 }
365 365
366 366 }
367 367
368 368 YUD.get('lang_stats').appendChild(tbl);
369 369 YUE.on('code_stats_show_more','click',function(){
370 370 l = YUD.getElementsByClassName('stats_hidden')
371 371 for (e in l){
372 372 YUD.setStyle(l[e],'display','');
373 373 };
374 374 YUD.setStyle(YUD.get('code_stats_show_more'),
375 375 'display','none');
376 376 });
377 377 </script>
378 378 <script type="text/javascript">
379 379 /**
380 380 * Plots summary graph
381 381 *
382 382 * @class SummaryPlot
383 383 * @param {from} initial from for detailed graph
384 384 * @param {to} initial to for detailed graph
385 385 * @param {dataset}
386 386 * @param {overview_dataset}
387 387 */
388 388 function SummaryPlot(from,to,dataset,overview_dataset) {
389 389 var initial_ranges = {
390 390 "xaxis":{
391 391 "from":from,
392 392 "to":to,
393 393 },
394 394 };
395 395 var dataset = dataset;
396 396 var overview_dataset = [overview_dataset];
397 397 var choiceContainer = YUD.get("legend_choices");
398 398 var choiceContainerTable = YUD.get("legend_choices_tables");
399 399 var plotContainer = YUD.get('commit_history');
400 400 var overviewContainer = YUD.get('overview');
401 401
402 402 var plot_options = {
403 403 bars: {show:true,align:'center',lineWidth:4},
404 404 legend: {show:true, container:"legend_container"},
405 405 points: {show:true,radius:0,fill:false},
406 406 yaxis: {tickDecimals:0,},
407 407 xaxis: {
408 408 mode: "time",
409 409 timeformat: "%d/%m",
410 410 min:from,
411 411 max:to,
412 412 },
413 413 grid: {
414 414 hoverable: true,
415 415 clickable: true,
416 416 autoHighlight:true,
417 417 color: "#999"
418 418 },
419 419 //selection: {mode: "x"}
420 420 };
421 421 var overview_options = {
422 422 legend:{show:false},
423 423 bars: {show:true,barWidth: 2,},
424 424 shadowSize: 0,
425 425 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
426 426 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
427 427 grid: {color: "#999",},
428 428 selection: {mode: "x"}
429 429 };
430 430
431 431 /**
432 432 *get dummy data needed in few places
433 433 */
434 434 function getDummyData(label){
435 435 return {"label":label,
436 436 "data":[{"time":0,
437 437 "commits":0,
438 438 "added":0,
439 439 "changed":0,
440 440 "removed":0,
441 441 }],
442 442 "schema":["commits"],
443 443 "color":'#ffffff',
444 444 }
445 445 }
446 446
447 447 /**
448 448 * generate checkboxes accordindly to data
449 449 * @param keys
450 450 * @returns
451 451 */
452 452 function generateCheckboxes(data) {
453 453 //append checkboxes
454 454 var i = 0;
455 455 choiceContainerTable.innerHTML = '';
456 456 for(var pos in data) {
457 457
458 458 data[pos].color = i;
459 459 i++;
460 460 if(data[pos].label != ''){
461 461 choiceContainerTable.innerHTML +=
462 462 '<tr><td><input type="checkbox" id="id_user_{0}" name="{0}" checked="checked" /> \
463 463 <label for="id_user_{0}">{0}</label></td></tr>'.format(data[pos].label);
464 464 }
465 465 }
466 466 }
467 467
468 468 /**
469 469 * ToolTip show
470 470 */
471 471 function showTooltip(x, y, contents) {
472 472 var div=document.getElementById('tooltip');
473 473 if(!div) {
474 474 div = document.createElement('div');
475 475 div.id="tooltip";
476 476 div.style.position="absolute";
477 477 div.style.border='1px solid #fdd';
478 478 div.style.padding='2px';
479 479 div.style.backgroundColor='#fee';
480 480 document.body.appendChild(div);
481 481 }
482 482 YUD.setStyle(div, 'opacity', 0);
483 483 div.innerHTML = contents;
484 484 div.style.top=(y + 5) + "px";
485 485 div.style.left=(x + 5) + "px";
486 486
487 487 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
488 488 anim.animate();
489 489 }
490 490
491 491 /**
492 492 * This function will detect if selected period has some changesets
493 493 for this user if it does this data is then pushed for displaying
494 494 Additionally it will only display users that are selected by the checkbox
495 495 */
496 496 function getDataAccordingToRanges(ranges) {
497 497
498 498 var data = [];
499 499 var new_dataset = {};
500 500 var keys = [];
501 501 var max_commits = 0;
502 502 for(var key in dataset){
503 503
504 504 for(var ds in dataset[key].data){
505 505 commit_data = dataset[key].data[ds];
506 506 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
507 507
508 508 if(new_dataset[key] === undefined){
509 509 new_dataset[key] = {data:[],schema:["commits"],label:key};
510 510 }
511 511 new_dataset[key].data.push(commit_data);
512 512 }
513 513 }
514 514 if (new_dataset[key] !== undefined){
515 515 data.push(new_dataset[key]);
516 516 }
517 517 }
518 518
519 519 if (data.length > 0){
520 520 return data;
521 521 }
522 522 else{
523 523 //just return dummy data for graph to plot itself
524 524 return [getDummyData('')];
525 525 }
526 526 }
527 527
528 528 /**
529 529 * redraw using new checkbox data
530 530 */
531 531 function plotchoiced(e,args){
532 532 var cur_data = args[0];
533 533 var cur_ranges = args[1];
534 534
535 535 var new_data = [];
536 536 var inputs = choiceContainer.getElementsByTagName("input");
537 537
538 538 //show only checked labels
539 539 for(var i=0; i<inputs.length; i++) {
540 540 var checkbox_key = inputs[i].name;
541 541
542 542 if(inputs[i].checked){
543 543 for(var d in cur_data){
544 544 if(cur_data[d].label == checkbox_key){
545 545 new_data.push(cur_data[d]);
546 546 }
547 547 }
548 548 }
549 549 else{
550 550 //push dummy data to not hide the label
551 551 new_data.push(getDummyData(checkbox_key));
552 552 }
553 553 }
554 554
555 555 var new_options = YAHOO.lang.merge(plot_options, {
556 556 xaxis: {
557 557 min: cur_ranges.xaxis.from,
558 558 max: cur_ranges.xaxis.to,
559 559 mode:"time",
560 560 timeformat: "%d/%m",
561 561 },
562 562 });
563 563 if (!new_data){
564 564 new_data = [[0,1]];
565 565 }
566 566 // do the zooming
567 567 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
568 568
569 569 plot.subscribe("plotselected", plotselected);
570 570
571 571 //resubscribe plothover
572 572 plot.subscribe("plothover", plothover);
573 573
574 574 // don't fire event on the overview to prevent eternal loop
575 575 overview.setSelection(cur_ranges, true);
576 576
577 577 }
578 578
579 579 /**
580 580 * plot only selected items from overview
581 581 * @param ranges
582 582 * @returns
583 583 */
584 584 function plotselected(ranges,cur_data) {
585 585 //updates the data for new plot
586 586 var data = getDataAccordingToRanges(ranges);
587 587 generateCheckboxes(data);
588 588
589 589 var new_options = YAHOO.lang.merge(plot_options, {
590 590 xaxis: {
591 591 min: ranges.xaxis.from,
592 592 max: ranges.xaxis.to,
593 593 mode:"time",
594 594 timeformat: "%d/%m",
595 595 },
596 596 });
597 597 // do the zooming
598 598 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
599 599
600 600 plot.subscribe("plotselected", plotselected);
601 601
602 602 //resubscribe plothover
603 603 plot.subscribe("plothover", plothover);
604 604
605 605 // don't fire event on the overview to prevent eternal loop
606 606 overview.setSelection(ranges, true);
607 607
608 608 //resubscribe choiced
609 609 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
610 610 }
611 611
612 612 var previousPoint = null;
613 613
614 614 function plothover(o) {
615 615 var pos = o.pos;
616 616 var item = o.item;
617 617
618 618 //YUD.get("x").innerHTML = pos.x.toFixed(2);
619 619 //YUD.get("y").innerHTML = pos.y.toFixed(2);
620 620 if (item) {
621 621 if (previousPoint != item.datapoint) {
622 622 previousPoint = item.datapoint;
623 623
624 624 var tooltip = YUD.get("tooltip");
625 625 if(tooltip) {
626 626 tooltip.parentNode.removeChild(tooltip);
627 627 }
628 628 var x = item.datapoint.x.toFixed(2);
629 629 var y = item.datapoint.y.toFixed(2);
630 630
631 631 if (!item.series.label){
632 632 item.series.label = 'commits';
633 633 }
634 634 var d = new Date(x*1000);
635 635 var fd = d.toDateString()
636 636 var nr_commits = parseInt(y);
637 637
638 638 var cur_data = dataset[item.series.label].data[item.dataIndex];
639 639 var added = cur_data.added;
640 640 var changed = cur_data.changed;
641 641 var removed = cur_data.removed;
642 642
643 643 var nr_commits_suffix = " ${_('commits')} ";
644 644 var added_suffix = " ${_('files added')} ";
645 645 var changed_suffix = " ${_('files changed')} ";
646 646 var removed_suffix = " ${_('files removed')} ";
647 647
648 648
649 649 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
650 650 if(added==1){added_suffix=" ${_('file added')} ";}
651 651 if(changed==1){changed_suffix=" ${_('file changed')} ";}
652 652 if(removed==1){removed_suffix=" ${_('file removed')} ";}
653 653
654 654 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
655 655 +'<br/>'+
656 656 nr_commits + nr_commits_suffix+'<br/>'+
657 657 added + added_suffix +'<br/>'+
658 658 changed + changed_suffix + '<br/>'+
659 659 removed + removed_suffix + '<br/>');
660 660 }
661 661 }
662 662 else {
663 663 var tooltip = YUD.get("tooltip");
664 664
665 665 if(tooltip) {
666 666 tooltip.parentNode.removeChild(tooltip);
667 667 }
668 668 previousPoint = null;
669 669 }
670 670 }
671 671
672 672 /**
673 673 * MAIN EXECUTION
674 674 */
675 675
676 676 var data = getDataAccordingToRanges(initial_ranges);
677 677 generateCheckboxes(data);
678 678
679 679 //main plot
680 680 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
681 681
682 682 //overview
683 683 var overview = YAHOO.widget.Flot(overviewContainer,
684 684 overview_dataset, overview_options);
685 685
686 686 //show initial selection on overview
687 687 overview.setSelection(initial_ranges);
688 688
689 689 plot.subscribe("plotselected", plotselected);
690 690 plot.subscribe("plothover", plothover)
691 691
692 692 overview.subscribe("plotselected", function (ranges) {
693 693 plot.setSelection(ranges);
694 694 });
695 695
696 696 // user choices on overview
697 697 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
698 698 }
699 699 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
700 700 </script>
701 701 %endif
702 702
703 703 </%def>
General Comments 0
You need to be logged in to leave comments. Login now