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