##// END OF EJS Templates
implements #193 journal stores information about deleting of repos...
marcink -
r1747:88047154 beta
parent child Browse files
Show More
@@ -1,407 +1,410
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.celerylib.tasks
3 rhodecode.lib.celerylib.tasks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode task modules, containing all task that suppose to be run
6 RhodeCode task modules, containing all task that suppose to be run
7 by celery daemon
7 by celery daemon
8
8
9 :created_on: Oct 6, 2010
9 :created_on: Oct 6, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from celery.decorators import task
26 from celery.decorators import task
27
27
28 import os
28 import os
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import join as jn
31 from os.path import join as jn
32
32
33 from time import mktime
33 from time import mktime
34 from operator import itemgetter
34 from operator import itemgetter
35 from string import lower
35 from string import lower
36
36
37 from pylons import config, url
37 from pylons import config, url
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from vcs import get_backend
40 from vcs import get_backend
41
41
42 from rhodecode import CELERY_ON
42 from rhodecode import CELERY_ON
43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
43 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
44 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
44 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
45 __get_lockkey, LockHeld, DaemonLock
45 __get_lockkey, LockHeld, DaemonLock
46 from rhodecode.lib.helpers import person
46 from rhodecode.lib.helpers import person
47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
47 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
48 from rhodecode.lib.utils import add_cache, action_logger
48 from rhodecode.lib.utils import add_cache, action_logger
49 from rhodecode.lib.compat import json, OrderedDict
49 from rhodecode.lib.compat import json, OrderedDict
50
50
51 from rhodecode.model import init_model
51 from rhodecode.model import init_model
52 from rhodecode.model import meta
52 from rhodecode.model import meta
53 from rhodecode.model.db import Statistics, Repository, User
53 from rhodecode.model.db import Statistics, Repository, User
54
54
55 from sqlalchemy import engine_from_config
55 from sqlalchemy import engine_from_config
56
56
57 add_cache(config)
57 add_cache(config)
58
58
59 __all__ = ['whoosh_index', 'get_commits_stats',
59 __all__ = ['whoosh_index', 'get_commits_stats',
60 'reset_user_password', 'send_email']
60 'reset_user_password', 'send_email']
61
61
62
62
63 def get_session():
63 def get_session():
64 if CELERY_ON:
64 if CELERY_ON:
65 engine = engine_from_config(config, 'sqlalchemy.db1.')
65 engine = engine_from_config(config, 'sqlalchemy.db1.')
66 init_model(engine)
66 init_model(engine)
67 sa = meta.Session()
67 sa = meta.Session()
68 return sa
68 return sa
69
69
70 def get_logger(cls):
70 def get_logger(cls):
71 if CELERY_ON:
71 if CELERY_ON:
72 try:
72 try:
73 log = cls.get_logger()
73 log = cls.get_logger()
74 except:
74 except:
75 log = logging.getLogger(__name__)
75 log = logging.getLogger(__name__)
76 else:
76 else:
77 log = logging.getLogger(__name__)
77 log = logging.getLogger(__name__)
78
78
79 return log
79 return log
80
80
81 @task(ignore_result=True)
81 @task(ignore_result=True)
82 @locked_task
82 @locked_task
83 def whoosh_index(repo_location, full_index):
83 def whoosh_index(repo_location, full_index):
84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
85
85
86 #log = whoosh_index.get_logger()
86 #log = whoosh_index.get_logger()
87
87
88 index_location = config['index_dir']
88 index_location = config['index_dir']
89 WhooshIndexingDaemon(index_location=index_location,
89 WhooshIndexingDaemon(index_location=index_location,
90 repo_location=repo_location, sa=get_session())\
90 repo_location=repo_location, sa=get_session())\
91 .run(full_index=full_index)
91 .run(full_index=full_index)
92
92
93
93
94 @task(ignore_result=True)
94 @task(ignore_result=True)
95 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
95 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
96 log = get_logger(get_commits_stats)
96 log = get_logger(get_commits_stats)
97
97
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
99 ts_max_y)
99 ts_max_y)
100 lockkey_path = config['here']
100 lockkey_path = config['here']
101
101
102 log.info('running task with lockkey %s', lockkey)
102 log.info('running task with lockkey %s', lockkey)
103 try:
103 try:
104 sa = get_session()
104 sa = get_session()
105 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
105 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
106
106
107 # for js data compatibilty cleans the key for person from '
107 # for js data compatibilty cleans the key for person from '
108 akc = lambda k: person(k).replace('"', "")
108 akc = lambda k: person(k).replace('"', "")
109
109
110 co_day_auth_aggr = {}
110 co_day_auth_aggr = {}
111 commits_by_day_aggregate = {}
111 commits_by_day_aggregate = {}
112 repo = Repository.get_by_repo_name(repo_name).scm_instance
112 repo = Repository.get_by_repo_name(repo_name).scm_instance
113 repo_size = len(repo.revisions)
113 repo_size = len(repo.revisions)
114 #return if repo have no revisions
114 #return if repo have no revisions
115 if repo_size < 1:
115 if repo_size < 1:
116 lock.release()
116 lock.release()
117 return True
117 return True
118
118
119 skip_date_limit = True
119 skip_date_limit = True
120 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
120 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
121 last_rev = 0
121 last_rev = 0
122 last_cs = None
122 last_cs = None
123 timegetter = itemgetter('time')
123 timegetter = itemgetter('time')
124
124
125 dbrepo = sa.query(Repository)\
125 dbrepo = sa.query(Repository)\
126 .filter(Repository.repo_name == repo_name).scalar()
126 .filter(Repository.repo_name == repo_name).scalar()
127 cur_stats = sa.query(Statistics)\
127 cur_stats = sa.query(Statistics)\
128 .filter(Statistics.repository == dbrepo).scalar()
128 .filter(Statistics.repository == dbrepo).scalar()
129
129
130 if cur_stats is not None:
130 if cur_stats is not None:
131 last_rev = cur_stats.stat_on_revision
131 last_rev = cur_stats.stat_on_revision
132
132
133 if last_rev == repo.get_changeset().revision and repo_size > 1:
133 if last_rev == repo.get_changeset().revision and repo_size > 1:
134 # pass silently without any work if we're not on first revision or
134 # pass silently without any work if we're not on first revision or
135 # current state of parsing revision(from db marker) is the
135 # current state of parsing revision(from db marker) is the
136 # last revision
136 # last revision
137 lock.release()
137 lock.release()
138 return True
138 return True
139
139
140 if cur_stats:
140 if cur_stats:
141 commits_by_day_aggregate = OrderedDict(json.loads(
141 commits_by_day_aggregate = OrderedDict(json.loads(
142 cur_stats.commit_activity_combined))
142 cur_stats.commit_activity_combined))
143 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
143 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
144
144
145 log.debug('starting parsing %s', parse_limit)
145 log.debug('starting parsing %s', parse_limit)
146 lmktime = mktime
146 lmktime = mktime
147
147
148 last_rev = last_rev + 1 if last_rev > 0 else last_rev
148 last_rev = last_rev + 1 if last_rev > 0 else last_rev
149
149
150 for cs in repo[last_rev:last_rev + parse_limit]:
150 for cs in repo[last_rev:last_rev + parse_limit]:
151 last_cs = cs # remember last parsed changeset
151 last_cs = cs # remember last parsed changeset
152 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
152 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
153 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
153 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
154
154
155 if akc(cs.author) in co_day_auth_aggr:
155 if akc(cs.author) in co_day_auth_aggr:
156 try:
156 try:
157 l = [timegetter(x) for x in
157 l = [timegetter(x) for x in
158 co_day_auth_aggr[akc(cs.author)]['data']]
158 co_day_auth_aggr[akc(cs.author)]['data']]
159 time_pos = l.index(k)
159 time_pos = l.index(k)
160 except ValueError:
160 except ValueError:
161 time_pos = False
161 time_pos = False
162
162
163 if time_pos >= 0 and time_pos is not False:
163 if time_pos >= 0 and time_pos is not False:
164
164
165 datadict = \
165 datadict = \
166 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
166 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
167
167
168 datadict["commits"] += 1
168 datadict["commits"] += 1
169 datadict["added"] += len(cs.added)
169 datadict["added"] += len(cs.added)
170 datadict["changed"] += len(cs.changed)
170 datadict["changed"] += len(cs.changed)
171 datadict["removed"] += len(cs.removed)
171 datadict["removed"] += len(cs.removed)
172
172
173 else:
173 else:
174 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
174 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
175
175
176 datadict = {"time": k,
176 datadict = {"time": k,
177 "commits": 1,
177 "commits": 1,
178 "added": len(cs.added),
178 "added": len(cs.added),
179 "changed": len(cs.changed),
179 "changed": len(cs.changed),
180 "removed": len(cs.removed),
180 "removed": len(cs.removed),
181 }
181 }
182 co_day_auth_aggr[akc(cs.author)]['data']\
182 co_day_auth_aggr[akc(cs.author)]['data']\
183 .append(datadict)
183 .append(datadict)
184
184
185 else:
185 else:
186 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
186 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
187 co_day_auth_aggr[akc(cs.author)] = {
187 co_day_auth_aggr[akc(cs.author)] = {
188 "label": akc(cs.author),
188 "label": akc(cs.author),
189 "data": [{"time":k,
189 "data": [{"time":k,
190 "commits":1,
190 "commits":1,
191 "added":len(cs.added),
191 "added":len(cs.added),
192 "changed":len(cs.changed),
192 "changed":len(cs.changed),
193 "removed":len(cs.removed),
193 "removed":len(cs.removed),
194 }],
194 }],
195 "schema": ["commits"],
195 "schema": ["commits"],
196 }
196 }
197
197
198 #gather all data by day
198 #gather all data by day
199 if k in commits_by_day_aggregate:
199 if k in commits_by_day_aggregate:
200 commits_by_day_aggregate[k] += 1
200 commits_by_day_aggregate[k] += 1
201 else:
201 else:
202 commits_by_day_aggregate[k] = 1
202 commits_by_day_aggregate[k] = 1
203
203
204 overview_data = sorted(commits_by_day_aggregate.items(),
204 overview_data = sorted(commits_by_day_aggregate.items(),
205 key=itemgetter(0))
205 key=itemgetter(0))
206
206
207 if not co_day_auth_aggr:
207 if not co_day_auth_aggr:
208 co_day_auth_aggr[akc(repo.contact)] = {
208 co_day_auth_aggr[akc(repo.contact)] = {
209 "label": akc(repo.contact),
209 "label": akc(repo.contact),
210 "data": [0, 1],
210 "data": [0, 1],
211 "schema": ["commits"],
211 "schema": ["commits"],
212 }
212 }
213
213
214 stats = cur_stats if cur_stats else Statistics()
214 stats = cur_stats if cur_stats else Statistics()
215 stats.commit_activity = json.dumps(co_day_auth_aggr)
215 stats.commit_activity = json.dumps(co_day_auth_aggr)
216 stats.commit_activity_combined = json.dumps(overview_data)
216 stats.commit_activity_combined = json.dumps(overview_data)
217
217
218 log.debug('last revison %s', last_rev)
218 log.debug('last revison %s', last_rev)
219 leftovers = len(repo.revisions[last_rev:])
219 leftovers = len(repo.revisions[last_rev:])
220 log.debug('revisions to parse %s', leftovers)
220 log.debug('revisions to parse %s', leftovers)
221
221
222 if last_rev == 0 or leftovers < parse_limit:
222 if last_rev == 0 or leftovers < parse_limit:
223 log.debug('getting code trending stats')
223 log.debug('getting code trending stats')
224 stats.languages = json.dumps(__get_codes_stats(repo_name))
224 stats.languages = json.dumps(__get_codes_stats(repo_name))
225
225
226 try:
226 try:
227 stats.repository = dbrepo
227 stats.repository = dbrepo
228 stats.stat_on_revision = last_cs.revision if last_cs else 0
228 stats.stat_on_revision = last_cs.revision if last_cs else 0
229 sa.add(stats)
229 sa.add(stats)
230 sa.commit()
230 sa.commit()
231 except:
231 except:
232 log.error(traceback.format_exc())
232 log.error(traceback.format_exc())
233 sa.rollback()
233 sa.rollback()
234 lock.release()
234 lock.release()
235 return False
235 return False
236
236
237 #final release
237 #final release
238 lock.release()
238 lock.release()
239
239
240 #execute another task if celery is enabled
240 #execute another task if celery is enabled
241 if len(repo.revisions) > 1 and CELERY_ON:
241 if len(repo.revisions) > 1 and CELERY_ON:
242 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
242 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
243 return True
243 return True
244 except LockHeld:
244 except LockHeld:
245 log.info('LockHeld')
245 log.info('LockHeld')
246 return 'Task with key %s already running' % lockkey
246 return 'Task with key %s already running' % lockkey
247
247
248 @task(ignore_result=True)
248 @task(ignore_result=True)
249 def send_password_link(user_email):
249 def send_password_link(user_email):
250 from rhodecode.model.notification import EmailNotificationModel
250 from rhodecode.model.notification import EmailNotificationModel
251
251
252 log = get_logger(send_password_link)
252 log = get_logger(send_password_link)
253
253
254 try:
254 try:
255 sa = get_session()
255 sa = get_session()
256 user = User.get_by_email(user_email)
256 user = User.get_by_email(user_email)
257 if user:
257 if user:
258 log.debug('password reset user found %s' % user)
258 log.debug('password reset user found %s' % user)
259 link = url('reset_password_confirmation', key=user.api_key,
259 link = url('reset_password_confirmation', key=user.api_key,
260 qualified=True)
260 qualified=True)
261 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
261 reg_type = EmailNotificationModel.TYPE_PASSWORD_RESET
262 body = EmailNotificationModel().get_email_tmpl(reg_type,
262 body = EmailNotificationModel().get_email_tmpl(reg_type,
263 **{'user':user.short_contact,
263 **{'user':user.short_contact,
264 'reset_url':link})
264 'reset_url':link})
265 log.debug('sending email')
265 log.debug('sending email')
266 run_task(send_email, user_email,
266 run_task(send_email, user_email,
267 _("password reset link"), body)
267 _("password reset link"), body)
268 log.info('send new password mail to %s', user_email)
268 log.info('send new password mail to %s', user_email)
269 else:
269 else:
270 log.debug("password reset email %s not found" % user_email)
270 log.debug("password reset email %s not found" % user_email)
271 except:
271 except:
272 log.error(traceback.format_exc())
272 log.error(traceback.format_exc())
273 return False
273 return False
274
274
275 return True
275 return True
276
276
277 @task(ignore_result=True)
277 @task(ignore_result=True)
278 def reset_user_password(user_email):
278 def reset_user_password(user_email):
279 from rhodecode.lib import auth
279 from rhodecode.lib import auth
280
280
281 log = get_logger(reset_user_password)
281 log = get_logger(reset_user_password)
282
282
283 try:
283 try:
284 try:
284 try:
285 sa = get_session()
285 sa = get_session()
286 user = User.get_by_email(user_email)
286 user = User.get_by_email(user_email)
287 new_passwd = auth.PasswordGenerator().gen_password(8,
287 new_passwd = auth.PasswordGenerator().gen_password(8,
288 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
288 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
289 if user:
289 if user:
290 user.password = auth.get_crypt_password(new_passwd)
290 user.password = auth.get_crypt_password(new_passwd)
291 user.api_key = auth.generate_api_key(user.username)
291 user.api_key = auth.generate_api_key(user.username)
292 sa.add(user)
292 sa.add(user)
293 sa.commit()
293 sa.commit()
294 log.info('change password for %s', user_email)
294 log.info('change password for %s', user_email)
295 if new_passwd is None:
295 if new_passwd is None:
296 raise Exception('unable to generate new password')
296 raise Exception('unable to generate new password')
297 except:
297 except:
298 log.error(traceback.format_exc())
298 log.error(traceback.format_exc())
299 sa.rollback()
299 sa.rollback()
300
300
301 run_task(send_email, user_email,
301 run_task(send_email, user_email,
302 'Your new password',
302 'Your new password',
303 'Your new RhodeCode password:%s' % (new_passwd))
303 'Your new RhodeCode password:%s' % (new_passwd))
304 log.info('send new password mail to %s', user_email)
304 log.info('send new password mail to %s', user_email)
305
305
306 except:
306 except:
307 log.error('Failed to update user password')
307 log.error('Failed to update user password')
308 log.error(traceback.format_exc())
308 log.error(traceback.format_exc())
309
309
310 return True
310 return True
311
311
312
312
313 @task(ignore_result=True)
313 @task(ignore_result=True)
314 def send_email(recipients, subject, body, html_body=''):
314 def send_email(recipients, subject, body, html_body=''):
315 """
315 """
316 Sends an email with defined parameters from the .ini files.
316 Sends an email with defined parameters from the .ini files.
317
317
318 :param recipients: list of recipients, it this is empty the defined email
318 :param recipients: list of recipients, it this is empty the defined email
319 address from field 'email_to' is used instead
319 address from field 'email_to' is used instead
320 :param subject: subject of the mail
320 :param subject: subject of the mail
321 :param body: body of the mail
321 :param body: body of the mail
322 :param html_body: html version of body
322 :param html_body: html version of body
323 """
323 """
324 log = get_logger(send_email)
324 log = get_logger(send_email)
325 sa = get_session()
325 sa = get_session()
326 email_config = config
326 email_config = config
327 subject = "%s %s" % (email_config.get('email_prefix'), subject)
327 subject = "%s %s" % (email_config.get('email_prefix'), subject)
328 if not recipients:
328 if not recipients:
329 # if recipients are not defined we send to email_config + all admins
329 # if recipients are not defined we send to email_config + all admins
330 admins = [u.email for u in User.query()
330 admins = [u.email for u in User.query()
331 .filter(User.admin == True).all()]
331 .filter(User.admin == True).all()]
332 recipients = [email_config.get('email_to')] + admins
332 recipients = [email_config.get('email_to')] + admins
333
333
334 mail_from = email_config.get('app_email_from', 'RhodeCode')
334 mail_from = email_config.get('app_email_from', 'RhodeCode')
335 user = email_config.get('smtp_username')
335 user = email_config.get('smtp_username')
336 passwd = email_config.get('smtp_password')
336 passwd = email_config.get('smtp_password')
337 mail_server = email_config.get('smtp_server')
337 mail_server = email_config.get('smtp_server')
338 mail_port = email_config.get('smtp_port')
338 mail_port = email_config.get('smtp_port')
339 tls = str2bool(email_config.get('smtp_use_tls'))
339 tls = str2bool(email_config.get('smtp_use_tls'))
340 ssl = str2bool(email_config.get('smtp_use_ssl'))
340 ssl = str2bool(email_config.get('smtp_use_ssl'))
341 debug = str2bool(config.get('debug'))
341 debug = str2bool(config.get('debug'))
342 smtp_auth = email_config.get('smtp_auth')
342 smtp_auth = email_config.get('smtp_auth')
343
343
344 try:
344 try:
345 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
345 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
346 mail_port, ssl, tls, debug=debug)
346 mail_port, ssl, tls, debug=debug)
347 m.send(recipients, subject, body, html_body)
347 m.send(recipients, subject, body, html_body)
348 except:
348 except:
349 log.error('Mail sending failed')
349 log.error('Mail sending failed')
350 log.error(traceback.format_exc())
350 log.error(traceback.format_exc())
351 return False
351 return False
352 return True
352 return True
353
353
354
354
355 @task(ignore_result=True)
355 @task(ignore_result=True)
356 def create_repo_fork(form_data, cur_user):
356 def create_repo_fork(form_data, cur_user):
357 """
357 """
358 Creates a fork of repository using interval VCS methods
358 Creates a fork of repository using interval VCS methods
359
359
360 :param form_data:
360 :param form_data:
361 :param cur_user:
361 :param cur_user:
362 """
362 """
363 from rhodecode.model.repo import RepoModel
363 from rhodecode.model.repo import RepoModel
364
364
365 log = get_logger(create_repo_fork)
365 log = get_logger(create_repo_fork)
366
366
367 Session = get_session()
367 Session = get_session()
368 base_path = Repository.base_path()
368 base_path = Repository.base_path()
369
369
370 RepoModel(Session).create(form_data, cur_user, just_db=True, fork=True)
370 RepoModel(Session).create(form_data, cur_user, just_db=True, fork=True)
371
371
372 alias = form_data['repo_type']
372 alias = form_data['repo_type']
373 org_repo_name = form_data['org_path']
373 org_repo_name = form_data['org_path']
374 fork_name = form_data['repo_name_full']
374 fork_name = form_data['repo_name_full']
375 update_after_clone = form_data['update_after_clone']
375 update_after_clone = form_data['update_after_clone']
376 source_repo_path = os.path.join(base_path, org_repo_name)
376 source_repo_path = os.path.join(base_path, org_repo_name)
377 destination_fork_path = os.path.join(base_path, fork_name)
377 destination_fork_path = os.path.join(base_path, fork_name)
378
378
379 log.info('creating fork of %s as %s', source_repo_path,
379 log.info('creating fork of %s as %s', source_repo_path,
380 destination_fork_path)
380 destination_fork_path)
381 backend = get_backend(alias)
381 backend = get_backend(alias)
382 backend(safe_str(destination_fork_path), create=True,
382 backend(safe_str(destination_fork_path), create=True,
383 src_url=safe_str(source_repo_path),
383 src_url=safe_str(source_repo_path),
384 update_after_clone=update_after_clone)
384 update_after_clone=update_after_clone)
385 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
385 action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
386 org_repo_name, '', Session)
386 org_repo_name, '', Session)
387
388 action_logger(cur_user, 'user_created_fork:%s' % fork_name,
389 fork_name, '', Session)
387 # finally commit at latest possible stage
390 # finally commit at latest possible stage
388 Session.commit()
391 Session.commit()
389
392
390 def __get_codes_stats(repo_name):
393 def __get_codes_stats(repo_name):
391 repo = Repository.get_by_repo_name(repo_name).scm_instance
394 repo = Repository.get_by_repo_name(repo_name).scm_instance
392
395
393 tip = repo.get_changeset()
396 tip = repo.get_changeset()
394 code_stats = {}
397 code_stats = {}
395
398
396 def aggregate(cs):
399 def aggregate(cs):
397 for f in cs[2]:
400 for f in cs[2]:
398 ext = lower(f.extension)
401 ext = lower(f.extension)
399 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
402 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
400 if ext in code_stats:
403 if ext in code_stats:
401 code_stats[ext] += 1
404 code_stats[ext] += 1
402 else:
405 else:
403 code_stats[ext] = 1
406 code_stats[ext] = 1
404
407
405 map(aggregate, tip.walk('/'))
408 map(aggregate, tip.walk('/'))
406
409
407 return code_stats or {}
410 return code_stats or {}
@@ -1,675 +1,677
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11
11
12 from datetime import datetime
12 from datetime import datetime
13 from pygments.formatters.html import HtmlFormatter
13 from pygments.formatters.html import HtmlFormatter
14 from pygments import highlight as code_highlight
14 from pygments import highlight as code_highlight
15 from pylons import url, request, config
15 from pylons import url, request, config
16 from pylons.i18n.translation import _, ungettext
16 from pylons.i18n.translation import _, ungettext
17
17
18 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html import literal, HTML, escape
19 from webhelpers.html.tools import *
19 from webhelpers.html.tools import *
20 from webhelpers.html.builder import make_tag
20 from webhelpers.html.builder import make_tag
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
24 password, textarea, title, ul, xml_declaration, radio
24 password, textarea, title, ul, xml_declaration, radio
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
26 mail_to, strip_links, strip_tags, tag_re
26 mail_to, strip_links, strip_tags, tag_re
27 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.number import format_byte_size, format_bit_size
28 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib import Flash as _Flash
29 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.pylonslib.secure_form import secure_form
30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
32 replace_whitespace, urlify, truncate, wrap_paragraphs
32 replace_whitespace, urlify, truncate, wrap_paragraphs
33 from webhelpers.date import time_ago_in_words
33 from webhelpers.date import time_ago_in_words
34 from webhelpers.paginate import Page
34 from webhelpers.paginate import Page
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
36 convert_boolean_attrs, NotGiven, _make_safe_id_component
36 convert_boolean_attrs, NotGiven, _make_safe_id_component
37
37
38 from vcs.utils.annotate import annotate_highlight
38 from vcs.utils.annotate import annotate_highlight
39 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
40 from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
41
41
42 from rhodecode.lib.markup_renderer import MarkupRenderer
42 from rhodecode.lib.markup_renderer import MarkupRenderer
43
43
44 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
44 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
45 """
45 """
46 Reset button
46 Reset button
47 """
47 """
48 _set_input_attrs(attrs, type, name, value)
48 _set_input_attrs(attrs, type, name, value)
49 _set_id_attr(attrs, id, name)
49 _set_id_attr(attrs, id, name)
50 convert_boolean_attrs(attrs, ["disabled"])
50 convert_boolean_attrs(attrs, ["disabled"])
51 return HTML.input(**attrs)
51 return HTML.input(**attrs)
52
52
53 reset = _reset
53 reset = _reset
54 safeid = _make_safe_id_component
54 safeid = _make_safe_id_component
55
55
56 def get_token():
56 def get_token():
57 """Return the current authentication token, creating one if one doesn't
57 """Return the current authentication token, creating one if one doesn't
58 already exist.
58 already exist.
59 """
59 """
60 token_key = "_authentication_token"
60 token_key = "_authentication_token"
61 from pylons import session
61 from pylons import session
62 if not token_key in session:
62 if not token_key in session:
63 try:
63 try:
64 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
64 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
65 except AttributeError: # Python < 2.4
65 except AttributeError: # Python < 2.4
66 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
66 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
67 session[token_key] = token
67 session[token_key] = token
68 if hasattr(session, 'save'):
68 if hasattr(session, 'save'):
69 session.save()
69 session.save()
70 return session[token_key]
70 return session[token_key]
71
71
72 class _GetError(object):
72 class _GetError(object):
73 """Get error from form_errors, and represent it as span wrapped error
73 """Get error from form_errors, and represent it as span wrapped error
74 message
74 message
75
75
76 :param field_name: field to fetch errors for
76 :param field_name: field to fetch errors for
77 :param form_errors: form errors dict
77 :param form_errors: form errors dict
78 """
78 """
79
79
80 def __call__(self, field_name, form_errors):
80 def __call__(self, field_name, form_errors):
81 tmpl = """<span class="error_msg">%s</span>"""
81 tmpl = """<span class="error_msg">%s</span>"""
82 if form_errors and form_errors.has_key(field_name):
82 if form_errors and form_errors.has_key(field_name):
83 return literal(tmpl % form_errors.get(field_name))
83 return literal(tmpl % form_errors.get(field_name))
84
84
85 get_error = _GetError()
85 get_error = _GetError()
86
86
87 class _ToolTip(object):
87 class _ToolTip(object):
88
88
89 def __call__(self, tooltip_title, trim_at=50):
89 def __call__(self, tooltip_title, trim_at=50):
90 """Special function just to wrap our text into nice formatted
90 """Special function just to wrap our text into nice formatted
91 autowrapped text
91 autowrapped text
92
92
93 :param tooltip_title:
93 :param tooltip_title:
94 """
94 """
95 return escape(tooltip_title)
95 return escape(tooltip_title)
96 tooltip = _ToolTip()
96 tooltip = _ToolTip()
97
97
98 class _FilesBreadCrumbs(object):
98 class _FilesBreadCrumbs(object):
99
99
100 def __call__(self, repo_name, rev, paths):
100 def __call__(self, repo_name, rev, paths):
101 if isinstance(paths, str):
101 if isinstance(paths, str):
102 paths = safe_unicode(paths)
102 paths = safe_unicode(paths)
103 url_l = [link_to(repo_name, url('files_home',
103 url_l = [link_to(repo_name, url('files_home',
104 repo_name=repo_name,
104 repo_name=repo_name,
105 revision=rev, f_path=''))]
105 revision=rev, f_path=''))]
106 paths_l = paths.split('/')
106 paths_l = paths.split('/')
107 for cnt, p in enumerate(paths_l):
107 for cnt, p in enumerate(paths_l):
108 if p != '':
108 if p != '':
109 url_l.append(link_to(p, url('files_home',
109 url_l.append(link_to(p, url('files_home',
110 repo_name=repo_name,
110 repo_name=repo_name,
111 revision=rev,
111 revision=rev,
112 f_path='/'.join(paths_l[:cnt + 1]))))
112 f_path='/'.join(paths_l[:cnt + 1]))))
113
113
114 return literal('/'.join(url_l))
114 return literal('/'.join(url_l))
115
115
116 files_breadcrumbs = _FilesBreadCrumbs()
116 files_breadcrumbs = _FilesBreadCrumbs()
117
117
118 class CodeHtmlFormatter(HtmlFormatter):
118 class CodeHtmlFormatter(HtmlFormatter):
119 """My code Html Formatter for source codes
119 """My code Html Formatter for source codes
120 """
120 """
121
121
122 def wrap(self, source, outfile):
122 def wrap(self, source, outfile):
123 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
123 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
124
124
125 def _wrap_code(self, source):
125 def _wrap_code(self, source):
126 for cnt, it in enumerate(source):
126 for cnt, it in enumerate(source):
127 i, t = it
127 i, t = it
128 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
128 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
129 yield i, t
129 yield i, t
130
130
131 def _wrap_tablelinenos(self, inner):
131 def _wrap_tablelinenos(self, inner):
132 dummyoutfile = StringIO.StringIO()
132 dummyoutfile = StringIO.StringIO()
133 lncount = 0
133 lncount = 0
134 for t, line in inner:
134 for t, line in inner:
135 if t:
135 if t:
136 lncount += 1
136 lncount += 1
137 dummyoutfile.write(line)
137 dummyoutfile.write(line)
138
138
139 fl = self.linenostart
139 fl = self.linenostart
140 mw = len(str(lncount + fl - 1))
140 mw = len(str(lncount + fl - 1))
141 sp = self.linenospecial
141 sp = self.linenospecial
142 st = self.linenostep
142 st = self.linenostep
143 la = self.lineanchors
143 la = self.lineanchors
144 aln = self.anchorlinenos
144 aln = self.anchorlinenos
145 nocls = self.noclasses
145 nocls = self.noclasses
146 if sp:
146 if sp:
147 lines = []
147 lines = []
148
148
149 for i in range(fl, fl + lncount):
149 for i in range(fl, fl + lncount):
150 if i % st == 0:
150 if i % st == 0:
151 if i % sp == 0:
151 if i % sp == 0:
152 if aln:
152 if aln:
153 lines.append('<a href="#%s%d" class="special">%*d</a>' %
153 lines.append('<a href="#%s%d" class="special">%*d</a>' %
154 (la, i, mw, i))
154 (la, i, mw, i))
155 else:
155 else:
156 lines.append('<span class="special">%*d</span>' % (mw, i))
156 lines.append('<span class="special">%*d</span>' % (mw, i))
157 else:
157 else:
158 if aln:
158 if aln:
159 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
159 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
160 else:
160 else:
161 lines.append('%*d' % (mw, i))
161 lines.append('%*d' % (mw, i))
162 else:
162 else:
163 lines.append('')
163 lines.append('')
164 ls = '\n'.join(lines)
164 ls = '\n'.join(lines)
165 else:
165 else:
166 lines = []
166 lines = []
167 for i in range(fl, fl + lncount):
167 for i in range(fl, fl + lncount):
168 if i % st == 0:
168 if i % st == 0:
169 if aln:
169 if aln:
170 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
170 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
171 else:
171 else:
172 lines.append('%*d' % (mw, i))
172 lines.append('%*d' % (mw, i))
173 else:
173 else:
174 lines.append('')
174 lines.append('')
175 ls = '\n'.join(lines)
175 ls = '\n'.join(lines)
176
176
177 # in case you wonder about the seemingly redundant <div> here: since the
177 # in case you wonder about the seemingly redundant <div> here: since the
178 # content in the other cell also is wrapped in a div, some browsers in
178 # content in the other cell also is wrapped in a div, some browsers in
179 # some configurations seem to mess up the formatting...
179 # some configurations seem to mess up the formatting...
180 if nocls:
180 if nocls:
181 yield 0, ('<table class="%stable">' % self.cssclass +
181 yield 0, ('<table class="%stable">' % self.cssclass +
182 '<tr><td><div class="linenodiv" '
182 '<tr><td><div class="linenodiv" '
183 'style="background-color: #f0f0f0; padding-right: 10px">'
183 'style="background-color: #f0f0f0; padding-right: 10px">'
184 '<pre style="line-height: 125%">' +
184 '<pre style="line-height: 125%">' +
185 ls + '</pre></div></td><td id="hlcode" class="code">')
185 ls + '</pre></div></td><td id="hlcode" class="code">')
186 else:
186 else:
187 yield 0, ('<table class="%stable">' % self.cssclass +
187 yield 0, ('<table class="%stable">' % self.cssclass +
188 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
188 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
189 ls + '</pre></div></td><td id="hlcode" class="code">')
189 ls + '</pre></div></td><td id="hlcode" class="code">')
190 yield 0, dummyoutfile.getvalue()
190 yield 0, dummyoutfile.getvalue()
191 yield 0, '</td></tr></table>'
191 yield 0, '</td></tr></table>'
192
192
193
193
194 def pygmentize(filenode, **kwargs):
194 def pygmentize(filenode, **kwargs):
195 """pygmentize function using pygments
195 """pygmentize function using pygments
196
196
197 :param filenode:
197 :param filenode:
198 """
198 """
199
199
200 return literal(code_highlight(filenode.content,
200 return literal(code_highlight(filenode.content,
201 filenode.lexer, CodeHtmlFormatter(**kwargs)))
201 filenode.lexer, CodeHtmlFormatter(**kwargs)))
202
202
203 def pygmentize_annotation(repo_name, filenode, **kwargs):
203 def pygmentize_annotation(repo_name, filenode, **kwargs):
204 """pygmentize function for annotation
204 """pygmentize function for annotation
205
205
206 :param filenode:
206 :param filenode:
207 """
207 """
208
208
209 color_dict = {}
209 color_dict = {}
210 def gen_color(n=10000):
210 def gen_color(n=10000):
211 """generator for getting n of evenly distributed colors using
211 """generator for getting n of evenly distributed colors using
212 hsv color and golden ratio. It always return same order of colors
212 hsv color and golden ratio. It always return same order of colors
213
213
214 :returns: RGB tuple
214 :returns: RGB tuple
215 """
215 """
216
216
217 def hsv_to_rgb(h, s, v):
217 def hsv_to_rgb(h, s, v):
218 if s == 0.0: return v, v, v
218 if s == 0.0: return v, v, v
219 i = int(h * 6.0) # XXX assume int() truncates!
219 i = int(h * 6.0) # XXX assume int() truncates!
220 f = (h * 6.0) - i
220 f = (h * 6.0) - i
221 p = v * (1.0 - s)
221 p = v * (1.0 - s)
222 q = v * (1.0 - s * f)
222 q = v * (1.0 - s * f)
223 t = v * (1.0 - s * (1.0 - f))
223 t = v * (1.0 - s * (1.0 - f))
224 i = i % 6
224 i = i % 6
225 if i == 0: return v, t, p
225 if i == 0: return v, t, p
226 if i == 1: return q, v, p
226 if i == 1: return q, v, p
227 if i == 2: return p, v, t
227 if i == 2: return p, v, t
228 if i == 3: return p, q, v
228 if i == 3: return p, q, v
229 if i == 4: return t, p, v
229 if i == 4: return t, p, v
230 if i == 5: return v, p, q
230 if i == 5: return v, p, q
231
231
232 golden_ratio = 0.618033988749895
232 golden_ratio = 0.618033988749895
233 h = 0.22717784590367374
233 h = 0.22717784590367374
234
234
235 for _ in xrange(n):
235 for _ in xrange(n):
236 h += golden_ratio
236 h += golden_ratio
237 h %= 1
237 h %= 1
238 HSV_tuple = [h, 0.95, 0.95]
238 HSV_tuple = [h, 0.95, 0.95]
239 RGB_tuple = hsv_to_rgb(*HSV_tuple)
239 RGB_tuple = hsv_to_rgb(*HSV_tuple)
240 yield map(lambda x:str(int(x * 256)), RGB_tuple)
240 yield map(lambda x:str(int(x * 256)), RGB_tuple)
241
241
242 cgenerator = gen_color()
242 cgenerator = gen_color()
243
243
244 def get_color_string(cs):
244 def get_color_string(cs):
245 if color_dict.has_key(cs):
245 if color_dict.has_key(cs):
246 col = color_dict[cs]
246 col = color_dict[cs]
247 else:
247 else:
248 col = color_dict[cs] = cgenerator.next()
248 col = color_dict[cs] = cgenerator.next()
249 return "color: rgb(%s)! important;" % (', '.join(col))
249 return "color: rgb(%s)! important;" % (', '.join(col))
250
250
251 def url_func(repo_name):
251 def url_func(repo_name):
252
252
253 def _url_func(changeset):
253 def _url_func(changeset):
254 author = changeset.author
254 author = changeset.author
255 date = changeset.date
255 date = changeset.date
256 message = tooltip(changeset.message)
256 message = tooltip(changeset.message)
257
257
258 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
258 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
259 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
259 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
260 "</b> %s<br/></div>")
260 "</b> %s<br/></div>")
261
261
262 tooltip_html = tooltip_html % (author, date, message)
262 tooltip_html = tooltip_html % (author, date, message)
263 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
263 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
264 short_id(changeset.raw_id))
264 short_id(changeset.raw_id))
265 uri = link_to(
265 uri = link_to(
266 lnk_format,
266 lnk_format,
267 url('changeset_home', repo_name=repo_name,
267 url('changeset_home', repo_name=repo_name,
268 revision=changeset.raw_id),
268 revision=changeset.raw_id),
269 style=get_color_string(changeset.raw_id),
269 style=get_color_string(changeset.raw_id),
270 class_='tooltip',
270 class_='tooltip',
271 title=tooltip_html
271 title=tooltip_html
272 )
272 )
273
273
274 uri += '\n'
274 uri += '\n'
275 return uri
275 return uri
276 return _url_func
276 return _url_func
277
277
278 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
278 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
279
279
280 def is_following_repo(repo_name, user_id):
280 def is_following_repo(repo_name, user_id):
281 from rhodecode.model.scm import ScmModel
281 from rhodecode.model.scm import ScmModel
282 return ScmModel().is_following_repo(repo_name, user_id)
282 return ScmModel().is_following_repo(repo_name, user_id)
283
283
284 flash = _Flash()
284 flash = _Flash()
285
285
286 #==============================================================================
286 #==============================================================================
287 # SCM FILTERS available via h.
287 # SCM FILTERS available via h.
288 #==============================================================================
288 #==============================================================================
289 from vcs.utils import author_name, author_email
289 from vcs.utils import author_name, author_email
290 from rhodecode.lib import credentials_filter, age as _age
290 from rhodecode.lib import credentials_filter, age as _age
291
291
292 age = lambda x:_age(x)
292 age = lambda x:_age(x)
293 capitalize = lambda x: x.capitalize()
293 capitalize = lambda x: x.capitalize()
294 email = author_email
294 email = author_email
295 email_or_none = lambda x: email(x) if email(x) != x else None
295 email_or_none = lambda x: email(x) if email(x) != x else None
296 person = lambda x: author_name(x)
296 person = lambda x: author_name(x)
297 short_id = lambda x: x[:12]
297 short_id = lambda x: x[:12]
298 hide_credentials = lambda x: ''.join(credentials_filter(x))
298 hide_credentials = lambda x: ''.join(credentials_filter(x))
299
299
300 def bool2icon(value):
300 def bool2icon(value):
301 """Returns True/False values represented as small html image of true/false
301 """Returns True/False values represented as small html image of true/false
302 icons
302 icons
303
303
304 :param value: bool value
304 :param value: bool value
305 """
305 """
306
306
307 if value is True:
307 if value is True:
308 return HTML.tag('img', src=url("/images/icons/accept.png"),
308 return HTML.tag('img', src=url("/images/icons/accept.png"),
309 alt=_('True'))
309 alt=_('True'))
310
310
311 if value is False:
311 if value is False:
312 return HTML.tag('img', src=url("/images/icons/cancel.png"),
312 return HTML.tag('img', src=url("/images/icons/cancel.png"),
313 alt=_('False'))
313 alt=_('False'))
314
314
315 return value
315 return value
316
316
317
317
318 def action_parser(user_log, feed=False):
318 def action_parser(user_log, feed=False):
319 """This helper will action_map the specified string action into translated
319 """This helper will action_map the specified string action into translated
320 fancy names with icons and links
320 fancy names with icons and links
321
321
322 :param user_log: user log instance
322 :param user_log: user log instance
323 :param feed: use output for feeds (no html and fancy icons)
323 :param feed: use output for feeds (no html and fancy icons)
324 """
324 """
325
325
326 action = user_log.action
326 action = user_log.action
327 action_params = ' '
327 action_params = ' '
328
328
329 x = action.split(':')
329 x = action.split(':')
330
330
331 if len(x) > 1:
331 if len(x) > 1:
332 action, action_params = x
332 action, action_params = x
333
333
334 def get_cs_links():
334 def get_cs_links():
335 revs_limit = 3 #display this amount always
335 revs_limit = 3 #display this amount always
336 revs_top_limit = 50 #show upto this amount of changesets hidden
336 revs_top_limit = 50 #show upto this amount of changesets hidden
337 revs = action_params.split(',')
337 revs = action_params.split(',')
338 repo_name = user_log.repository.repo_name
338 repo_name = user_log.repository.repo_name
339
339
340 from rhodecode.model.scm import ScmModel
340 from rhodecode.model.scm import ScmModel
341 repo = user_log.repository.scm_instance
341 repo = user_log.repository.scm_instance
342
342
343 message = lambda rev: get_changeset_safe(repo, rev).message
343 message = lambda rev: get_changeset_safe(repo, rev).message
344 cs_links = []
344 cs_links = []
345 cs_links.append(" " + ', '.join ([link_to(rev,
345 cs_links.append(" " + ', '.join ([link_to(rev,
346 url('changeset_home',
346 url('changeset_home',
347 repo_name=repo_name,
347 repo_name=repo_name,
348 revision=rev), title=tooltip(message(rev)),
348 revision=rev), title=tooltip(message(rev)),
349 class_='tooltip') for rev in revs[:revs_limit] ]))
349 class_='tooltip') for rev in revs[:revs_limit] ]))
350
350
351 compare_view = (' <div class="compare_view tooltip" title="%s">'
351 compare_view = (' <div class="compare_view tooltip" title="%s">'
352 '<a href="%s">%s</a> '
352 '<a href="%s">%s</a> '
353 '</div>' % (_('Show all combined changesets %s->%s' \
353 '</div>' % (_('Show all combined changesets %s->%s' \
354 % (revs[0], revs[-1])),
354 % (revs[0], revs[-1])),
355 url('changeset_home', repo_name=repo_name,
355 url('changeset_home', repo_name=repo_name,
356 revision='%s...%s' % (revs[0], revs[-1])
356 revision='%s...%s' % (revs[0], revs[-1])
357 ),
357 ),
358 _('compare view'))
358 _('compare view'))
359 )
359 )
360
360
361 if len(revs) > revs_limit:
361 if len(revs) > revs_limit:
362 uniq_id = revs[0]
362 uniq_id = revs[0]
363 html_tmpl = ('<span> %s '
363 html_tmpl = ('<span> %s '
364 '<a class="show_more" id="_%s" href="#more">%s</a> '
364 '<a class="show_more" id="_%s" href="#more">%s</a> '
365 '%s</span>')
365 '%s</span>')
366 if not feed:
366 if not feed:
367 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
367 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
368 % (len(revs) - revs_limit),
368 % (len(revs) - revs_limit),
369 _('revisions')))
369 _('revisions')))
370
370
371 if not feed:
371 if not feed:
372 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
372 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
373 else:
373 else:
374 html_tmpl = '<span id="%s"> %s </span>'
374 html_tmpl = '<span id="%s"> %s </span>'
375
375
376 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
376 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
377 url('changeset_home',
377 url('changeset_home',
378 repo_name=repo_name, revision=rev),
378 repo_name=repo_name, revision=rev),
379 title=message(rev), class_='tooltip')
379 title=message(rev), class_='tooltip')
380 for rev in revs[revs_limit:revs_top_limit]])))
380 for rev in revs[revs_limit:revs_top_limit]])))
381 if len(revs) > 1:
381 if len(revs) > 1:
382 cs_links.append(compare_view)
382 cs_links.append(compare_view)
383 return ''.join(cs_links)
383 return ''.join(cs_links)
384
384
385 def get_fork_name():
385 def get_fork_name():
386 repo_name = action_params
386 repo_name = action_params
387 return _('fork name ') + str(link_to(action_params, url('summary_home',
387 return _('fork name ') + str(link_to(action_params, url('summary_home',
388 repo_name=repo_name,)))
388 repo_name=repo_name,)))
389
389
390 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
390 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
391 'user_created_repo':(_('[created] repository'), None),
391 'user_created_repo':(_('[created] repository'), None),
392 'user_created_fork':(_('[created] repository as fork'), None),
392 'user_forked_repo':(_('[forked] repository'), get_fork_name),
393 'user_forked_repo':(_('[forked] repository'), get_fork_name),
393 'user_updated_repo':(_('[updated] repository'), None),
394 'user_updated_repo':(_('[updated] repository'), None),
394 'admin_deleted_repo':(_('[delete] repository'), None),
395 'admin_deleted_repo':(_('[delete] repository'), None),
395 'admin_created_repo':(_('[created] repository'), None),
396 'admin_created_repo':(_('[created] repository'), None),
396 'admin_forked_repo':(_('[forked] repository'), None),
397 'admin_forked_repo':(_('[forked] repository'), None),
397 'admin_updated_repo':(_('[updated] repository'), None),
398 'admin_updated_repo':(_('[updated] repository'), None),
398 'push':(_('[pushed] into'), get_cs_links),
399 'push':(_('[pushed] into'), get_cs_links),
399 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
400 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
400 'push_remote':(_('[pulled from remote] into'), get_cs_links),
401 'push_remote':(_('[pulled from remote] into'), get_cs_links),
401 'pull':(_('[pulled] from'), None),
402 'pull':(_('[pulled] from'), None),
402 'started_following_repo':(_('[started following] repository'), None),
403 'started_following_repo':(_('[started following] repository'), None),
403 'stopped_following_repo':(_('[stopped following] repository'), None),
404 'stopped_following_repo':(_('[stopped following] repository'), None),
404 }
405 }
405
406
406 action_str = action_map.get(action, action)
407 action_str = action_map.get(action, action)
407 if feed:
408 if feed:
408 action = action_str[0].replace('[', '').replace(']', '')
409 action = action_str[0].replace('[', '').replace(']', '')
409 else:
410 else:
410 action = action_str[0].replace('[', '<span class="journal_highlight">')\
411 action = action_str[0].replace('[', '<span class="journal_highlight">')\
411 .replace(']', '</span>')
412 .replace(']', '</span>')
412
413
413 action_params_func = lambda :""
414 action_params_func = lambda :""
414
415
415 if callable(action_str[1]):
416 if callable(action_str[1]):
416 action_params_func = action_str[1]
417 action_params_func = action_str[1]
417
418
418 return [literal(action), action_params_func]
419 return [literal(action), action_params_func]
419
420
420 def action_parser_icon(user_log):
421 def action_parser_icon(user_log):
421 action = user_log.action
422 action = user_log.action
422 action_params = None
423 action_params = None
423 x = action.split(':')
424 x = action.split(':')
424
425
425 if len(x) > 1:
426 if len(x) > 1:
426 action, action_params = x
427 action, action_params = x
427
428
428 tmpl = """<img src="%s%s" alt="%s"/>"""
429 tmpl = """<img src="%s%s" alt="%s"/>"""
429 map = {'user_deleted_repo':'database_delete.png',
430 map = {'user_deleted_repo':'database_delete.png',
430 'user_created_repo':'database_add.png',
431 'user_created_repo':'database_add.png',
432 'user_created_fork':'arrow_divide.png',
431 'user_forked_repo':'arrow_divide.png',
433 'user_forked_repo':'arrow_divide.png',
432 'user_updated_repo':'database_edit.png',
434 'user_updated_repo':'database_edit.png',
433 'admin_deleted_repo':'database_delete.png',
435 'admin_deleted_repo':'database_delete.png',
434 'admin_created_repo':'database_add.png',
436 'admin_created_repo':'database_add.png',
435 'admin_forked_repo':'arrow_divide.png',
437 'admin_forked_repo':'arrow_divide.png',
436 'admin_updated_repo':'database_edit.png',
438 'admin_updated_repo':'database_edit.png',
437 'push':'script_add.png',
439 'push':'script_add.png',
438 'push_local':'script_edit.png',
440 'push_local':'script_edit.png',
439 'push_remote':'connect.png',
441 'push_remote':'connect.png',
440 'pull':'down_16.png',
442 'pull':'down_16.png',
441 'started_following_repo':'heart_add.png',
443 'started_following_repo':'heart_add.png',
442 'stopped_following_repo':'heart_delete.png',
444 'stopped_following_repo':'heart_delete.png',
443 }
445 }
444 return literal(tmpl % ((url('/images/icons/')),
446 return literal(tmpl % ((url('/images/icons/')),
445 map.get(action, action), action))
447 map.get(action, action), action))
446
448
447
449
448 #==============================================================================
450 #==============================================================================
449 # PERMS
451 # PERMS
450 #==============================================================================
452 #==============================================================================
451 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
453 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
452 HasRepoPermissionAny, HasRepoPermissionAll
454 HasRepoPermissionAny, HasRepoPermissionAll
453
455
454 #==============================================================================
456 #==============================================================================
455 # GRAVATAR URL
457 # GRAVATAR URL
456 #==============================================================================
458 #==============================================================================
457
459
458 def gravatar_url(email_address, size=30):
460 def gravatar_url(email_address, size=30):
459 if (not str2bool(config['app_conf'].get('use_gravatar')) or
461 if (not str2bool(config['app_conf'].get('use_gravatar')) or
460 not email_address or email_address == 'anonymous@rhodecode.org'):
462 not email_address or email_address == 'anonymous@rhodecode.org'):
461 return url("/images/user%s.png" % size)
463 return url("/images/user%s.png" % size)
462
464
463 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
465 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
464 default = 'identicon'
466 default = 'identicon'
465 baseurl_nossl = "http://www.gravatar.com/avatar/"
467 baseurl_nossl = "http://www.gravatar.com/avatar/"
466 baseurl_ssl = "https://secure.gravatar.com/avatar/"
468 baseurl_ssl = "https://secure.gravatar.com/avatar/"
467 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
469 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
468
470
469 if isinstance(email_address, unicode):
471 if isinstance(email_address, unicode):
470 #hashlib crashes on unicode items
472 #hashlib crashes on unicode items
471 email_address = safe_str(email_address)
473 email_address = safe_str(email_address)
472 # construct the url
474 # construct the url
473 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
475 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
474 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
476 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
475
477
476 return gravatar_url
478 return gravatar_url
477
479
478
480
479 #==============================================================================
481 #==============================================================================
480 # REPO PAGER, PAGER FOR REPOSITORY
482 # REPO PAGER, PAGER FOR REPOSITORY
481 #==============================================================================
483 #==============================================================================
482 class RepoPage(Page):
484 class RepoPage(Page):
483
485
484 def __init__(self, collection, page=1, items_per_page=20,
486 def __init__(self, collection, page=1, items_per_page=20,
485 item_count=None, url=None, **kwargs):
487 item_count=None, url=None, **kwargs):
486
488
487 """Create a "RepoPage" instance. special pager for paging
489 """Create a "RepoPage" instance. special pager for paging
488 repository
490 repository
489 """
491 """
490 self._url_generator = url
492 self._url_generator = url
491
493
492 # Safe the kwargs class-wide so they can be used in the pager() method
494 # Safe the kwargs class-wide so they can be used in the pager() method
493 self.kwargs = kwargs
495 self.kwargs = kwargs
494
496
495 # Save a reference to the collection
497 # Save a reference to the collection
496 self.original_collection = collection
498 self.original_collection = collection
497
499
498 self.collection = collection
500 self.collection = collection
499
501
500 # The self.page is the number of the current page.
502 # The self.page is the number of the current page.
501 # The first page has the number 1!
503 # The first page has the number 1!
502 try:
504 try:
503 self.page = int(page) # make it int() if we get it as a string
505 self.page = int(page) # make it int() if we get it as a string
504 except (ValueError, TypeError):
506 except (ValueError, TypeError):
505 self.page = 1
507 self.page = 1
506
508
507 self.items_per_page = items_per_page
509 self.items_per_page = items_per_page
508
510
509 # Unless the user tells us how many items the collections has
511 # Unless the user tells us how many items the collections has
510 # we calculate that ourselves.
512 # we calculate that ourselves.
511 if item_count is not None:
513 if item_count is not None:
512 self.item_count = item_count
514 self.item_count = item_count
513 else:
515 else:
514 self.item_count = len(self.collection)
516 self.item_count = len(self.collection)
515
517
516 # Compute the number of the first and last available page
518 # Compute the number of the first and last available page
517 if self.item_count > 0:
519 if self.item_count > 0:
518 self.first_page = 1
520 self.first_page = 1
519 self.page_count = int(math.ceil(float(self.item_count) /
521 self.page_count = int(math.ceil(float(self.item_count) /
520 self.items_per_page))
522 self.items_per_page))
521 self.last_page = self.first_page + self.page_count - 1
523 self.last_page = self.first_page + self.page_count - 1
522
524
523 # Make sure that the requested page number is the range of valid pages
525 # Make sure that the requested page number is the range of valid pages
524 if self.page > self.last_page:
526 if self.page > self.last_page:
525 self.page = self.last_page
527 self.page = self.last_page
526 elif self.page < self.first_page:
528 elif self.page < self.first_page:
527 self.page = self.first_page
529 self.page = self.first_page
528
530
529 # Note: the number of items on this page can be less than
531 # Note: the number of items on this page can be less than
530 # items_per_page if the last page is not full
532 # items_per_page if the last page is not full
531 self.first_item = max(0, (self.item_count) - (self.page *
533 self.first_item = max(0, (self.item_count) - (self.page *
532 items_per_page))
534 items_per_page))
533 self.last_item = ((self.item_count - 1) - items_per_page *
535 self.last_item = ((self.item_count - 1) - items_per_page *
534 (self.page - 1))
536 (self.page - 1))
535
537
536 self.items = list(self.collection[self.first_item:self.last_item + 1])
538 self.items = list(self.collection[self.first_item:self.last_item + 1])
537
539
538
540
539 # Links to previous and next page
541 # Links to previous and next page
540 if self.page > self.first_page:
542 if self.page > self.first_page:
541 self.previous_page = self.page - 1
543 self.previous_page = self.page - 1
542 else:
544 else:
543 self.previous_page = None
545 self.previous_page = None
544
546
545 if self.page < self.last_page:
547 if self.page < self.last_page:
546 self.next_page = self.page + 1
548 self.next_page = self.page + 1
547 else:
549 else:
548 self.next_page = None
550 self.next_page = None
549
551
550 # No items available
552 # No items available
551 else:
553 else:
552 self.first_page = None
554 self.first_page = None
553 self.page_count = 0
555 self.page_count = 0
554 self.last_page = None
556 self.last_page = None
555 self.first_item = None
557 self.first_item = None
556 self.last_item = None
558 self.last_item = None
557 self.previous_page = None
559 self.previous_page = None
558 self.next_page = None
560 self.next_page = None
559 self.items = []
561 self.items = []
560
562
561 # This is a subclass of the 'list' type. Initialise the list now.
563 # This is a subclass of the 'list' type. Initialise the list now.
562 list.__init__(self, reversed(self.items))
564 list.__init__(self, reversed(self.items))
563
565
564
566
565 def changed_tooltip(nodes):
567 def changed_tooltip(nodes):
566 """
568 """
567 Generates a html string for changed nodes in changeset page.
569 Generates a html string for changed nodes in changeset page.
568 It limits the output to 30 entries
570 It limits the output to 30 entries
569
571
570 :param nodes: LazyNodesGenerator
572 :param nodes: LazyNodesGenerator
571 """
573 """
572 if nodes:
574 if nodes:
573 pref = ': <br/> '
575 pref = ': <br/> '
574 suf = ''
576 suf = ''
575 if len(nodes) > 30:
577 if len(nodes) > 30:
576 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
578 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
577 return literal(pref + '<br/> '.join([safe_unicode(x.path)
579 return literal(pref + '<br/> '.join([safe_unicode(x.path)
578 for x in nodes[:30]]) + suf)
580 for x in nodes[:30]]) + suf)
579 else:
581 else:
580 return ': ' + _('No Files')
582 return ': ' + _('No Files')
581
583
582
584
583
585
584 def repo_link(groups_and_repos):
586 def repo_link(groups_and_repos):
585 """
587 """
586 Makes a breadcrumbs link to repo within a group
588 Makes a breadcrumbs link to repo within a group
587 joins &raquo; on each group to create a fancy link
589 joins &raquo; on each group to create a fancy link
588
590
589 ex::
591 ex::
590 group >> subgroup >> repo
592 group >> subgroup >> repo
591
593
592 :param groups_and_repos:
594 :param groups_and_repos:
593 """
595 """
594 groups, repo_name = groups_and_repos
596 groups, repo_name = groups_and_repos
595
597
596 if not groups:
598 if not groups:
597 return repo_name
599 return repo_name
598 else:
600 else:
599 def make_link(group):
601 def make_link(group):
600 return link_to(group.name, url('repos_group_home',
602 return link_to(group.name, url('repos_group_home',
601 group_name=group.group_name))
603 group_name=group.group_name))
602 return literal(' &raquo; '.join(map(make_link, groups)) + \
604 return literal(' &raquo; '.join(map(make_link, groups)) + \
603 " &raquo; " + repo_name)
605 " &raquo; " + repo_name)
604
606
605 def fancy_file_stats(stats):
607 def fancy_file_stats(stats):
606 """
608 """
607 Displays a fancy two colored bar for number of added/deleted
609 Displays a fancy two colored bar for number of added/deleted
608 lines of code on file
610 lines of code on file
609
611
610 :param stats: two element list of added/deleted lines of code
612 :param stats: two element list of added/deleted lines of code
611 """
613 """
612
614
613 a, d, t = stats[0], stats[1], stats[0] + stats[1]
615 a, d, t = stats[0], stats[1], stats[0] + stats[1]
614 width = 100
616 width = 100
615 unit = float(width) / (t or 1)
617 unit = float(width) / (t or 1)
616
618
617 # needs > 9% of width to be visible or 0 to be hidden
619 # needs > 9% of width to be visible or 0 to be hidden
618 a_p = max(9, unit * a) if a > 0 else 0
620 a_p = max(9, unit * a) if a > 0 else 0
619 d_p = max(9, unit * d) if d > 0 else 0
621 d_p = max(9, unit * d) if d > 0 else 0
620 p_sum = a_p + d_p
622 p_sum = a_p + d_p
621
623
622 if p_sum > width:
624 if p_sum > width:
623 #adjust the percentage to be == 100% since we adjusted to 9
625 #adjust the percentage to be == 100% since we adjusted to 9
624 if a_p > d_p:
626 if a_p > d_p:
625 a_p = a_p - (p_sum - width)
627 a_p = a_p - (p_sum - width)
626 else:
628 else:
627 d_p = d_p - (p_sum - width)
629 d_p = d_p - (p_sum - width)
628
630
629 a_v = a if a > 0 else ''
631 a_v = a if a > 0 else ''
630 d_v = d if d > 0 else ''
632 d_v = d if d > 0 else ''
631
633
632
634
633 def cgen(l_type):
635 def cgen(l_type):
634 mapping = {'tr':'top-right-rounded-corner',
636 mapping = {'tr':'top-right-rounded-corner',
635 'tl':'top-left-rounded-corner',
637 'tl':'top-left-rounded-corner',
636 'br':'bottom-right-rounded-corner',
638 'br':'bottom-right-rounded-corner',
637 'bl':'bottom-left-rounded-corner'}
639 'bl':'bottom-left-rounded-corner'}
638 map_getter = lambda x:mapping[x]
640 map_getter = lambda x:mapping[x]
639
641
640 if l_type == 'a' and d_v:
642 if l_type == 'a' and d_v:
641 #case when added and deleted are present
643 #case when added and deleted are present
642 return ' '.join(map(map_getter, ['tl', 'bl']))
644 return ' '.join(map(map_getter, ['tl', 'bl']))
643
645
644 if l_type == 'a' and not d_v:
646 if l_type == 'a' and not d_v:
645 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
647 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
646
648
647 if l_type == 'd' and a_v:
649 if l_type == 'd' and a_v:
648 return ' '.join(map(map_getter, ['tr', 'br']))
650 return ' '.join(map(map_getter, ['tr', 'br']))
649
651
650 if l_type == 'd' and not a_v:
652 if l_type == 'd' and not a_v:
651 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
653 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
652
654
653
655
654
656
655 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
657 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
656 a_p, a_v)
658 a_p, a_v)
657 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
659 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
658 d_p, d_v)
660 d_p, d_v)
659 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
661 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
660
662
661
663
662 def urlify_text(text):
664 def urlify_text(text):
663 import re
665 import re
664
666
665 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
667 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
666
668
667 def url_func(match_obj):
669 def url_func(match_obj):
668 url_full = match_obj.groups()[0]
670 url_full = match_obj.groups()[0]
669 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
671 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
670
672
671 return literal(url_pat.sub(url_func, text))
673 return literal(url_pat.sub(url_func, text))
672
674
673
675
674 def rst(source):
676 def rst(source):
675 return literal('<div class="rst-block">%s</div>' % MarkupRenderer.rst(source))
677 return literal('<div class="rst-block">%s</div>' % MarkupRenderer.rst(source))
@@ -1,1237 +1,1237
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) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 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
30
31 from sqlalchemy import *
31 from sqlalchemy import *
32 from sqlalchemy.ext.hybrid import hybrid_property
32 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
33 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 from beaker.cache import cache_region, region_invalidate
34 from beaker.cache import cache_region, region_invalidate
35
35
36 from vcs import get_backend
36 from vcs import get_backend
37 from vcs.utils.helpers import get_scm
37 from vcs.utils.helpers import get_scm
38 from vcs.exceptions import VCSError
38 from vcs.exceptions import VCSError
39 from vcs.utils.lazy import LazyProperty
39 from vcs.utils.lazy import LazyProperty
40
40
41 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
41 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
42 from rhodecode.lib.exceptions import UsersGroupsAssignedException
42 from rhodecode.lib.exceptions import UsersGroupsAssignedException
43 from rhodecode.lib.compat import json
43 from rhodecode.lib.compat import json
44 from rhodecode.lib.caching_query import FromCache
44 from rhodecode.lib.caching_query import FromCache
45
45
46 from rhodecode.model.meta import Base, Session
46 from rhodecode.model.meta import Base, Session
47
47
48 log = logging.getLogger(__name__)
48 log = logging.getLogger(__name__)
49
49
50 #==============================================================================
50 #==============================================================================
51 # BASE CLASSES
51 # BASE CLASSES
52 #==============================================================================
52 #==============================================================================
53
53
54 class ModelSerializer(json.JSONEncoder):
54 class ModelSerializer(json.JSONEncoder):
55 """
55 """
56 Simple Serializer for JSON,
56 Simple Serializer for JSON,
57
57
58 usage::
58 usage::
59
59
60 to make object customized for serialization implement a __json__
60 to make object customized for serialization implement a __json__
61 method that will return a dict for serialization into json
61 method that will return a dict for serialization into json
62
62
63 example::
63 example::
64
64
65 class Task(object):
65 class Task(object):
66
66
67 def __init__(self, name, value):
67 def __init__(self, name, value):
68 self.name = name
68 self.name = name
69 self.value = value
69 self.value = value
70
70
71 def __json__(self):
71 def __json__(self):
72 return dict(name=self.name,
72 return dict(name=self.name,
73 value=self.value)
73 value=self.value)
74
74
75 """
75 """
76
76
77 def default(self, obj):
77 def default(self, obj):
78
78
79 if hasattr(obj, '__json__'):
79 if hasattr(obj, '__json__'):
80 return obj.__json__()
80 return obj.__json__()
81 else:
81 else:
82 return json.JSONEncoder.default(self, obj)
82 return json.JSONEncoder.default(self, obj)
83
83
84 class BaseModel(object):
84 class BaseModel(object):
85 """Base Model for all classess
85 """Base Model for all classess
86
86
87 """
87 """
88
88
89 @classmethod
89 @classmethod
90 def _get_keys(cls):
90 def _get_keys(cls):
91 """return column names for this model """
91 """return column names for this model """
92 return class_mapper(cls).c.keys()
92 return class_mapper(cls).c.keys()
93
93
94 def get_dict(self):
94 def get_dict(self):
95 """return dict with keys and values corresponding
95 """return dict with keys and values corresponding
96 to this model data """
96 to this model data """
97
97
98 d = {}
98 d = {}
99 for k in self._get_keys():
99 for k in self._get_keys():
100 d[k] = getattr(self, k)
100 d[k] = getattr(self, k)
101 return d
101 return d
102
102
103 def get_appstruct(self):
103 def get_appstruct(self):
104 """return list with keys and values tupples corresponding
104 """return list with keys and values tupples corresponding
105 to this model data """
105 to this model data """
106
106
107 l = []
107 l = []
108 for k in self._get_keys():
108 for k in self._get_keys():
109 l.append((k, getattr(self, k),))
109 l.append((k, getattr(self, k),))
110 return l
110 return l
111
111
112 def populate_obj(self, populate_dict):
112 def populate_obj(self, populate_dict):
113 """populate model with data from given populate_dict"""
113 """populate model with data from given populate_dict"""
114
114
115 for k in self._get_keys():
115 for k in self._get_keys():
116 if k in populate_dict:
116 if k in populate_dict:
117 setattr(self, k, populate_dict[k])
117 setattr(self, k, populate_dict[k])
118
118
119 @classmethod
119 @classmethod
120 def query(cls):
120 def query(cls):
121 return Session().query(cls)
121 return Session().query(cls)
122
122
123 @classmethod
123 @classmethod
124 def get(cls, id_):
124 def get(cls, id_):
125 if id_:
125 if id_:
126 return cls.query().get(id_)
126 return cls.query().get(id_)
127
127
128 @classmethod
128 @classmethod
129 def getAll(cls):
129 def getAll(cls):
130 return cls.query().all()
130 return cls.query().all()
131
131
132 @classmethod
132 @classmethod
133 def delete(cls, id_):
133 def delete(cls, id_):
134 obj = cls.query().get(id_)
134 obj = cls.query().get(id_)
135 Session().delete(obj)
135 Session().delete(obj)
136
136
137
137
138 class RhodeCodeSetting(Base, BaseModel):
138 class RhodeCodeSetting(Base, BaseModel):
139 __tablename__ = 'rhodecode_settings'
139 __tablename__ = 'rhodecode_settings'
140 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
140 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
141 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
141 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
142 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144
144
145 def __init__(self, k='', v=''):
145 def __init__(self, k='', v=''):
146 self.app_settings_name = k
146 self.app_settings_name = k
147 self.app_settings_value = v
147 self.app_settings_value = v
148
148
149
149
150 @validates('_app_settings_value')
150 @validates('_app_settings_value')
151 def validate_settings_value(self, key, val):
151 def validate_settings_value(self, key, val):
152 assert type(val) == unicode
152 assert type(val) == unicode
153 return val
153 return val
154
154
155 @hybrid_property
155 @hybrid_property
156 def app_settings_value(self):
156 def app_settings_value(self):
157 v = self._app_settings_value
157 v = self._app_settings_value
158 if v == 'ldap_active':
158 if v == 'ldap_active':
159 v = str2bool(v)
159 v = str2bool(v)
160 return v
160 return v
161
161
162 @app_settings_value.setter
162 @app_settings_value.setter
163 def app_settings_value(self, val):
163 def app_settings_value(self, val):
164 """
164 """
165 Setter that will always make sure we use unicode in app_settings_value
165 Setter that will always make sure we use unicode in app_settings_value
166
166
167 :param val:
167 :param val:
168 """
168 """
169 self._app_settings_value = safe_unicode(val)
169 self._app_settings_value = safe_unicode(val)
170
170
171 def __repr__(self):
171 def __repr__(self):
172 return "<%s('%s:%s')>" % (self.__class__.__name__,
172 return "<%s('%s:%s')>" % (self.__class__.__name__,
173 self.app_settings_name, self.app_settings_value)
173 self.app_settings_name, self.app_settings_value)
174
174
175
175
176 @classmethod
176 @classmethod
177 def get_by_name(cls, ldap_key):
177 def get_by_name(cls, ldap_key):
178 return cls.query()\
178 return cls.query()\
179 .filter(cls.app_settings_name == ldap_key).scalar()
179 .filter(cls.app_settings_name == ldap_key).scalar()
180
180
181 @classmethod
181 @classmethod
182 def get_app_settings(cls, cache=False):
182 def get_app_settings(cls, cache=False):
183
183
184 ret = cls.query()
184 ret = cls.query()
185
185
186 if cache:
186 if cache:
187 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
187 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
188
188
189 if not ret:
189 if not ret:
190 raise Exception('Could not get application settings !')
190 raise Exception('Could not get application settings !')
191 settings = {}
191 settings = {}
192 for each in ret:
192 for each in ret:
193 settings['rhodecode_' + each.app_settings_name] = \
193 settings['rhodecode_' + each.app_settings_name] = \
194 each.app_settings_value
194 each.app_settings_value
195
195
196 return settings
196 return settings
197
197
198 @classmethod
198 @classmethod
199 def get_ldap_settings(cls, cache=False):
199 def get_ldap_settings(cls, cache=False):
200 ret = cls.query()\
200 ret = cls.query()\
201 .filter(cls.app_settings_name.startswith('ldap_')).all()
201 .filter(cls.app_settings_name.startswith('ldap_')).all()
202 fd = {}
202 fd = {}
203 for row in ret:
203 for row in ret:
204 fd.update({row.app_settings_name:row.app_settings_value})
204 fd.update({row.app_settings_name:row.app_settings_value})
205
205
206 return fd
206 return fd
207
207
208
208
209 class RhodeCodeUi(Base, BaseModel):
209 class RhodeCodeUi(Base, BaseModel):
210 __tablename__ = 'rhodecode_ui'
210 __tablename__ = 'rhodecode_ui'
211 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
211 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
212
212
213 HOOK_UPDATE = 'changegroup.update'
213 HOOK_UPDATE = 'changegroup.update'
214 HOOK_REPO_SIZE = 'changegroup.repo_size'
214 HOOK_REPO_SIZE = 'changegroup.repo_size'
215 HOOK_PUSH = 'pretxnchangegroup.push_logger'
215 HOOK_PUSH = 'pretxnchangegroup.push_logger'
216 HOOK_PULL = 'preoutgoing.pull_logger'
216 HOOK_PULL = 'preoutgoing.pull_logger'
217
217
218 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
218 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
219 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
219 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
220 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
220 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
221 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
221 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
222 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
222 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
223
223
224
224
225 @classmethod
225 @classmethod
226 def get_by_key(cls, key):
226 def get_by_key(cls, key):
227 return cls.query().filter(cls.ui_key == key)
227 return cls.query().filter(cls.ui_key == key)
228
228
229
229
230 @classmethod
230 @classmethod
231 def get_builtin_hooks(cls):
231 def get_builtin_hooks(cls):
232 q = cls.query()
232 q = cls.query()
233 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
233 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
234 cls.HOOK_REPO_SIZE,
234 cls.HOOK_REPO_SIZE,
235 cls.HOOK_PUSH, cls.HOOK_PULL]))
235 cls.HOOK_PUSH, cls.HOOK_PULL]))
236 return q.all()
236 return q.all()
237
237
238 @classmethod
238 @classmethod
239 def get_custom_hooks(cls):
239 def get_custom_hooks(cls):
240 q = cls.query()
240 q = cls.query()
241 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
241 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
242 cls.HOOK_REPO_SIZE,
242 cls.HOOK_REPO_SIZE,
243 cls.HOOK_PUSH, cls.HOOK_PULL]))
243 cls.HOOK_PUSH, cls.HOOK_PULL]))
244 q = q.filter(cls.ui_section == 'hooks')
244 q = q.filter(cls.ui_section == 'hooks')
245 return q.all()
245 return q.all()
246
246
247 @classmethod
247 @classmethod
248 def create_or_update_hook(cls, key, val):
248 def create_or_update_hook(cls, key, val):
249 new_ui = cls.get_by_key(key).scalar() or cls()
249 new_ui = cls.get_by_key(key).scalar() or cls()
250 new_ui.ui_section = 'hooks'
250 new_ui.ui_section = 'hooks'
251 new_ui.ui_active = True
251 new_ui.ui_active = True
252 new_ui.ui_key = key
252 new_ui.ui_key = key
253 new_ui.ui_value = val
253 new_ui.ui_value = val
254
254
255 Session().add(new_ui)
255 Session().add(new_ui)
256 Session().commit()
256 Session().commit()
257
257
258
258
259 class User(Base, BaseModel):
259 class User(Base, BaseModel):
260 __tablename__ = 'users'
260 __tablename__ = 'users'
261 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
261 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
262 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
265 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
266 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
266 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
267 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
270 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
271 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273
273
274 user_log = relationship('UserLog', cascade='all')
274 user_log = relationship('UserLog', cascade='all')
275 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
275 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
276
276
277 repositories = relationship('Repository')
277 repositories = relationship('Repository')
278 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
278 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
279 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
279 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
280
280
281 group_member = relationship('UsersGroupMember', cascade='all')
281 group_member = relationship('UsersGroupMember', cascade='all')
282
282
283 notifications = relationship('UserNotification',)
283 notifications = relationship('UserNotification',)
284
284
285 @property
285 @property
286 def full_name(self):
286 def full_name(self):
287 return '%s %s' % (self.name, self.lastname)
287 return '%s %s' % (self.name, self.lastname)
288
288
289 @property
289 @property
290 def full_contact(self):
290 def full_contact(self):
291 return '%s %s <%s>' % (self.name, self.lastname, self.email)
291 return '%s %s <%s>' % (self.name, self.lastname, self.email)
292
292
293 @property
293 @property
294 def short_contact(self):
294 def short_contact(self):
295 return '%s %s' % (self.name, self.lastname)
295 return '%s %s' % (self.name, self.lastname)
296
296
297 @property
297 @property
298 def is_admin(self):
298 def is_admin(self):
299 return self.admin
299 return self.admin
300
300
301 def __repr__(self):
301 def __repr__(self):
302 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
302 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
303 self.user_id, self.username)
303 self.user_id, self.username)
304
304
305
305
306 @classmethod
306 @classmethod
307 def get_by_username(cls, username, case_insensitive=False, cache=False):
307 def get_by_username(cls, username, case_insensitive=False, cache=False):
308 if case_insensitive:
308 if case_insensitive:
309 q = cls.query().filter(cls.username.ilike(username))
309 q = cls.query().filter(cls.username.ilike(username))
310 else:
310 else:
311 q = cls.query().filter(cls.username == username)
311 q = cls.query().filter(cls.username == username)
312
312
313 if cache:
313 if cache:
314 q = q.options(FromCache("sql_cache_short",
314 q = q.options(FromCache("sql_cache_short",
315 "get_user_%s" % username))
315 "get_user_%s" % username))
316 return q.scalar()
316 return q.scalar()
317
317
318 @classmethod
318 @classmethod
319 def get_by_api_key(cls, api_key, cache=False):
319 def get_by_api_key(cls, api_key, cache=False):
320 q = cls.query().filter(cls.api_key == api_key)
320 q = cls.query().filter(cls.api_key == api_key)
321
321
322 if cache:
322 if cache:
323 q = q.options(FromCache("sql_cache_short",
323 q = q.options(FromCache("sql_cache_short",
324 "get_api_key_%s" % api_key))
324 "get_api_key_%s" % api_key))
325 return q.scalar()
325 return q.scalar()
326
326
327 @classmethod
327 @classmethod
328 def get_by_email(cls, email, cache=False):
328 def get_by_email(cls, email, cache=False):
329 q = cls.query().filter(cls.email == email)
329 q = cls.query().filter(cls.email == email)
330
330
331 if cache:
331 if cache:
332 q = q.options(FromCache("sql_cache_short",
332 q = q.options(FromCache("sql_cache_short",
333 "get_api_key_%s" % email))
333 "get_api_key_%s" % email))
334 return q.scalar()
334 return q.scalar()
335
335
336 def update_lastlogin(self):
336 def update_lastlogin(self):
337 """Update user lastlogin"""
337 """Update user lastlogin"""
338
338
339 self.last_login = datetime.datetime.now()
339 self.last_login = datetime.datetime.now()
340 Session().add(self)
340 Session().add(self)
341 Session().commit()
341 Session().commit()
342 log.debug('updated user %s lastlogin', self.username)
342 log.debug('updated user %s lastlogin', self.username)
343
343
344
344
345 class UserLog(Base, BaseModel):
345 class UserLog(Base, BaseModel):
346 __tablename__ = 'user_logs'
346 __tablename__ = 'user_logs'
347 __table_args__ = {'extend_existing':True}
347 __table_args__ = {'extend_existing':True}
348 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
348 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
349 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
349 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
350 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
350 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
351 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
351 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
353 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
354 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
354 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
355
355
356 @property
356 @property
357 def action_as_day(self):
357 def action_as_day(self):
358 return datetime.date(*self.action_date.timetuple()[:3])
358 return datetime.date(*self.action_date.timetuple()[:3])
359
359
360 user = relationship('User')
360 user = relationship('User')
361 repository = relationship('Repository')
361 repository = relationship('Repository',cascade='')
362
362
363
363
364 class UsersGroup(Base, BaseModel):
364 class UsersGroup(Base, BaseModel):
365 __tablename__ = 'users_groups'
365 __tablename__ = 'users_groups'
366 __table_args__ = {'extend_existing':True}
366 __table_args__ = {'extend_existing':True}
367
367
368 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
368 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
369 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
369 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
370 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
370 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
371
371
372 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
372 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
373
373
374 def __repr__(self):
374 def __repr__(self):
375 return '<userGroup(%s)>' % (self.users_group_name)
375 return '<userGroup(%s)>' % (self.users_group_name)
376
376
377 @classmethod
377 @classmethod
378 def get_by_group_name(cls, group_name, cache=False,
378 def get_by_group_name(cls, group_name, cache=False,
379 case_insensitive=False):
379 case_insensitive=False):
380 if case_insensitive:
380 if case_insensitive:
381 q = cls.query().filter(cls.users_group_name.ilike(group_name))
381 q = cls.query().filter(cls.users_group_name.ilike(group_name))
382 else:
382 else:
383 q = cls.query().filter(cls.users_group_name == group_name)
383 q = cls.query().filter(cls.users_group_name == group_name)
384 if cache:
384 if cache:
385 q = q.options(FromCache("sql_cache_short",
385 q = q.options(FromCache("sql_cache_short",
386 "get_user_%s" % group_name))
386 "get_user_%s" % group_name))
387 return q.scalar()
387 return q.scalar()
388
388
389
389
390 @classmethod
390 @classmethod
391 def get(cls, users_group_id, cache=False):
391 def get(cls, users_group_id, cache=False):
392 users_group = cls.query()
392 users_group = cls.query()
393 if cache:
393 if cache:
394 users_group = users_group.options(FromCache("sql_cache_short",
394 users_group = users_group.options(FromCache("sql_cache_short",
395 "get_users_group_%s" % users_group_id))
395 "get_users_group_%s" % users_group_id))
396 return users_group.get(users_group_id)
396 return users_group.get(users_group_id)
397
397
398 @classmethod
398 @classmethod
399 def create(cls, form_data):
399 def create(cls, form_data):
400 try:
400 try:
401 new_users_group = cls()
401 new_users_group = cls()
402 for k, v in form_data.items():
402 for k, v in form_data.items():
403 setattr(new_users_group, k, v)
403 setattr(new_users_group, k, v)
404
404
405 Session().add(new_users_group)
405 Session().add(new_users_group)
406 Session().commit()
406 Session().commit()
407 return new_users_group
407 return new_users_group
408 except:
408 except:
409 log.error(traceback.format_exc())
409 log.error(traceback.format_exc())
410 Session().rollback()
410 Session().rollback()
411 raise
411 raise
412
412
413 @classmethod
413 @classmethod
414 def update(cls, users_group_id, form_data):
414 def update(cls, users_group_id, form_data):
415
415
416 try:
416 try:
417 users_group = cls.get(users_group_id, cache=False)
417 users_group = cls.get(users_group_id, cache=False)
418
418
419 for k, v in form_data.items():
419 for k, v in form_data.items():
420 if k == 'users_group_members':
420 if k == 'users_group_members':
421 users_group.members = []
421 users_group.members = []
422 Session().flush()
422 Session().flush()
423 members_list = []
423 members_list = []
424 if v:
424 if v:
425 v = [v] if isinstance(v, basestring) else v
425 v = [v] if isinstance(v, basestring) else v
426 for u_id in set(v):
426 for u_id in set(v):
427 member = UsersGroupMember(users_group_id, u_id)
427 member = UsersGroupMember(users_group_id, u_id)
428 members_list.append(member)
428 members_list.append(member)
429 setattr(users_group, 'members', members_list)
429 setattr(users_group, 'members', members_list)
430 setattr(users_group, k, v)
430 setattr(users_group, k, v)
431
431
432 Session().add(users_group)
432 Session().add(users_group)
433 Session().commit()
433 Session().commit()
434 except:
434 except:
435 log.error(traceback.format_exc())
435 log.error(traceback.format_exc())
436 Session().rollback()
436 Session().rollback()
437 raise
437 raise
438
438
439 @classmethod
439 @classmethod
440 def delete(cls, users_group_id):
440 def delete(cls, users_group_id):
441 try:
441 try:
442
442
443 # check if this group is not assigned to repo
443 # check if this group is not assigned to repo
444 assigned_groups = UsersGroupRepoToPerm.query()\
444 assigned_groups = UsersGroupRepoToPerm.query()\
445 .filter(UsersGroupRepoToPerm.users_group_id ==
445 .filter(UsersGroupRepoToPerm.users_group_id ==
446 users_group_id).all()
446 users_group_id).all()
447
447
448 if assigned_groups:
448 if assigned_groups:
449 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
449 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
450 assigned_groups)
450 assigned_groups)
451
451
452 users_group = cls.get(users_group_id, cache=False)
452 users_group = cls.get(users_group_id, cache=False)
453 Session().delete(users_group)
453 Session().delete(users_group)
454 Session().commit()
454 Session().commit()
455 except:
455 except:
456 log.error(traceback.format_exc())
456 log.error(traceback.format_exc())
457 Session().rollback()
457 Session().rollback()
458 raise
458 raise
459
459
460 class UsersGroupMember(Base, BaseModel):
460 class UsersGroupMember(Base, BaseModel):
461 __tablename__ = 'users_groups_members'
461 __tablename__ = 'users_groups_members'
462 __table_args__ = {'extend_existing':True}
462 __table_args__ = {'extend_existing':True}
463
463
464 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
464 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
465 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
465 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
466 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
467
467
468 user = relationship('User', lazy='joined')
468 user = relationship('User', lazy='joined')
469 users_group = relationship('UsersGroup')
469 users_group = relationship('UsersGroup')
470
470
471 def __init__(self, gr_id='', u_id=''):
471 def __init__(self, gr_id='', u_id=''):
472 self.users_group_id = gr_id
472 self.users_group_id = gr_id
473 self.user_id = u_id
473 self.user_id = u_id
474
474
475 @staticmethod
475 @staticmethod
476 def add_user_to_group(group, user):
476 def add_user_to_group(group, user):
477 ugm = UsersGroupMember()
477 ugm = UsersGroupMember()
478 ugm.users_group = group
478 ugm.users_group = group
479 ugm.user = user
479 ugm.user = user
480 Session().add(ugm)
480 Session().add(ugm)
481 Session().commit()
481 Session().commit()
482 return ugm
482 return ugm
483
483
484 class Repository(Base, BaseModel):
484 class Repository(Base, BaseModel):
485 __tablename__ = 'repositories'
485 __tablename__ = 'repositories'
486 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
486 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
487
487
488 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
488 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
489 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
489 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
490 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
490 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
491 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
491 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
492 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
493 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
493 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
494 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
494 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
495 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
495 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
496 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
496 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
497 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
497 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
498
498
499 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
499 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
500 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
500 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
501
501
502
502
503 user = relationship('User')
503 user = relationship('User')
504 fork = relationship('Repository', remote_side=repo_id)
504 fork = relationship('Repository', remote_side=repo_id)
505 group = relationship('RepoGroup')
505 group = relationship('RepoGroup')
506 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
506 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
507 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
507 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
508 stats = relationship('Statistics', cascade='all', uselist=False)
508 stats = relationship('Statistics', cascade='all', uselist=False)
509
509
510 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
510 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
511
511
512 logs = relationship('UserLog', cascade='all')
512 logs = relationship('UserLog')
513
513
514 def __repr__(self):
514 def __repr__(self):
515 return "<%s('%s:%s')>" % (self.__class__.__name__,
515 return "<%s('%s:%s')>" % (self.__class__.__name__,
516 self.repo_id, self.repo_name)
516 self.repo_id, self.repo_name)
517
517
518 @classmethod
518 @classmethod
519 def url_sep(cls):
519 def url_sep(cls):
520 return '/'
520 return '/'
521
521
522 @classmethod
522 @classmethod
523 def get_by_repo_name(cls, repo_name):
523 def get_by_repo_name(cls, repo_name):
524 q = Session().query(cls).filter(cls.repo_name == repo_name)
524 q = Session().query(cls).filter(cls.repo_name == repo_name)
525 q = q.options(joinedload(Repository.fork))\
525 q = q.options(joinedload(Repository.fork))\
526 .options(joinedload(Repository.user))\
526 .options(joinedload(Repository.user))\
527 .options(joinedload(Repository.group))
527 .options(joinedload(Repository.group))
528 return q.one()
528 return q.one()
529
529
530 @classmethod
530 @classmethod
531 def get_repo_forks(cls, repo_id):
531 def get_repo_forks(cls, repo_id):
532 return cls.query().filter(Repository.fork_id == repo_id)
532 return cls.query().filter(Repository.fork_id == repo_id)
533
533
534 @classmethod
534 @classmethod
535 def base_path(cls):
535 def base_path(cls):
536 """
536 """
537 Returns base path when all repos are stored
537 Returns base path when all repos are stored
538
538
539 :param cls:
539 :param cls:
540 """
540 """
541 q = Session().query(RhodeCodeUi)\
541 q = Session().query(RhodeCodeUi)\
542 .filter(RhodeCodeUi.ui_key == cls.url_sep())
542 .filter(RhodeCodeUi.ui_key == cls.url_sep())
543 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
543 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
544 return q.one().ui_value
544 return q.one().ui_value
545
545
546 @property
546 @property
547 def just_name(self):
547 def just_name(self):
548 return self.repo_name.split(Repository.url_sep())[-1]
548 return self.repo_name.split(Repository.url_sep())[-1]
549
549
550 @property
550 @property
551 def groups_with_parents(self):
551 def groups_with_parents(self):
552 groups = []
552 groups = []
553 if self.group is None:
553 if self.group is None:
554 return groups
554 return groups
555
555
556 cur_gr = self.group
556 cur_gr = self.group
557 groups.insert(0, cur_gr)
557 groups.insert(0, cur_gr)
558 while 1:
558 while 1:
559 gr = getattr(cur_gr, 'parent_group', None)
559 gr = getattr(cur_gr, 'parent_group', None)
560 cur_gr = cur_gr.parent_group
560 cur_gr = cur_gr.parent_group
561 if gr is None:
561 if gr is None:
562 break
562 break
563 groups.insert(0, gr)
563 groups.insert(0, gr)
564
564
565 return groups
565 return groups
566
566
567 @property
567 @property
568 def groups_and_repo(self):
568 def groups_and_repo(self):
569 return self.groups_with_parents, self.just_name
569 return self.groups_with_parents, self.just_name
570
570
571 @LazyProperty
571 @LazyProperty
572 def repo_path(self):
572 def repo_path(self):
573 """
573 """
574 Returns base full path for that repository means where it actually
574 Returns base full path for that repository means where it actually
575 exists on a filesystem
575 exists on a filesystem
576 """
576 """
577 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
577 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
578 Repository.url_sep())
578 Repository.url_sep())
579 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
579 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
580 return q.one().ui_value
580 return q.one().ui_value
581
581
582 @property
582 @property
583 def repo_full_path(self):
583 def repo_full_path(self):
584 p = [self.repo_path]
584 p = [self.repo_path]
585 # we need to split the name by / since this is how we store the
585 # we need to split the name by / since this is how we store the
586 # names in the database, but that eventually needs to be converted
586 # names in the database, but that eventually needs to be converted
587 # into a valid system path
587 # into a valid system path
588 p += self.repo_name.split(Repository.url_sep())
588 p += self.repo_name.split(Repository.url_sep())
589 return os.path.join(*p)
589 return os.path.join(*p)
590
590
591 def get_new_name(self, repo_name):
591 def get_new_name(self, repo_name):
592 """
592 """
593 returns new full repository name based on assigned group and new new
593 returns new full repository name based on assigned group and new new
594
594
595 :param group_name:
595 :param group_name:
596 """
596 """
597 path_prefix = self.group.full_path_splitted if self.group else []
597 path_prefix = self.group.full_path_splitted if self.group else []
598 return Repository.url_sep().join(path_prefix + [repo_name])
598 return Repository.url_sep().join(path_prefix + [repo_name])
599
599
600 @property
600 @property
601 def _ui(self):
601 def _ui(self):
602 """
602 """
603 Creates an db based ui object for this repository
603 Creates an db based ui object for this repository
604 """
604 """
605 from mercurial import ui
605 from mercurial import ui
606 from mercurial import config
606 from mercurial import config
607 baseui = ui.ui()
607 baseui = ui.ui()
608
608
609 #clean the baseui object
609 #clean the baseui object
610 baseui._ocfg = config.config()
610 baseui._ocfg = config.config()
611 baseui._ucfg = config.config()
611 baseui._ucfg = config.config()
612 baseui._tcfg = config.config()
612 baseui._tcfg = config.config()
613
613
614
614
615 ret = RhodeCodeUi.query()\
615 ret = RhodeCodeUi.query()\
616 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
616 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
617
617
618 hg_ui = ret
618 hg_ui = ret
619 for ui_ in hg_ui:
619 for ui_ in hg_ui:
620 if ui_.ui_active:
620 if ui_.ui_active:
621 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
621 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
622 ui_.ui_key, ui_.ui_value)
622 ui_.ui_key, ui_.ui_value)
623 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
623 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
624
624
625 return baseui
625 return baseui
626
626
627 @classmethod
627 @classmethod
628 def is_valid(cls, repo_name):
628 def is_valid(cls, repo_name):
629 """
629 """
630 returns True if given repo name is a valid filesystem repository
630 returns True if given repo name is a valid filesystem repository
631
631
632 @param cls:
632 @param cls:
633 @param repo_name:
633 @param repo_name:
634 """
634 """
635 from rhodecode.lib.utils import is_valid_repo
635 from rhodecode.lib.utils import is_valid_repo
636
636
637 return is_valid_repo(repo_name, cls.base_path())
637 return is_valid_repo(repo_name, cls.base_path())
638
638
639
639
640 #==========================================================================
640 #==========================================================================
641 # SCM PROPERTIES
641 # SCM PROPERTIES
642 #==========================================================================
642 #==========================================================================
643
643
644 def get_changeset(self, rev):
644 def get_changeset(self, rev):
645 return get_changeset_safe(self.scm_instance, rev)
645 return get_changeset_safe(self.scm_instance, rev)
646
646
647 @property
647 @property
648 def tip(self):
648 def tip(self):
649 return self.get_changeset('tip')
649 return self.get_changeset('tip')
650
650
651 @property
651 @property
652 def author(self):
652 def author(self):
653 return self.tip.author
653 return self.tip.author
654
654
655 @property
655 @property
656 def last_change(self):
656 def last_change(self):
657 return self.scm_instance.last_change
657 return self.scm_instance.last_change
658
658
659 #==========================================================================
659 #==========================================================================
660 # SCM CACHE INSTANCE
660 # SCM CACHE INSTANCE
661 #==========================================================================
661 #==========================================================================
662
662
663 @property
663 @property
664 def invalidate(self):
664 def invalidate(self):
665 return CacheInvalidation.invalidate(self.repo_name)
665 return CacheInvalidation.invalidate(self.repo_name)
666
666
667 def set_invalidate(self):
667 def set_invalidate(self):
668 """
668 """
669 set a cache for invalidation for this instance
669 set a cache for invalidation for this instance
670 """
670 """
671 CacheInvalidation.set_invalidate(self.repo_name)
671 CacheInvalidation.set_invalidate(self.repo_name)
672
672
673 @LazyProperty
673 @LazyProperty
674 def scm_instance(self):
674 def scm_instance(self):
675 return self.__get_instance()
675 return self.__get_instance()
676
676
677 @property
677 @property
678 def scm_instance_cached(self):
678 def scm_instance_cached(self):
679 @cache_region('long_term')
679 @cache_region('long_term')
680 def _c(repo_name):
680 def _c(repo_name):
681 return self.__get_instance()
681 return self.__get_instance()
682 rn = self.repo_name
682 rn = self.repo_name
683 log.debug('Getting cached instance of repo')
683 log.debug('Getting cached instance of repo')
684 inv = self.invalidate
684 inv = self.invalidate
685 if inv is not None:
685 if inv is not None:
686 region_invalidate(_c, None, rn)
686 region_invalidate(_c, None, rn)
687 # update our cache
687 # update our cache
688 CacheInvalidation.set_valid(inv.cache_key)
688 CacheInvalidation.set_valid(inv.cache_key)
689 return _c(rn)
689 return _c(rn)
690
690
691 def __get_instance(self):
691 def __get_instance(self):
692 repo_full_path = self.repo_full_path
692 repo_full_path = self.repo_full_path
693 try:
693 try:
694 alias = get_scm(repo_full_path)[0]
694 alias = get_scm(repo_full_path)[0]
695 log.debug('Creating instance of %s repository', alias)
695 log.debug('Creating instance of %s repository', alias)
696 backend = get_backend(alias)
696 backend = get_backend(alias)
697 except VCSError:
697 except VCSError:
698 log.error(traceback.format_exc())
698 log.error(traceback.format_exc())
699 log.error('Perhaps this repository is in db and not in '
699 log.error('Perhaps this repository is in db and not in '
700 'filesystem run rescan repositories with '
700 'filesystem run rescan repositories with '
701 '"destroy old data " option from admin panel')
701 '"destroy old data " option from admin panel')
702 return
702 return
703
703
704 if alias == 'hg':
704 if alias == 'hg':
705 repo = backend(safe_str(repo_full_path), create=False,
705 repo = backend(safe_str(repo_full_path), create=False,
706 baseui=self._ui)
706 baseui=self._ui)
707 # skip hidden web repository
707 # skip hidden web repository
708 if repo._get_hidden():
708 if repo._get_hidden():
709 return
709 return
710 else:
710 else:
711 repo = backend(repo_full_path, create=False)
711 repo = backend(repo_full_path, create=False)
712
712
713 return repo
713 return repo
714
714
715
715
716 class RepoGroup(Base, BaseModel):
716 class RepoGroup(Base, BaseModel):
717 __tablename__ = 'groups'
717 __tablename__ = 'groups'
718 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
718 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
719 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
719 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
720 __mapper_args__ = {'order_by':'group_name'}
720 __mapper_args__ = {'order_by':'group_name'}
721
721
722 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
722 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
723 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
723 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
724 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
724 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
725 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
725 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
726
726
727 parent_group = relationship('RepoGroup', remote_side=group_id)
727 parent_group = relationship('RepoGroup', remote_side=group_id)
728
728
729
729
730 def __init__(self, group_name='', parent_group=None):
730 def __init__(self, group_name='', parent_group=None):
731 self.group_name = group_name
731 self.group_name = group_name
732 self.parent_group = parent_group
732 self.parent_group = parent_group
733
733
734 def __repr__(self):
734 def __repr__(self):
735 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
735 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
736 self.group_name)
736 self.group_name)
737
737
738 @classmethod
738 @classmethod
739 def groups_choices(cls):
739 def groups_choices(cls):
740 from webhelpers.html import literal as _literal
740 from webhelpers.html import literal as _literal
741 repo_groups = [('', '')]
741 repo_groups = [('', '')]
742 sep = ' &raquo; '
742 sep = ' &raquo; '
743 _name = lambda k: _literal(sep.join(k))
743 _name = lambda k: _literal(sep.join(k))
744
744
745 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
745 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
746 for x in cls.query().all()])
746 for x in cls.query().all()])
747
747
748 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
748 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
749 return repo_groups
749 return repo_groups
750
750
751 @classmethod
751 @classmethod
752 def url_sep(cls):
752 def url_sep(cls):
753 return '/'
753 return '/'
754
754
755 @classmethod
755 @classmethod
756 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
756 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
757 if case_insensitive:
757 if case_insensitive:
758 gr = cls.query()\
758 gr = cls.query()\
759 .filter(cls.group_name.ilike(group_name))
759 .filter(cls.group_name.ilike(group_name))
760 else:
760 else:
761 gr = cls.query()\
761 gr = cls.query()\
762 .filter(cls.group_name == group_name)
762 .filter(cls.group_name == group_name)
763 if cache:
763 if cache:
764 gr = gr.options(FromCache("sql_cache_short",
764 gr = gr.options(FromCache("sql_cache_short",
765 "get_group_%s" % group_name))
765 "get_group_%s" % group_name))
766 return gr.scalar()
766 return gr.scalar()
767
767
768 @property
768 @property
769 def parents(self):
769 def parents(self):
770 parents_recursion_limit = 5
770 parents_recursion_limit = 5
771 groups = []
771 groups = []
772 if self.parent_group is None:
772 if self.parent_group is None:
773 return groups
773 return groups
774 cur_gr = self.parent_group
774 cur_gr = self.parent_group
775 groups.insert(0, cur_gr)
775 groups.insert(0, cur_gr)
776 cnt = 0
776 cnt = 0
777 while 1:
777 while 1:
778 cnt += 1
778 cnt += 1
779 gr = getattr(cur_gr, 'parent_group', None)
779 gr = getattr(cur_gr, 'parent_group', None)
780 cur_gr = cur_gr.parent_group
780 cur_gr = cur_gr.parent_group
781 if gr is None:
781 if gr is None:
782 break
782 break
783 if cnt == parents_recursion_limit:
783 if cnt == parents_recursion_limit:
784 # this will prevent accidental infinit loops
784 # this will prevent accidental infinit loops
785 log.error('group nested more than %s' %
785 log.error('group nested more than %s' %
786 parents_recursion_limit)
786 parents_recursion_limit)
787 break
787 break
788
788
789 groups.insert(0, gr)
789 groups.insert(0, gr)
790 return groups
790 return groups
791
791
792 @property
792 @property
793 def children(self):
793 def children(self):
794 return RepoGroup.query().filter(RepoGroup.parent_group == self)
794 return RepoGroup.query().filter(RepoGroup.parent_group == self)
795
795
796 @property
796 @property
797 def name(self):
797 def name(self):
798 return self.group_name.split(RepoGroup.url_sep())[-1]
798 return self.group_name.split(RepoGroup.url_sep())[-1]
799
799
800 @property
800 @property
801 def full_path(self):
801 def full_path(self):
802 return self.group_name
802 return self.group_name
803
803
804 @property
804 @property
805 def full_path_splitted(self):
805 def full_path_splitted(self):
806 return self.group_name.split(RepoGroup.url_sep())
806 return self.group_name.split(RepoGroup.url_sep())
807
807
808 @property
808 @property
809 def repositories(self):
809 def repositories(self):
810 return Repository.query().filter(Repository.group == self)
810 return Repository.query().filter(Repository.group == self)
811
811
812 @property
812 @property
813 def repositories_recursive_count(self):
813 def repositories_recursive_count(self):
814 cnt = self.repositories.count()
814 cnt = self.repositories.count()
815
815
816 def children_count(group):
816 def children_count(group):
817 cnt = 0
817 cnt = 0
818 for child in group.children:
818 for child in group.children:
819 cnt += child.repositories.count()
819 cnt += child.repositories.count()
820 cnt += children_count(child)
820 cnt += children_count(child)
821 return cnt
821 return cnt
822
822
823 return cnt + children_count(self)
823 return cnt + children_count(self)
824
824
825
825
826 def get_new_name(self, group_name):
826 def get_new_name(self, group_name):
827 """
827 """
828 returns new full group name based on parent and new name
828 returns new full group name based on parent and new name
829
829
830 :param group_name:
830 :param group_name:
831 """
831 """
832 path_prefix = (self.parent_group.full_path_splitted if
832 path_prefix = (self.parent_group.full_path_splitted if
833 self.parent_group else [])
833 self.parent_group else [])
834 return RepoGroup.url_sep().join(path_prefix + [group_name])
834 return RepoGroup.url_sep().join(path_prefix + [group_name])
835
835
836
836
837 class Permission(Base, BaseModel):
837 class Permission(Base, BaseModel):
838 __tablename__ = 'permissions'
838 __tablename__ = 'permissions'
839 __table_args__ = {'extend_existing':True}
839 __table_args__ = {'extend_existing':True}
840 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
840 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
841 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
841 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
842 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
842 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
843
843
844 def __repr__(self):
844 def __repr__(self):
845 return "<%s('%s:%s')>" % (self.__class__.__name__,
845 return "<%s('%s:%s')>" % (self.__class__.__name__,
846 self.permission_id, self.permission_name)
846 self.permission_id, self.permission_name)
847
847
848 @classmethod
848 @classmethod
849 def get_by_key(cls, key):
849 def get_by_key(cls, key):
850 return cls.query().filter(cls.permission_name == key).scalar()
850 return cls.query().filter(cls.permission_name == key).scalar()
851
851
852 @classmethod
852 @classmethod
853 def get_default_perms(cls, default_user_id):
853 def get_default_perms(cls, default_user_id):
854 q = Session().query(UserRepoToPerm, Repository, cls)\
854 q = Session().query(UserRepoToPerm, Repository, cls)\
855 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
855 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
856 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
856 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
857 .filter(UserRepoToPerm.user_id == default_user_id)
857 .filter(UserRepoToPerm.user_id == default_user_id)
858
858
859 return q.all()
859 return q.all()
860
860
861
861
862 class UserRepoToPerm(Base, BaseModel):
862 class UserRepoToPerm(Base, BaseModel):
863 __tablename__ = 'repo_to_perm'
863 __tablename__ = 'repo_to_perm'
864 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
864 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
865 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
865 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
866 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
866 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
867 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
867 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
868 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
868 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
869
869
870 user = relationship('User')
870 user = relationship('User')
871 permission = relationship('Permission')
871 permission = relationship('Permission')
872 repository = relationship('Repository')
872 repository = relationship('Repository')
873
873
874 @classmethod
874 @classmethod
875 def create(cls, user, repository, permission):
875 def create(cls, user, repository, permission):
876 n = cls()
876 n = cls()
877 n.user = user
877 n.user = user
878 n.repository = repository
878 n.repository = repository
879 n.permission = permission
879 n.permission = permission
880 Session().add(n)
880 Session().add(n)
881 return n
881 return n
882
882
883 def __repr__(self):
883 def __repr__(self):
884 return '<user:%s => %s >' % (self.user, self.repository)
884 return '<user:%s => %s >' % (self.user, self.repository)
885
885
886 class UserToPerm(Base, BaseModel):
886 class UserToPerm(Base, BaseModel):
887 __tablename__ = 'user_to_perm'
887 __tablename__ = 'user_to_perm'
888 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
888 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
889 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
889 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
890 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
890 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
891 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
891 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
892
892
893 user = relationship('User')
893 user = relationship('User')
894 permission = relationship('Permission', lazy='joined')
894 permission = relationship('Permission', lazy='joined')
895
895
896 @classmethod
896 @classmethod
897 def has_perm(cls, user_id, perm):
897 def has_perm(cls, user_id, perm):
898 if not isinstance(perm, Permission):
898 if not isinstance(perm, Permission):
899 raise Exception('perm needs to be an instance of Permission class')
899 raise Exception('perm needs to be an instance of Permission class')
900
900
901 return cls.query().filter(cls.user_id == user_id)\
901 return cls.query().filter(cls.user_id == user_id)\
902 .filter(cls.permission == perm).scalar() is not None
902 .filter(cls.permission == perm).scalar() is not None
903
903
904 @classmethod
904 @classmethod
905 def grant_perm(cls, user_id, perm):
905 def grant_perm(cls, user_id, perm):
906 if not isinstance(perm, Permission):
906 if not isinstance(perm, Permission):
907 raise Exception('perm needs to be an instance of Permission class')
907 raise Exception('perm needs to be an instance of Permission class')
908
908
909 new = cls()
909 new = cls()
910 new.user_id = user_id
910 new.user_id = user_id
911 new.permission = perm
911 new.permission = perm
912 try:
912 try:
913 Session().add(new)
913 Session().add(new)
914 Session().commit()
914 Session().commit()
915 except:
915 except:
916 Session().rollback()
916 Session().rollback()
917
917
918
918
919 @classmethod
919 @classmethod
920 def revoke_perm(cls, user_id, perm):
920 def revoke_perm(cls, user_id, perm):
921 if not isinstance(perm, Permission):
921 if not isinstance(perm, Permission):
922 raise Exception('perm needs to be an instance of Permission class')
922 raise Exception('perm needs to be an instance of Permission class')
923
923
924 try:
924 try:
925 obj = cls.query().filter(cls.user_id == user_id)\
925 obj = cls.query().filter(cls.user_id == user_id)\
926 .filter(cls.permission == perm).one()
926 .filter(cls.permission == perm).one()
927 Session().delete(obj)
927 Session().delete(obj)
928 Session().commit()
928 Session().commit()
929 except:
929 except:
930 Session().rollback()
930 Session().rollback()
931
931
932 class UsersGroupRepoToPerm(Base, BaseModel):
932 class UsersGroupRepoToPerm(Base, BaseModel):
933 __tablename__ = 'users_group_repo_to_perm'
933 __tablename__ = 'users_group_repo_to_perm'
934 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
934 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
935 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
935 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
936 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
936 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
937 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
937 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
938 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
938 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
939
939
940 users_group = relationship('UsersGroup')
940 users_group = relationship('UsersGroup')
941 permission = relationship('Permission')
941 permission = relationship('Permission')
942 repository = relationship('Repository')
942 repository = relationship('Repository')
943
943
944 @classmethod
944 @classmethod
945 def create(cls, users_group, repository, permission):
945 def create(cls, users_group, repository, permission):
946 n = cls()
946 n = cls()
947 n.users_group = users_group
947 n.users_group = users_group
948 n.repository = repository
948 n.repository = repository
949 n.permission = permission
949 n.permission = permission
950 Session().add(n)
950 Session().add(n)
951 return n
951 return n
952
952
953 def __repr__(self):
953 def __repr__(self):
954 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
954 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
955
955
956 class UsersGroupToPerm(Base, BaseModel):
956 class UsersGroupToPerm(Base, BaseModel):
957 __tablename__ = 'users_group_to_perm'
957 __tablename__ = 'users_group_to_perm'
958 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
958 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
959 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
959 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
960 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
960 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
961
961
962 users_group = relationship('UsersGroup')
962 users_group = relationship('UsersGroup')
963 permission = relationship('Permission')
963 permission = relationship('Permission')
964
964
965
965
966 @classmethod
966 @classmethod
967 def has_perm(cls, users_group_id, perm):
967 def has_perm(cls, users_group_id, perm):
968 if not isinstance(perm, Permission):
968 if not isinstance(perm, Permission):
969 raise Exception('perm needs to be an instance of Permission class')
969 raise Exception('perm needs to be an instance of Permission class')
970
970
971 return cls.query().filter(cls.users_group_id ==
971 return cls.query().filter(cls.users_group_id ==
972 users_group_id)\
972 users_group_id)\
973 .filter(cls.permission == perm)\
973 .filter(cls.permission == perm)\
974 .scalar() is not None
974 .scalar() is not None
975
975
976 @classmethod
976 @classmethod
977 def grant_perm(cls, users_group_id, perm):
977 def grant_perm(cls, users_group_id, perm):
978 if not isinstance(perm, Permission):
978 if not isinstance(perm, Permission):
979 raise Exception('perm needs to be an instance of Permission class')
979 raise Exception('perm needs to be an instance of Permission class')
980
980
981 new = cls()
981 new = cls()
982 new.users_group_id = users_group_id
982 new.users_group_id = users_group_id
983 new.permission = perm
983 new.permission = perm
984 try:
984 try:
985 Session().add(new)
985 Session().add(new)
986 Session().commit()
986 Session().commit()
987 except:
987 except:
988 Session().rollback()
988 Session().rollback()
989
989
990
990
991 @classmethod
991 @classmethod
992 def revoke_perm(cls, users_group_id, perm):
992 def revoke_perm(cls, users_group_id, perm):
993 if not isinstance(perm, Permission):
993 if not isinstance(perm, Permission):
994 raise Exception('perm needs to be an instance of Permission class')
994 raise Exception('perm needs to be an instance of Permission class')
995
995
996 try:
996 try:
997 obj = cls.query().filter(cls.users_group_id == users_group_id)\
997 obj = cls.query().filter(cls.users_group_id == users_group_id)\
998 .filter(cls.permission == perm).one()
998 .filter(cls.permission == perm).one()
999 Session().delete(obj)
999 Session().delete(obj)
1000 Session().commit()
1000 Session().commit()
1001 except:
1001 except:
1002 Session().rollback()
1002 Session().rollback()
1003
1003
1004
1004
1005 class UserRepoGroupToPerm(Base, BaseModel):
1005 class UserRepoGroupToPerm(Base, BaseModel):
1006 __tablename__ = 'group_to_perm'
1006 __tablename__ = 'group_to_perm'
1007 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
1007 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
1008
1008
1009 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1009 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1010 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1010 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1011 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1011 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1012 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1012 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1013
1013
1014 user = relationship('User')
1014 user = relationship('User')
1015 permission = relationship('Permission')
1015 permission = relationship('Permission')
1016 group = relationship('RepoGroup')
1016 group = relationship('RepoGroup')
1017
1017
1018 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1018 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1019 __tablename__ = 'users_group_repo_group_to_perm'
1019 __tablename__ = 'users_group_repo_group_to_perm'
1020 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
1020 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
1021
1021
1022 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)
1022 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)
1023 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1023 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1024 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1024 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1025 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1025 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1026
1026
1027 users_group = relationship('UsersGroup')
1027 users_group = relationship('UsersGroup')
1028 permission = relationship('Permission')
1028 permission = relationship('Permission')
1029 group = relationship('RepoGroup')
1029 group = relationship('RepoGroup')
1030
1030
1031 class Statistics(Base, BaseModel):
1031 class Statistics(Base, BaseModel):
1032 __tablename__ = 'statistics'
1032 __tablename__ = 'statistics'
1033 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
1033 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
1034 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1034 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1035 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1035 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1036 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1036 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1037 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1037 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1038 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1038 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1039 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1039 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1040
1040
1041 repository = relationship('Repository', single_parent=True)
1041 repository = relationship('Repository', single_parent=True)
1042
1042
1043 class UserFollowing(Base, BaseModel):
1043 class UserFollowing(Base, BaseModel):
1044 __tablename__ = 'user_followings'
1044 __tablename__ = 'user_followings'
1045 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1045 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1046 UniqueConstraint('user_id', 'follows_user_id')
1046 UniqueConstraint('user_id', 'follows_user_id')
1047 , {'extend_existing':True})
1047 , {'extend_existing':True})
1048
1048
1049 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1049 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1050 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1050 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1051 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1051 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1052 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1052 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1053 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1053 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1054
1054
1055 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1055 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1056
1056
1057 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1057 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1058 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1058 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1059
1059
1060
1060
1061 @classmethod
1061 @classmethod
1062 def get_repo_followers(cls, repo_id):
1062 def get_repo_followers(cls, repo_id):
1063 return cls.query().filter(cls.follows_repo_id == repo_id)
1063 return cls.query().filter(cls.follows_repo_id == repo_id)
1064
1064
1065 class CacheInvalidation(Base, BaseModel):
1065 class CacheInvalidation(Base, BaseModel):
1066 __tablename__ = 'cache_invalidation'
1066 __tablename__ = 'cache_invalidation'
1067 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1067 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1068 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1068 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1069 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1069 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1070 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1070 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1071 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1071 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1072
1072
1073
1073
1074 def __init__(self, cache_key, cache_args=''):
1074 def __init__(self, cache_key, cache_args=''):
1075 self.cache_key = cache_key
1075 self.cache_key = cache_key
1076 self.cache_args = cache_args
1076 self.cache_args = cache_args
1077 self.cache_active = False
1077 self.cache_active = False
1078
1078
1079 def __repr__(self):
1079 def __repr__(self):
1080 return "<%s('%s:%s')>" % (self.__class__.__name__,
1080 return "<%s('%s:%s')>" % (self.__class__.__name__,
1081 self.cache_id, self.cache_key)
1081 self.cache_id, self.cache_key)
1082
1082
1083 @classmethod
1083 @classmethod
1084 def invalidate(cls, key):
1084 def invalidate(cls, key):
1085 """
1085 """
1086 Returns Invalidation object if this given key should be invalidated
1086 Returns Invalidation object if this given key should be invalidated
1087 None otherwise. `cache_active = False` means that this cache
1087 None otherwise. `cache_active = False` means that this cache
1088 state is not valid and needs to be invalidated
1088 state is not valid and needs to be invalidated
1089
1089
1090 :param key:
1090 :param key:
1091 """
1091 """
1092 return cls.query()\
1092 return cls.query()\
1093 .filter(CacheInvalidation.cache_key == key)\
1093 .filter(CacheInvalidation.cache_key == key)\
1094 .filter(CacheInvalidation.cache_active == False)\
1094 .filter(CacheInvalidation.cache_active == False)\
1095 .scalar()
1095 .scalar()
1096
1096
1097 @classmethod
1097 @classmethod
1098 def set_invalidate(cls, key):
1098 def set_invalidate(cls, key):
1099 """
1099 """
1100 Mark this Cache key for invalidation
1100 Mark this Cache key for invalidation
1101
1101
1102 :param key:
1102 :param key:
1103 """
1103 """
1104
1104
1105 log.debug('marking %s for invalidation' % key)
1105 log.debug('marking %s for invalidation' % key)
1106 inv_obj = Session().query(cls)\
1106 inv_obj = Session().query(cls)\
1107 .filter(cls.cache_key == key).scalar()
1107 .filter(cls.cache_key == key).scalar()
1108 if inv_obj:
1108 if inv_obj:
1109 inv_obj.cache_active = False
1109 inv_obj.cache_active = False
1110 else:
1110 else:
1111 log.debug('cache key not found in invalidation db -> creating one')
1111 log.debug('cache key not found in invalidation db -> creating one')
1112 inv_obj = CacheInvalidation(key)
1112 inv_obj = CacheInvalidation(key)
1113
1113
1114 try:
1114 try:
1115 Session().add(inv_obj)
1115 Session().add(inv_obj)
1116 Session().commit()
1116 Session().commit()
1117 except Exception:
1117 except Exception:
1118 log.error(traceback.format_exc())
1118 log.error(traceback.format_exc())
1119 Session().rollback()
1119 Session().rollback()
1120
1120
1121 @classmethod
1121 @classmethod
1122 def set_valid(cls, key):
1122 def set_valid(cls, key):
1123 """
1123 """
1124 Mark this cache key as active and currently cached
1124 Mark this cache key as active and currently cached
1125
1125
1126 :param key:
1126 :param key:
1127 """
1127 """
1128 inv_obj = CacheInvalidation.query()\
1128 inv_obj = CacheInvalidation.query()\
1129 .filter(CacheInvalidation.cache_key == key).scalar()
1129 .filter(CacheInvalidation.cache_key == key).scalar()
1130 inv_obj.cache_active = True
1130 inv_obj.cache_active = True
1131 Session().add(inv_obj)
1131 Session().add(inv_obj)
1132 Session().commit()
1132 Session().commit()
1133
1133
1134
1134
1135 class ChangesetComment(Base, BaseModel):
1135 class ChangesetComment(Base, BaseModel):
1136 __tablename__ = 'changeset_comments'
1136 __tablename__ = 'changeset_comments'
1137 __table_args__ = ({'extend_existing':True},)
1137 __table_args__ = ({'extend_existing':True},)
1138 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1138 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1139 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1139 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1140 revision = Column('revision', String(40), nullable=False)
1140 revision = Column('revision', String(40), nullable=False)
1141 line_no = Column('line_no', Unicode(10), nullable=True)
1141 line_no = Column('line_no', Unicode(10), nullable=True)
1142 f_path = Column('f_path', Unicode(1000), nullable=True)
1142 f_path = Column('f_path', Unicode(1000), nullable=True)
1143 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1143 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1144 text = Column('text', Unicode(25000), nullable=False)
1144 text = Column('text', Unicode(25000), nullable=False)
1145 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1145 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1146
1146
1147 author = relationship('User', lazy='joined')
1147 author = relationship('User', lazy='joined')
1148 repo = relationship('Repository')
1148 repo = relationship('Repository')
1149
1149
1150
1150
1151 @classmethod
1151 @classmethod
1152 def get_users(cls, revision):
1152 def get_users(cls, revision):
1153 """
1153 """
1154 Returns user associated with this changesetComment. ie those
1154 Returns user associated with this changesetComment. ie those
1155 who actually commented
1155 who actually commented
1156
1156
1157 :param cls:
1157 :param cls:
1158 :param revision:
1158 :param revision:
1159 """
1159 """
1160 return Session().query(User)\
1160 return Session().query(User)\
1161 .filter(cls.revision == revision)\
1161 .filter(cls.revision == revision)\
1162 .join(ChangesetComment.author).all()
1162 .join(ChangesetComment.author).all()
1163
1163
1164
1164
1165 class Notification(Base, BaseModel):
1165 class Notification(Base, BaseModel):
1166 __tablename__ = 'notifications'
1166 __tablename__ = 'notifications'
1167 __table_args__ = ({'extend_existing':True})
1167 __table_args__ = ({'extend_existing':True})
1168
1168
1169 TYPE_CHANGESET_COMMENT = u'cs_comment'
1169 TYPE_CHANGESET_COMMENT = u'cs_comment'
1170 TYPE_MESSAGE = u'message'
1170 TYPE_MESSAGE = u'message'
1171 TYPE_MENTION = u'mention'
1171 TYPE_MENTION = u'mention'
1172 TYPE_REGISTRATION = u'registration'
1172 TYPE_REGISTRATION = u'registration'
1173
1173
1174 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1174 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1175 subject = Column('subject', Unicode(512), nullable=True)
1175 subject = Column('subject', Unicode(512), nullable=True)
1176 body = Column('body', Unicode(50000), nullable=True)
1176 body = Column('body', Unicode(50000), nullable=True)
1177 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1177 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1178 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1178 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1179 type_ = Column('type', Unicode(256))
1179 type_ = Column('type', Unicode(256))
1180
1180
1181 created_by_user = relationship('User')
1181 created_by_user = relationship('User')
1182 notifications_to_users = relationship('UserNotification', lazy='joined',
1182 notifications_to_users = relationship('UserNotification', lazy='joined',
1183 cascade="all, delete, delete-orphan")
1183 cascade="all, delete, delete-orphan")
1184
1184
1185 @property
1185 @property
1186 def recipients(self):
1186 def recipients(self):
1187 return [x.user for x in UserNotification.query()\
1187 return [x.user for x in UserNotification.query()\
1188 .filter(UserNotification.notification == self).all()]
1188 .filter(UserNotification.notification == self).all()]
1189
1189
1190 @classmethod
1190 @classmethod
1191 def create(cls, created_by, subject, body, recipients, type_=None):
1191 def create(cls, created_by, subject, body, recipients, type_=None):
1192 if type_ is None:
1192 if type_ is None:
1193 type_ = Notification.TYPE_MESSAGE
1193 type_ = Notification.TYPE_MESSAGE
1194
1194
1195 notification = cls()
1195 notification = cls()
1196 notification.created_by_user = created_by
1196 notification.created_by_user = created_by
1197 notification.subject = subject
1197 notification.subject = subject
1198 notification.body = body
1198 notification.body = body
1199 notification.type_ = type_
1199 notification.type_ = type_
1200 notification.created_on = datetime.datetime.now()
1200 notification.created_on = datetime.datetime.now()
1201
1201
1202 for u in recipients:
1202 for u in recipients:
1203 assoc = UserNotification()
1203 assoc = UserNotification()
1204 assoc.notification = notification
1204 assoc.notification = notification
1205 u.notifications.append(assoc)
1205 u.notifications.append(assoc)
1206 Session().add(notification)
1206 Session().add(notification)
1207 return notification
1207 return notification
1208
1208
1209 @property
1209 @property
1210 def description(self):
1210 def description(self):
1211 from rhodecode.model.notification import NotificationModel
1211 from rhodecode.model.notification import NotificationModel
1212 return NotificationModel().make_description(self)
1212 return NotificationModel().make_description(self)
1213
1213
1214 class UserNotification(Base, BaseModel):
1214 class UserNotification(Base, BaseModel):
1215 __tablename__ = 'user_to_notification'
1215 __tablename__ = 'user_to_notification'
1216 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1216 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1217 {'extend_existing':True})
1217 {'extend_existing':True})
1218 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1218 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1219 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1219 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1220 read = Column('read', Boolean, default=False)
1220 read = Column('read', Boolean, default=False)
1221 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1221 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1222
1222
1223 user = relationship('User', lazy="joined")
1223 user = relationship('User', lazy="joined")
1224 notification = relationship('Notification', lazy="joined",
1224 notification = relationship('Notification', lazy="joined",
1225 order_by=lambda:Notification.created_on.desc(),)
1225 order_by=lambda:Notification.created_on.desc(),)
1226
1226
1227 def mark_as_read(self):
1227 def mark_as_read(self):
1228 self.read = True
1228 self.read = True
1229 Session().add(self)
1229 Session().add(self)
1230
1230
1231 class DbMigrateVersion(Base, BaseModel):
1231 class DbMigrateVersion(Base, BaseModel):
1232 __tablename__ = 'db_migrate_version'
1232 __tablename__ = 'db_migrate_version'
1233 __table_args__ = {'extend_existing':True}
1233 __table_args__ = {'extend_existing':True}
1234 repository_id = Column('repository_id', String(250), primary_key=True)
1234 repository_id = Column('repository_id', String(250), primary_key=True)
1235 repository_path = Column('repository_path', Text)
1235 repository_path = Column('repository_path', Text)
1236 version = Column('version', Integer)
1236 version = Column('version', Integer)
1237
1237
General Comments 0
You need to be logged in to leave comments. Login now