##// END OF EJS Templates
Better descriptions of given permission overview in edit user view
marcink -
r2532:19de74e3 beta
parent child Browse files
Show More
@@ -1,537 +1,516
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.db_manage
3 rhodecode.lib.db_manage
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Database creation, and setup module for RhodeCode. Used for creation
6 Database creation, and setup module for RhodeCode. Used for creation
7 of database as well as for migration operations
7 of database as well as for migration operations
8
8
9 :created_on: Apr 10, 2010
9 :created_on: Apr 10, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 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
26
27 import os
27 import os
28 import sys
28 import sys
29 import uuid
29 import uuid
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from rhodecode import __dbversion__
33 from rhodecode import __dbversion__
34 from rhodecode.model import meta
34 from rhodecode.model import meta
35
35
36 from rhodecode.model.user import UserModel
36 from rhodecode.model.user import UserModel
37 from rhodecode.lib.utils import ask_ok
37 from rhodecode.lib.utils import ask_ok
38 from rhodecode.model import init_model
38 from rhodecode.model import init_model
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
39 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
40 RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
41 UserRepoGroupToPerm
41 UserRepoGroupToPerm
42
42
43 from sqlalchemy.engine import create_engine
43 from sqlalchemy.engine import create_engine
44 from rhodecode.model.repos_group import ReposGroupModel
44 from rhodecode.model.repos_group import ReposGroupModel
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class DbManage(object):
49 class DbManage(object):
50 def __init__(self, log_sql, dbconf, root, tests=False):
50 def __init__(self, log_sql, dbconf, root, tests=False):
51 self.dbname = dbconf.split('/')[-1]
51 self.dbname = dbconf.split('/')[-1]
52 self.tests = tests
52 self.tests = tests
53 self.root = root
53 self.root = root
54 self.dburi = dbconf
54 self.dburi = dbconf
55 self.log_sql = log_sql
55 self.log_sql = log_sql
56 self.db_exists = False
56 self.db_exists = False
57 self.init_db()
57 self.init_db()
58
58
59 def init_db(self):
59 def init_db(self):
60 engine = create_engine(self.dburi, echo=self.log_sql)
60 engine = create_engine(self.dburi, echo=self.log_sql)
61 init_model(engine)
61 init_model(engine)
62 self.sa = meta.Session
62 self.sa = meta.Session
63
63
64 def create_tables(self, override=False):
64 def create_tables(self, override=False):
65 """
65 """
66 Create a auth database
66 Create a auth database
67 """
67 """
68
68
69 log.info("Any existing database is going to be destroyed")
69 log.info("Any existing database is going to be destroyed")
70 if self.tests:
70 if self.tests:
71 destroy = True
71 destroy = True
72 else:
72 else:
73 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
73 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
74 if not destroy:
74 if not destroy:
75 sys.exit()
75 sys.exit()
76 if destroy:
76 if destroy:
77 meta.Base.metadata.drop_all()
77 meta.Base.metadata.drop_all()
78
78
79 checkfirst = not override
79 checkfirst = not override
80 meta.Base.metadata.create_all(checkfirst=checkfirst)
80 meta.Base.metadata.create_all(checkfirst=checkfirst)
81 log.info('Created tables for %s' % self.dbname)
81 log.info('Created tables for %s' % self.dbname)
82
82
83 def set_db_version(self):
83 def set_db_version(self):
84 ver = DbMigrateVersion()
84 ver = DbMigrateVersion()
85 ver.version = __dbversion__
85 ver.version = __dbversion__
86 ver.repository_id = 'rhodecode_db_migrations'
86 ver.repository_id = 'rhodecode_db_migrations'
87 ver.repository_path = 'versions'
87 ver.repository_path = 'versions'
88 self.sa.add(ver)
88 self.sa.add(ver)
89 log.info('db version set to: %s' % __dbversion__)
89 log.info('db version set to: %s' % __dbversion__)
90
90
91 def upgrade(self):
91 def upgrade(self):
92 """
92 """
93 Upgrades given database schema to given revision following
93 Upgrades given database schema to given revision following
94 all needed steps, to perform the upgrade
94 all needed steps, to perform the upgrade
95
95
96 """
96 """
97
97
98 from rhodecode.lib.dbmigrate.migrate.versioning import api
98 from rhodecode.lib.dbmigrate.migrate.versioning import api
99 from rhodecode.lib.dbmigrate.migrate.exceptions import \
99 from rhodecode.lib.dbmigrate.migrate.exceptions import \
100 DatabaseNotControlledError
100 DatabaseNotControlledError
101
101
102 if 'sqlite' in self.dburi:
102 if 'sqlite' in self.dburi:
103 print (
103 print (
104 '********************** WARNING **********************\n'
104 '********************** WARNING **********************\n'
105 'Make sure your version of sqlite is at least 3.7.X. \n'
105 'Make sure your version of sqlite is at least 3.7.X. \n'
106 'Earlier versions are known to fail on some migrations\n'
106 'Earlier versions are known to fail on some migrations\n'
107 '*****************************************************\n'
107 '*****************************************************\n'
108 )
108 )
109 upgrade = ask_ok('You are about to perform database upgrade, make '
109 upgrade = ask_ok('You are about to perform database upgrade, make '
110 'sure You backed up your database before. '
110 'sure You backed up your database before. '
111 'Continue ? [y/n]')
111 'Continue ? [y/n]')
112 if not upgrade:
112 if not upgrade:
113 sys.exit('Nothing done')
113 sys.exit('Nothing done')
114
114
115 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
115 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
116 'rhodecode/lib/dbmigrate')
116 'rhodecode/lib/dbmigrate')
117 db_uri = self.dburi
117 db_uri = self.dburi
118
118
119 try:
119 try:
120 curr_version = api.db_version(db_uri, repository_path)
120 curr_version = api.db_version(db_uri, repository_path)
121 msg = ('Found current database under version'
121 msg = ('Found current database under version'
122 ' control with version %s' % curr_version)
122 ' control with version %s' % curr_version)
123
123
124 except (RuntimeError, DatabaseNotControlledError):
124 except (RuntimeError, DatabaseNotControlledError):
125 curr_version = 1
125 curr_version = 1
126 msg = ('Current database is not under version control. Setting'
126 msg = ('Current database is not under version control. Setting'
127 ' as version %s' % curr_version)
127 ' as version %s' % curr_version)
128 api.version_control(db_uri, repository_path, curr_version)
128 api.version_control(db_uri, repository_path, curr_version)
129
129
130 print (msg)
130 print (msg)
131
131
132 if curr_version == __dbversion__:
132 if curr_version == __dbversion__:
133 sys.exit('This database is already at the newest version')
133 sys.exit('This database is already at the newest version')
134
134
135 #======================================================================
135 #======================================================================
136 # UPGRADE STEPS
136 # UPGRADE STEPS
137 #======================================================================
137 #======================================================================
138 class UpgradeSteps(object):
138 class UpgradeSteps(object):
139 """
139 """
140 Those steps follow schema versions so for example schema
140 Those steps follow schema versions so for example schema
141 for example schema with seq 002 == step_2 and so on.
141 for example schema with seq 002 == step_2 and so on.
142 """
142 """
143
143
144 def __init__(self, klass):
144 def __init__(self, klass):
145 self.klass = klass
145 self.klass = klass
146
146
147 def step_0(self):
147 def step_0(self):
148 # step 0 is the schema upgrade, and than follow proper upgrades
148 # step 0 is the schema upgrade, and than follow proper upgrades
149 print ('attempting to do database upgrade to version %s' \
149 print ('attempting to do database upgrade to version %s' \
150 % __dbversion__)
150 % __dbversion__)
151 api.upgrade(db_uri, repository_path, __dbversion__)
151 api.upgrade(db_uri, repository_path, __dbversion__)
152 print ('Schema upgrade completed')
152 print ('Schema upgrade completed')
153
153
154 def step_1(self):
154 def step_1(self):
155 pass
155 pass
156
156
157 def step_2(self):
157 def step_2(self):
158 print ('Patching repo paths for newer version of RhodeCode')
158 print ('Patching repo paths for newer version of RhodeCode')
159 self.klass.fix_repo_paths()
159 self.klass.fix_repo_paths()
160
160
161 print ('Patching default user of RhodeCode')
161 print ('Patching default user of RhodeCode')
162 self.klass.fix_default_user()
162 self.klass.fix_default_user()
163
163
164 log.info('Changing ui settings')
164 log.info('Changing ui settings')
165 self.klass.create_ui_settings()
165 self.klass.create_ui_settings()
166
166
167 def step_3(self):
167 def step_3(self):
168 print ('Adding additional settings into RhodeCode db')
168 print ('Adding additional settings into RhodeCode db')
169 self.klass.fix_settings()
169 self.klass.fix_settings()
170 print ('Adding ldap defaults')
170 print ('Adding ldap defaults')
171 self.klass.create_ldap_options(skip_existing=True)
171 self.klass.create_ldap_options(skip_existing=True)
172
172
173 def step_4(self):
173 def step_4(self):
174 print ('create permissions and fix groups')
174 print ('create permissions and fix groups')
175 self.klass.create_permissions()
175 self.klass.create_permissions()
176 self.klass.fixup_groups()
176 self.klass.fixup_groups()
177
177
178 def step_5(self):
178 def step_5(self):
179 pass
179 pass
180
180
181 def step_6(self):
181 def step_6(self):
182 pass
182 pass
183 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
183 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
184
184
185 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
185 # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
186 for step in upgrade_steps:
186 for step in upgrade_steps:
187 print ('performing upgrade step %s' % step)
187 print ('performing upgrade step %s' % step)
188 getattr(UpgradeSteps(self), 'step_%s' % step)()
188 getattr(UpgradeSteps(self), 'step_%s' % step)()
189 self.sa.commit()
189 self.sa.commit()
190
190
191 def fix_repo_paths(self):
191 def fix_repo_paths(self):
192 """
192 """
193 Fixes a old rhodecode version path into new one without a '*'
193 Fixes a old rhodecode version path into new one without a '*'
194 """
194 """
195
195
196 paths = self.sa.query(RhodeCodeUi)\
196 paths = self.sa.query(RhodeCodeUi)\
197 .filter(RhodeCodeUi.ui_key == '/')\
197 .filter(RhodeCodeUi.ui_key == '/')\
198 .scalar()
198 .scalar()
199
199
200 paths.ui_value = paths.ui_value.replace('*', '')
200 paths.ui_value = paths.ui_value.replace('*', '')
201
201
202 try:
202 try:
203 self.sa.add(paths)
203 self.sa.add(paths)
204 self.sa.commit()
204 self.sa.commit()
205 except:
205 except:
206 self.sa.rollback()
206 self.sa.rollback()
207 raise
207 raise
208
208
209 def fix_default_user(self):
209 def fix_default_user(self):
210 """
210 """
211 Fixes a old default user with some 'nicer' default values,
211 Fixes a old default user with some 'nicer' default values,
212 used mostly for anonymous access
212 used mostly for anonymous access
213 """
213 """
214 def_user = self.sa.query(User)\
214 def_user = self.sa.query(User)\
215 .filter(User.username == 'default')\
215 .filter(User.username == 'default')\
216 .one()
216 .one()
217
217
218 def_user.name = 'Anonymous'
218 def_user.name = 'Anonymous'
219 def_user.lastname = 'User'
219 def_user.lastname = 'User'
220 def_user.email = 'anonymous@rhodecode.org'
220 def_user.email = 'anonymous@rhodecode.org'
221
221
222 try:
222 try:
223 self.sa.add(def_user)
223 self.sa.add(def_user)
224 self.sa.commit()
224 self.sa.commit()
225 except:
225 except:
226 self.sa.rollback()
226 self.sa.rollback()
227 raise
227 raise
228
228
229 def fix_settings(self):
229 def fix_settings(self):
230 """
230 """
231 Fixes rhodecode settings adds ga_code key for google analytics
231 Fixes rhodecode settings adds ga_code key for google analytics
232 """
232 """
233
233
234 hgsettings3 = RhodeCodeSetting('ga_code', '')
234 hgsettings3 = RhodeCodeSetting('ga_code', '')
235
235
236 try:
236 try:
237 self.sa.add(hgsettings3)
237 self.sa.add(hgsettings3)
238 self.sa.commit()
238 self.sa.commit()
239 except:
239 except:
240 self.sa.rollback()
240 self.sa.rollback()
241 raise
241 raise
242
242
243 def admin_prompt(self, second=False, defaults={}):
243 def admin_prompt(self, second=False, defaults={}):
244 if not self.tests:
244 if not self.tests:
245 import getpass
245 import getpass
246
246
247 # defaults
247 # defaults
248 username = defaults.get('username')
248 username = defaults.get('username')
249 password = defaults.get('password')
249 password = defaults.get('password')
250 email = defaults.get('email')
250 email = defaults.get('email')
251
251
252 def get_password():
252 def get_password():
253 password = getpass.getpass('Specify admin password '
253 password = getpass.getpass('Specify admin password '
254 '(min 6 chars):')
254 '(min 6 chars):')
255 confirm = getpass.getpass('Confirm password:')
255 confirm = getpass.getpass('Confirm password:')
256
256
257 if password != confirm:
257 if password != confirm:
258 log.error('passwords mismatch')
258 log.error('passwords mismatch')
259 return False
259 return False
260 if len(password) < 6:
260 if len(password) < 6:
261 log.error('password is to short use at least 6 characters')
261 log.error('password is to short use at least 6 characters')
262 return False
262 return False
263
263
264 return password
264 return password
265 if username is None:
265 if username is None:
266 username = raw_input('Specify admin username:')
266 username = raw_input('Specify admin username:')
267 if password is None:
267 if password is None:
268 password = get_password()
268 password = get_password()
269 if not password:
269 if not password:
270 #second try
270 #second try
271 password = get_password()
271 password = get_password()
272 if not password:
272 if not password:
273 sys.exit()
273 sys.exit()
274 if email is None:
274 if email is None:
275 email = raw_input('Specify admin email:')
275 email = raw_input('Specify admin email:')
276 self.create_user(username, password, email, True)
276 self.create_user(username, password, email, True)
277 else:
277 else:
278 log.info('creating admin and regular test users')
278 log.info('creating admin and regular test users')
279 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
279 from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
280 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
280 TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
281 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
281 TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
282 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
282 TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
283 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
283 TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
284
284
285 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
285 self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
286 TEST_USER_ADMIN_EMAIL, True)
286 TEST_USER_ADMIN_EMAIL, True)
287
287
288 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
288 self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
289 TEST_USER_REGULAR_EMAIL, False)
289 TEST_USER_REGULAR_EMAIL, False)
290
290
291 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
291 self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
292 TEST_USER_REGULAR2_EMAIL, False)
292 TEST_USER_REGULAR2_EMAIL, False)
293
293
294 def create_ui_settings(self):
294 def create_ui_settings(self):
295 """
295 """
296 Creates ui settings, fills out hooks
296 Creates ui settings, fills out hooks
297 and disables dotencode
297 and disables dotencode
298 """
298 """
299
299
300 #HOOKS
300 #HOOKS
301 hooks1_key = RhodeCodeUi.HOOK_UPDATE
301 hooks1_key = RhodeCodeUi.HOOK_UPDATE
302 hooks1_ = self.sa.query(RhodeCodeUi)\
302 hooks1_ = self.sa.query(RhodeCodeUi)\
303 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
303 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
304
304
305 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
305 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
306 hooks1.ui_section = 'hooks'
306 hooks1.ui_section = 'hooks'
307 hooks1.ui_key = hooks1_key
307 hooks1.ui_key = hooks1_key
308 hooks1.ui_value = 'hg update >&2'
308 hooks1.ui_value = 'hg update >&2'
309 hooks1.ui_active = False
309 hooks1.ui_active = False
310
310
311 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
311 hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
312 hooks2_ = self.sa.query(RhodeCodeUi)\
312 hooks2_ = self.sa.query(RhodeCodeUi)\
313 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
313 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
314
314
315 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
315 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
316 hooks2.ui_section = 'hooks'
316 hooks2.ui_section = 'hooks'
317 hooks2.ui_key = hooks2_key
317 hooks2.ui_key = hooks2_key
318 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
318 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
319
319
320 hooks3 = RhodeCodeUi()
320 hooks3 = RhodeCodeUi()
321 hooks3.ui_section = 'hooks'
321 hooks3.ui_section = 'hooks'
322 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
322 hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
323 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
323 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
324
324
325 hooks4 = RhodeCodeUi()
325 hooks4 = RhodeCodeUi()
326 hooks4.ui_section = 'hooks'
326 hooks4.ui_section = 'hooks'
327 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
327 hooks4.ui_key = RhodeCodeUi.HOOK_PULL
328 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
328 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
329
329
330 # For mercurial 1.7 set backward comapatibility with format
330 # For mercurial 1.7 set backward comapatibility with format
331 dotencode_disable = RhodeCodeUi()
331 dotencode_disable = RhodeCodeUi()
332 dotencode_disable.ui_section = 'format'
332 dotencode_disable.ui_section = 'format'
333 dotencode_disable.ui_key = 'dotencode'
333 dotencode_disable.ui_key = 'dotencode'
334 dotencode_disable.ui_value = 'false'
334 dotencode_disable.ui_value = 'false'
335
335
336 # enable largefiles
336 # enable largefiles
337 largefiles = RhodeCodeUi()
337 largefiles = RhodeCodeUi()
338 largefiles.ui_section = 'extensions'
338 largefiles.ui_section = 'extensions'
339 largefiles.ui_key = 'largefiles'
339 largefiles.ui_key = 'largefiles'
340 largefiles.ui_value = ''
340 largefiles.ui_value = ''
341
341
342 self.sa.add(hooks1)
342 self.sa.add(hooks1)
343 self.sa.add(hooks2)
343 self.sa.add(hooks2)
344 self.sa.add(hooks3)
344 self.sa.add(hooks3)
345 self.sa.add(hooks4)
345 self.sa.add(hooks4)
346 self.sa.add(largefiles)
346 self.sa.add(largefiles)
347
347
348 def create_ldap_options(self, skip_existing=False):
348 def create_ldap_options(self, skip_existing=False):
349 """Creates ldap settings"""
349 """Creates ldap settings"""
350
350
351 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
351 for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
352 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
352 ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
353 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
353 ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
354 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
354 ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
355 ('ldap_filter', ''), ('ldap_search_scope', ''),
355 ('ldap_filter', ''), ('ldap_search_scope', ''),
356 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
356 ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
357 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
357 ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
358
358
359 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
359 if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
360 log.debug('Skipping option %s' % k)
360 log.debug('Skipping option %s' % k)
361 continue
361 continue
362 setting = RhodeCodeSetting(k, v)
362 setting = RhodeCodeSetting(k, v)
363 self.sa.add(setting)
363 self.sa.add(setting)
364
364
365 def fixup_groups(self):
365 def fixup_groups(self):
366 def_usr = User.get_by_username('default')
366 def_usr = User.get_by_username('default')
367 for g in RepoGroup.query().all():
367 for g in RepoGroup.query().all():
368 g.group_name = g.get_new_name(g.name)
368 g.group_name = g.get_new_name(g.name)
369 self.sa.add(g)
369 self.sa.add(g)
370 # get default perm
370 # get default perm
371 default = UserRepoGroupToPerm.query()\
371 default = UserRepoGroupToPerm.query()\
372 .filter(UserRepoGroupToPerm.group == g)\
372 .filter(UserRepoGroupToPerm.group == g)\
373 .filter(UserRepoGroupToPerm.user == def_usr)\
373 .filter(UserRepoGroupToPerm.user == def_usr)\
374 .scalar()
374 .scalar()
375
375
376 if default is None:
376 if default is None:
377 log.debug('missing default permission for group %s adding' % g)
377 log.debug('missing default permission for group %s adding' % g)
378 ReposGroupModel()._create_default_perms(g)
378 ReposGroupModel()._create_default_perms(g)
379
379
380 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
380 def config_prompt(self, test_repo_path='', retries=3, defaults={}):
381 _path = defaults.get('repos_location')
381 _path = defaults.get('repos_location')
382 if retries == 3:
382 if retries == 3:
383 log.info('Setting up repositories config')
383 log.info('Setting up repositories config')
384
384
385 if _path is not None:
385 if _path is not None:
386 path = _path
386 path = _path
387 elif not self.tests and not test_repo_path:
387 elif not self.tests and not test_repo_path:
388 path = raw_input(
388 path = raw_input(
389 'Enter a valid absolute path to store repositories. '
389 'Enter a valid absolute path to store repositories. '
390 'All repositories in that path will be added automatically:'
390 'All repositories in that path will be added automatically:'
391 )
391 )
392 else:
392 else:
393 path = test_repo_path
393 path = test_repo_path
394 path_ok = True
394 path_ok = True
395
395
396 # check proper dir
396 # check proper dir
397 if not os.path.isdir(path):
397 if not os.path.isdir(path):
398 path_ok = False
398 path_ok = False
399 log.error('Given path %s is not a valid directory' % path)
399 log.error('Given path %s is not a valid directory' % path)
400
400
401 elif not os.path.isabs(path):
401 elif not os.path.isabs(path):
402 path_ok = False
402 path_ok = False
403 log.error('Given path %s is not an absolute path' % path)
403 log.error('Given path %s is not an absolute path' % path)
404
404
405 # check write access
405 # check write access
406 elif not os.access(path, os.W_OK) and path_ok:
406 elif not os.access(path, os.W_OK) and path_ok:
407 path_ok = False
407 path_ok = False
408 log.error('No write permission to given path %s' % path)
408 log.error('No write permission to given path %s' % path)
409
409
410 if retries == 0:
410 if retries == 0:
411 sys.exit('max retries reached')
411 sys.exit('max retries reached')
412 if path_ok is False:
412 if path_ok is False:
413 retries -= 1
413 retries -= 1
414 return self.config_prompt(test_repo_path, retries)
414 return self.config_prompt(test_repo_path, retries)
415
415
416 return path
416 return path
417
417
418 def create_settings(self, path):
418 def create_settings(self, path):
419
419
420 self.create_ui_settings()
420 self.create_ui_settings()
421
421
422 #HG UI OPTIONS
422 #HG UI OPTIONS
423 web1 = RhodeCodeUi()
423 web1 = RhodeCodeUi()
424 web1.ui_section = 'web'
424 web1.ui_section = 'web'
425 web1.ui_key = 'push_ssl'
425 web1.ui_key = 'push_ssl'
426 web1.ui_value = 'false'
426 web1.ui_value = 'false'
427
427
428 web2 = RhodeCodeUi()
428 web2 = RhodeCodeUi()
429 web2.ui_section = 'web'
429 web2.ui_section = 'web'
430 web2.ui_key = 'allow_archive'
430 web2.ui_key = 'allow_archive'
431 web2.ui_value = 'gz zip bz2'
431 web2.ui_value = 'gz zip bz2'
432
432
433 web3 = RhodeCodeUi()
433 web3 = RhodeCodeUi()
434 web3.ui_section = 'web'
434 web3.ui_section = 'web'
435 web3.ui_key = 'allow_push'
435 web3.ui_key = 'allow_push'
436 web3.ui_value = '*'
436 web3.ui_value = '*'
437
437
438 web4 = RhodeCodeUi()
438 web4 = RhodeCodeUi()
439 web4.ui_section = 'web'
439 web4.ui_section = 'web'
440 web4.ui_key = 'baseurl'
440 web4.ui_key = 'baseurl'
441 web4.ui_value = '/'
441 web4.ui_value = '/'
442
442
443 paths = RhodeCodeUi()
443 paths = RhodeCodeUi()
444 paths.ui_section = 'paths'
444 paths.ui_section = 'paths'
445 paths.ui_key = '/'
445 paths.ui_key = '/'
446 paths.ui_value = path
446 paths.ui_value = path
447
447
448 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
448 hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
449 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
449 hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
450 hgsettings3 = RhodeCodeSetting('ga_code', '')
450 hgsettings3 = RhodeCodeSetting('ga_code', '')
451
451
452 self.sa.add(web1)
452 self.sa.add(web1)
453 self.sa.add(web2)
453 self.sa.add(web2)
454 self.sa.add(web3)
454 self.sa.add(web3)
455 self.sa.add(web4)
455 self.sa.add(web4)
456 self.sa.add(paths)
456 self.sa.add(paths)
457 self.sa.add(hgsettings1)
457 self.sa.add(hgsettings1)
458 self.sa.add(hgsettings2)
458 self.sa.add(hgsettings2)
459 self.sa.add(hgsettings3)
459 self.sa.add(hgsettings3)
460
460
461 self.create_ldap_options()
461 self.create_ldap_options()
462
462
463 log.info('created ui config')
463 log.info('created ui config')
464
464
465 def create_user(self, username, password, email='', admin=False):
465 def create_user(self, username, password, email='', admin=False):
466 log.info('creating user %s' % username)
466 log.info('creating user %s' % username)
467 UserModel().create_or_update(username, password, email,
467 UserModel().create_or_update(username, password, email,
468 firstname='RhodeCode', lastname='Admin',
468 firstname='RhodeCode', lastname='Admin',
469 active=True, admin=admin)
469 active=True, admin=admin)
470
470
471 def create_default_user(self):
471 def create_default_user(self):
472 log.info('creating default user')
472 log.info('creating default user')
473 # create default user for handling default permissions.
473 # create default user for handling default permissions.
474 UserModel().create_or_update(username='default',
474 UserModel().create_or_update(username='default',
475 password=str(uuid.uuid1())[:8],
475 password=str(uuid.uuid1())[:8],
476 email='anonymous@rhodecode.org',
476 email='anonymous@rhodecode.org',
477 firstname='Anonymous', lastname='User')
477 firstname='Anonymous', lastname='User')
478
478
479 def create_permissions(self):
479 def create_permissions(self):
480 # module.(access|create|change|delete)_[name]
480 # module.(access|create|change|delete)_[name]
481 # module.(none|read|write|admin)
481 # module.(none|read|write|admin)
482 perms = [
483 ('repository.none', 'Repository no access'),
484 ('repository.read', 'Repository read access'),
485 ('repository.write', 'Repository write access'),
486 ('repository.admin', 'Repository admin access'),
487
482
488 ('group.none', 'Repositories Group no access'),
483 for p in Permission.PERMS:
489 ('group.read', 'Repositories Group read access'),
490 ('group.write', 'Repositories Group write access'),
491 ('group.admin', 'Repositories Group admin access'),
492
493 ('hg.admin', 'Hg Administrator'),
494 ('hg.create.repository', 'Repository create'),
495 ('hg.create.none', 'Repository creation disabled'),
496 ('hg.register.none', 'Register disabled'),
497 ('hg.register.manual_activate', 'Register new user with RhodeCode '
498 'without manual activation'),
499
500 ('hg.register.auto_activate', 'Register new user with RhodeCode '
501 'without auto activation'),
502 ]
503
504 for p in perms:
505 if not Permission.get_by_key(p[0]):
484 if not Permission.get_by_key(p[0]):
506 new_perm = Permission()
485 new_perm = Permission()
507 new_perm.permission_name = p[0]
486 new_perm.permission_name = p[0]
508 new_perm.permission_longname = p[1]
487 new_perm.permission_longname = p[0]
509 self.sa.add(new_perm)
488 self.sa.add(new_perm)
510
489
511 def populate_default_permissions(self):
490 def populate_default_permissions(self):
512 log.info('creating default user permissions')
491 log.info('creating default user permissions')
513
492
514 default_user = self.sa.query(User)\
493 default_user = self.sa.query(User)\
515 .filter(User.username == 'default').scalar()
494 .filter(User.username == 'default').scalar()
516
495
517 reg_perm = UserToPerm()
496 reg_perm = UserToPerm()
518 reg_perm.user = default_user
497 reg_perm.user = default_user
519 reg_perm.permission = self.sa.query(Permission)\
498 reg_perm.permission = self.sa.query(Permission)\
520 .filter(Permission.permission_name == 'hg.register.manual_activate')\
499 .filter(Permission.permission_name == 'hg.register.manual_activate')\
521 .scalar()
500 .scalar()
522
501
523 create_repo_perm = UserToPerm()
502 create_repo_perm = UserToPerm()
524 create_repo_perm.user = default_user
503 create_repo_perm.user = default_user
525 create_repo_perm.permission = self.sa.query(Permission)\
504 create_repo_perm.permission = self.sa.query(Permission)\
526 .filter(Permission.permission_name == 'hg.create.repository')\
505 .filter(Permission.permission_name == 'hg.create.repository')\
527 .scalar()
506 .scalar()
528
507
529 default_repo_perm = UserToPerm()
508 default_repo_perm = UserToPerm()
530 default_repo_perm.user = default_user
509 default_repo_perm.user = default_user
531 default_repo_perm.permission = self.sa.query(Permission)\
510 default_repo_perm.permission = self.sa.query(Permission)\
532 .filter(Permission.permission_name == 'repository.read')\
511 .filter(Permission.permission_name == 'repository.read')\
533 .scalar()
512 .scalar()
534
513
535 self.sa.add(reg_perm)
514 self.sa.add(reg_perm)
536 self.sa.add(create_repo_perm)
515 self.sa.add(create_repo_perm)
537 self.sa.add(default_repo_perm)
516 self.sa.add(default_repo_perm)
@@ -1,1009 +1,1013
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 import logging
11 import logging
12
12
13 from datetime import datetime
13 from datetime import datetime
14 from pygments.formatters.html import HtmlFormatter
14 from pygments.formatters.html import HtmlFormatter
15 from pygments import highlight as code_highlight
15 from pygments import highlight as code_highlight
16 from pylons import url, request, config
16 from pylons import url, request, config
17 from pylons.i18n.translation import _, ungettext
17 from pylons.i18n.translation import _, ungettext
18 from hashlib import md5
18 from hashlib import md5
19
19
20 from webhelpers.html import literal, HTML, escape
20 from webhelpers.html import literal, HTML, escape
21 from webhelpers.html.tools import *
21 from webhelpers.html.tools import *
22 from webhelpers.html.builder import make_tag
22 from webhelpers.html.builder import make_tag
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
23 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
24 end_form, file, form, hidden, image, javascript_link, link_to, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
25 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
26 submit, text, password, textarea, title, ul, xml_declaration, radio
26 submit, text, password, textarea, title, ul, xml_declaration, radio
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
27 from webhelpers.html.tools import auto_link, button_to, highlight, \
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
28 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
29 from webhelpers.number import format_byte_size, format_bit_size
29 from webhelpers.number import format_byte_size, format_bit_size
30 from webhelpers.pylonslib import Flash as _Flash
30 from webhelpers.pylonslib import Flash as _Flash
31 from webhelpers.pylonslib.secure_form import secure_form
31 from webhelpers.pylonslib.secure_form import secure_form
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
32 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
33 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
34 replace_whitespace, urlify, truncate, wrap_paragraphs
34 replace_whitespace, urlify, truncate, wrap_paragraphs
35 from webhelpers.date import time_ago_in_words
35 from webhelpers.date import time_ago_in_words
36 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
37 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
38 convert_boolean_attrs, NotGiven, _make_safe_id_component
39
39
40 from rhodecode.lib.annotate import annotate_highlight
40 from rhodecode.lib.annotate import annotate_highlight
41 from rhodecode.lib.utils import repo_name_slug
41 from rhodecode.lib.utils import repo_name_slug
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
42 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
43 get_changeset_safe
43 get_changeset_safe
44 from rhodecode.lib.markup_renderer import MarkupRenderer
44 from rhodecode.lib.markup_renderer import MarkupRenderer
45 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
45 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
46 from rhodecode.lib.vcs.backends.base import BaseChangeset
46 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
47 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.db import URL_SEP
49 from rhodecode.model.db import URL_SEP, Permission
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 html_escape_table = {
54 html_escape_table = {
55 "&": "&amp;",
55 "&": "&amp;",
56 '"': "&quot;",
56 '"': "&quot;",
57 "'": "&apos;",
57 "'": "&apos;",
58 ">": "&gt;",
58 ">": "&gt;",
59 "<": "&lt;",
59 "<": "&lt;",
60 }
60 }
61
61
62
62
63 def html_escape(text):
63 def html_escape(text):
64 """Produce entities within text."""
64 """Produce entities within text."""
65 return "".join(html_escape_table.get(c,c) for c in text)
65 return "".join(html_escape_table.get(c,c) for c in text)
66
66
67
67
68 def shorter(text, size=20):
68 def shorter(text, size=20):
69 postfix = '...'
69 postfix = '...'
70 if len(text) > size:
70 if len(text) > size:
71 return text[:size - len(postfix)] + postfix
71 return text[:size - len(postfix)] + postfix
72 return text
72 return text
73
73
74
74
75 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
75 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
76 """
76 """
77 Reset button
77 Reset button
78 """
78 """
79 _set_input_attrs(attrs, type, name, value)
79 _set_input_attrs(attrs, type, name, value)
80 _set_id_attr(attrs, id, name)
80 _set_id_attr(attrs, id, name)
81 convert_boolean_attrs(attrs, ["disabled"])
81 convert_boolean_attrs(attrs, ["disabled"])
82 return HTML.input(**attrs)
82 return HTML.input(**attrs)
83
83
84 reset = _reset
84 reset = _reset
85 safeid = _make_safe_id_component
85 safeid = _make_safe_id_component
86
86
87
87
88 def FID(raw_id, path):
88 def FID(raw_id, path):
89 """
89 """
90 Creates a uniqe ID for filenode based on it's hash of path and revision
90 Creates a uniqe ID for filenode based on it's hash of path and revision
91 it's safe to use in urls
91 it's safe to use in urls
92
92
93 :param raw_id:
93 :param raw_id:
94 :param path:
94 :param path:
95 """
95 """
96
96
97 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
97 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
98
98
99
99
100 def get_token():
100 def get_token():
101 """Return the current authentication token, creating one if one doesn't
101 """Return the current authentication token, creating one if one doesn't
102 already exist.
102 already exist.
103 """
103 """
104 token_key = "_authentication_token"
104 token_key = "_authentication_token"
105 from pylons import session
105 from pylons import session
106 if not token_key in session:
106 if not token_key in session:
107 try:
107 try:
108 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
108 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
109 except AttributeError: # Python < 2.4
109 except AttributeError: # Python < 2.4
110 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
110 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
111 session[token_key] = token
111 session[token_key] = token
112 if hasattr(session, 'save'):
112 if hasattr(session, 'save'):
113 session.save()
113 session.save()
114 return session[token_key]
114 return session[token_key]
115
115
116
116
117 class _GetError(object):
117 class _GetError(object):
118 """Get error from form_errors, and represent it as span wrapped error
118 """Get error from form_errors, and represent it as span wrapped error
119 message
119 message
120
120
121 :param field_name: field to fetch errors for
121 :param field_name: field to fetch errors for
122 :param form_errors: form errors dict
122 :param form_errors: form errors dict
123 """
123 """
124
124
125 def __call__(self, field_name, form_errors):
125 def __call__(self, field_name, form_errors):
126 tmpl = """<span class="error_msg">%s</span>"""
126 tmpl = """<span class="error_msg">%s</span>"""
127 if form_errors and field_name in form_errors:
127 if form_errors and field_name in form_errors:
128 return literal(tmpl % form_errors.get(field_name))
128 return literal(tmpl % form_errors.get(field_name))
129
129
130 get_error = _GetError()
130 get_error = _GetError()
131
131
132
132
133 class _ToolTip(object):
133 class _ToolTip(object):
134
134
135 def __call__(self, tooltip_title, trim_at=50):
135 def __call__(self, tooltip_title, trim_at=50):
136 """
136 """
137 Special function just to wrap our text into nice formatted
137 Special function just to wrap our text into nice formatted
138 autowrapped text
138 autowrapped text
139
139
140 :param tooltip_title:
140 :param tooltip_title:
141 """
141 """
142 tooltip_title = escape(tooltip_title)
142 tooltip_title = escape(tooltip_title)
143 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
143 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
144 return tooltip_title
144 return tooltip_title
145 tooltip = _ToolTip()
145 tooltip = _ToolTip()
146
146
147
147
148 class _FilesBreadCrumbs(object):
148 class _FilesBreadCrumbs(object):
149
149
150 def __call__(self, repo_name, rev, paths):
150 def __call__(self, repo_name, rev, paths):
151 if isinstance(paths, str):
151 if isinstance(paths, str):
152 paths = safe_unicode(paths)
152 paths = safe_unicode(paths)
153 url_l = [link_to(repo_name, url('files_home',
153 url_l = [link_to(repo_name, url('files_home',
154 repo_name=repo_name,
154 repo_name=repo_name,
155 revision=rev, f_path=''))]
155 revision=rev, f_path=''))]
156 paths_l = paths.split('/')
156 paths_l = paths.split('/')
157 for cnt, p in enumerate(paths_l):
157 for cnt, p in enumerate(paths_l):
158 if p != '':
158 if p != '':
159 url_l.append(link_to(p,
159 url_l.append(link_to(p,
160 url('files_home',
160 url('files_home',
161 repo_name=repo_name,
161 repo_name=repo_name,
162 revision=rev,
162 revision=rev,
163 f_path='/'.join(paths_l[:cnt + 1])
163 f_path='/'.join(paths_l[:cnt + 1])
164 )
164 )
165 )
165 )
166 )
166 )
167
167
168 return literal('/'.join(url_l))
168 return literal('/'.join(url_l))
169
169
170 files_breadcrumbs = _FilesBreadCrumbs()
170 files_breadcrumbs = _FilesBreadCrumbs()
171
171
172
172
173 class CodeHtmlFormatter(HtmlFormatter):
173 class CodeHtmlFormatter(HtmlFormatter):
174 """
174 """
175 My code Html Formatter for source codes
175 My code Html Formatter for source codes
176 """
176 """
177
177
178 def wrap(self, source, outfile):
178 def wrap(self, source, outfile):
179 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
179 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
180
180
181 def _wrap_code(self, source):
181 def _wrap_code(self, source):
182 for cnt, it in enumerate(source):
182 for cnt, it in enumerate(source):
183 i, t = it
183 i, t = it
184 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
184 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
185 yield i, t
185 yield i, t
186
186
187 def _wrap_tablelinenos(self, inner):
187 def _wrap_tablelinenos(self, inner):
188 dummyoutfile = StringIO.StringIO()
188 dummyoutfile = StringIO.StringIO()
189 lncount = 0
189 lncount = 0
190 for t, line in inner:
190 for t, line in inner:
191 if t:
191 if t:
192 lncount += 1
192 lncount += 1
193 dummyoutfile.write(line)
193 dummyoutfile.write(line)
194
194
195 fl = self.linenostart
195 fl = self.linenostart
196 mw = len(str(lncount + fl - 1))
196 mw = len(str(lncount + fl - 1))
197 sp = self.linenospecial
197 sp = self.linenospecial
198 st = self.linenostep
198 st = self.linenostep
199 la = self.lineanchors
199 la = self.lineanchors
200 aln = self.anchorlinenos
200 aln = self.anchorlinenos
201 nocls = self.noclasses
201 nocls = self.noclasses
202 if sp:
202 if sp:
203 lines = []
203 lines = []
204
204
205 for i in range(fl, fl + lncount):
205 for i in range(fl, fl + lncount):
206 if i % st == 0:
206 if i % st == 0:
207 if i % sp == 0:
207 if i % sp == 0:
208 if aln:
208 if aln:
209 lines.append('<a href="#%s%d" class="special">%*d</a>' %
209 lines.append('<a href="#%s%d" class="special">%*d</a>' %
210 (la, i, mw, i))
210 (la, i, mw, i))
211 else:
211 else:
212 lines.append('<span class="special">%*d</span>' % (mw, i))
212 lines.append('<span class="special">%*d</span>' % (mw, i))
213 else:
213 else:
214 if aln:
214 if aln:
215 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
215 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
216 else:
216 else:
217 lines.append('%*d' % (mw, i))
217 lines.append('%*d' % (mw, i))
218 else:
218 else:
219 lines.append('')
219 lines.append('')
220 ls = '\n'.join(lines)
220 ls = '\n'.join(lines)
221 else:
221 else:
222 lines = []
222 lines = []
223 for i in range(fl, fl + lncount):
223 for i in range(fl, fl + lncount):
224 if i % st == 0:
224 if i % st == 0:
225 if aln:
225 if aln:
226 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
226 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
227 else:
227 else:
228 lines.append('%*d' % (mw, i))
228 lines.append('%*d' % (mw, i))
229 else:
229 else:
230 lines.append('')
230 lines.append('')
231 ls = '\n'.join(lines)
231 ls = '\n'.join(lines)
232
232
233 # in case you wonder about the seemingly redundant <div> here: since the
233 # in case you wonder about the seemingly redundant <div> here: since the
234 # content in the other cell also is wrapped in a div, some browsers in
234 # content in the other cell also is wrapped in a div, some browsers in
235 # some configurations seem to mess up the formatting...
235 # some configurations seem to mess up the formatting...
236 if nocls:
236 if nocls:
237 yield 0, ('<table class="%stable">' % self.cssclass +
237 yield 0, ('<table class="%stable">' % self.cssclass +
238 '<tr><td><div class="linenodiv" '
238 '<tr><td><div class="linenodiv" '
239 'style="background-color: #f0f0f0; padding-right: 10px">'
239 'style="background-color: #f0f0f0; padding-right: 10px">'
240 '<pre style="line-height: 125%">' +
240 '<pre style="line-height: 125%">' +
241 ls + '</pre></div></td><td id="hlcode" class="code">')
241 ls + '</pre></div></td><td id="hlcode" class="code">')
242 else:
242 else:
243 yield 0, ('<table class="%stable">' % self.cssclass +
243 yield 0, ('<table class="%stable">' % self.cssclass +
244 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
244 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
245 ls + '</pre></div></td><td id="hlcode" class="code">')
245 ls + '</pre></div></td><td id="hlcode" class="code">')
246 yield 0, dummyoutfile.getvalue()
246 yield 0, dummyoutfile.getvalue()
247 yield 0, '</td></tr></table>'
247 yield 0, '</td></tr></table>'
248
248
249
249
250 def pygmentize(filenode, **kwargs):
250 def pygmentize(filenode, **kwargs):
251 """pygmentize function using pygments
251 """pygmentize function using pygments
252
252
253 :param filenode:
253 :param filenode:
254 """
254 """
255
255
256 return literal(code_highlight(filenode.content,
256 return literal(code_highlight(filenode.content,
257 filenode.lexer, CodeHtmlFormatter(**kwargs)))
257 filenode.lexer, CodeHtmlFormatter(**kwargs)))
258
258
259
259
260 def pygmentize_annotation(repo_name, filenode, **kwargs):
260 def pygmentize_annotation(repo_name, filenode, **kwargs):
261 """
261 """
262 pygmentize function for annotation
262 pygmentize function for annotation
263
263
264 :param filenode:
264 :param filenode:
265 """
265 """
266
266
267 color_dict = {}
267 color_dict = {}
268
268
269 def gen_color(n=10000):
269 def gen_color(n=10000):
270 """generator for getting n of evenly distributed colors using
270 """generator for getting n of evenly distributed colors using
271 hsv color and golden ratio. It always return same order of colors
271 hsv color and golden ratio. It always return same order of colors
272
272
273 :returns: RGB tuple
273 :returns: RGB tuple
274 """
274 """
275
275
276 def hsv_to_rgb(h, s, v):
276 def hsv_to_rgb(h, s, v):
277 if s == 0.0:
277 if s == 0.0:
278 return v, v, v
278 return v, v, v
279 i = int(h * 6.0) # XXX assume int() truncates!
279 i = int(h * 6.0) # XXX assume int() truncates!
280 f = (h * 6.0) - i
280 f = (h * 6.0) - i
281 p = v * (1.0 - s)
281 p = v * (1.0 - s)
282 q = v * (1.0 - s * f)
282 q = v * (1.0 - s * f)
283 t = v * (1.0 - s * (1.0 - f))
283 t = v * (1.0 - s * (1.0 - f))
284 i = i % 6
284 i = i % 6
285 if i == 0:
285 if i == 0:
286 return v, t, p
286 return v, t, p
287 if i == 1:
287 if i == 1:
288 return q, v, p
288 return q, v, p
289 if i == 2:
289 if i == 2:
290 return p, v, t
290 return p, v, t
291 if i == 3:
291 if i == 3:
292 return p, q, v
292 return p, q, v
293 if i == 4:
293 if i == 4:
294 return t, p, v
294 return t, p, v
295 if i == 5:
295 if i == 5:
296 return v, p, q
296 return v, p, q
297
297
298 golden_ratio = 0.618033988749895
298 golden_ratio = 0.618033988749895
299 h = 0.22717784590367374
299 h = 0.22717784590367374
300
300
301 for _ in xrange(n):
301 for _ in xrange(n):
302 h += golden_ratio
302 h += golden_ratio
303 h %= 1
303 h %= 1
304 HSV_tuple = [h, 0.95, 0.95]
304 HSV_tuple = [h, 0.95, 0.95]
305 RGB_tuple = hsv_to_rgb(*HSV_tuple)
305 RGB_tuple = hsv_to_rgb(*HSV_tuple)
306 yield map(lambda x: str(int(x * 256)), RGB_tuple)
306 yield map(lambda x: str(int(x * 256)), RGB_tuple)
307
307
308 cgenerator = gen_color()
308 cgenerator = gen_color()
309
309
310 def get_color_string(cs):
310 def get_color_string(cs):
311 if cs in color_dict:
311 if cs in color_dict:
312 col = color_dict[cs]
312 col = color_dict[cs]
313 else:
313 else:
314 col = color_dict[cs] = cgenerator.next()
314 col = color_dict[cs] = cgenerator.next()
315 return "color: rgb(%s)! important;" % (', '.join(col))
315 return "color: rgb(%s)! important;" % (', '.join(col))
316
316
317 def url_func(repo_name):
317 def url_func(repo_name):
318
318
319 def _url_func(changeset):
319 def _url_func(changeset):
320 author = changeset.author
320 author = changeset.author
321 date = changeset.date
321 date = changeset.date
322 message = tooltip(changeset.message)
322 message = tooltip(changeset.message)
323
323
324 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
324 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
325 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
326 "</b> %s<br/></div>")
326 "</b> %s<br/></div>")
327
327
328 tooltip_html = tooltip_html % (author, date, message)
328 tooltip_html = tooltip_html % (author, date, message)
329 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
329 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
330 short_id(changeset.raw_id))
330 short_id(changeset.raw_id))
331 uri = link_to(
331 uri = link_to(
332 lnk_format,
332 lnk_format,
333 url('changeset_home', repo_name=repo_name,
333 url('changeset_home', repo_name=repo_name,
334 revision=changeset.raw_id),
334 revision=changeset.raw_id),
335 style=get_color_string(changeset.raw_id),
335 style=get_color_string(changeset.raw_id),
336 class_='tooltip',
336 class_='tooltip',
337 title=tooltip_html
337 title=tooltip_html
338 )
338 )
339
339
340 uri += '\n'
340 uri += '\n'
341 return uri
341 return uri
342 return _url_func
342 return _url_func
343
343
344 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
344 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
345
345
346
346
347 def is_following_repo(repo_name, user_id):
347 def is_following_repo(repo_name, user_id):
348 from rhodecode.model.scm import ScmModel
348 from rhodecode.model.scm import ScmModel
349 return ScmModel().is_following_repo(repo_name, user_id)
349 return ScmModel().is_following_repo(repo_name, user_id)
350
350
351 flash = _Flash()
351 flash = _Flash()
352
352
353 #==============================================================================
353 #==============================================================================
354 # SCM FILTERS available via h.
354 # SCM FILTERS available via h.
355 #==============================================================================
355 #==============================================================================
356 from rhodecode.lib.vcs.utils import author_name, author_email
356 from rhodecode.lib.vcs.utils import author_name, author_email
357 from rhodecode.lib.utils2 import credentials_filter, age as _age
357 from rhodecode.lib.utils2 import credentials_filter, age as _age
358 from rhodecode.model.db import User, ChangesetStatus
358 from rhodecode.model.db import User, ChangesetStatus
359
359
360 age = lambda x: _age(x)
360 age = lambda x: _age(x)
361 capitalize = lambda x: x.capitalize()
361 capitalize = lambda x: x.capitalize()
362 email = author_email
362 email = author_email
363 short_id = lambda x: x[:12]
363 short_id = lambda x: x[:12]
364 hide_credentials = lambda x: ''.join(credentials_filter(x))
364 hide_credentials = lambda x: ''.join(credentials_filter(x))
365
365
366
366
367 def fmt_date(date):
367 def fmt_date(date):
368 if date:
368 if date:
369 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
369 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
370 return date.strftime(_fmt).decode('utf8')
370 return date.strftime(_fmt).decode('utf8')
371
371
372 return ""
372 return ""
373
373
374
374
375 def is_git(repository):
375 def is_git(repository):
376 if hasattr(repository, 'alias'):
376 if hasattr(repository, 'alias'):
377 _type = repository.alias
377 _type = repository.alias
378 elif hasattr(repository, 'repo_type'):
378 elif hasattr(repository, 'repo_type'):
379 _type = repository.repo_type
379 _type = repository.repo_type
380 else:
380 else:
381 _type = repository
381 _type = repository
382 return _type == 'git'
382 return _type == 'git'
383
383
384
384
385 def is_hg(repository):
385 def is_hg(repository):
386 if hasattr(repository, 'alias'):
386 if hasattr(repository, 'alias'):
387 _type = repository.alias
387 _type = repository.alias
388 elif hasattr(repository, 'repo_type'):
388 elif hasattr(repository, 'repo_type'):
389 _type = repository.repo_type
389 _type = repository.repo_type
390 else:
390 else:
391 _type = repository
391 _type = repository
392 return _type == 'hg'
392 return _type == 'hg'
393
393
394
394
395 def email_or_none(author):
395 def email_or_none(author):
396 _email = email(author)
396 _email = email(author)
397 if _email != '':
397 if _email != '':
398 return _email
398 return _email
399
399
400 # See if it contains a username we can get an email from
400 # See if it contains a username we can get an email from
401 user = User.get_by_username(author_name(author), case_insensitive=True,
401 user = User.get_by_username(author_name(author), case_insensitive=True,
402 cache=True)
402 cache=True)
403 if user is not None:
403 if user is not None:
404 return user.email
404 return user.email
405
405
406 # No valid email, not a valid user in the system, none!
406 # No valid email, not a valid user in the system, none!
407 return None
407 return None
408
408
409
409
410 def person(author):
410 def person(author):
411 # attr to return from fetched user
411 # attr to return from fetched user
412 person_getter = lambda usr: usr.username
412 person_getter = lambda usr: usr.username
413
413
414 # Valid email in the attribute passed, see if they're in the system
414 # Valid email in the attribute passed, see if they're in the system
415 _email = email(author)
415 _email = email(author)
416 if _email != '':
416 if _email != '':
417 user = User.get_by_email(_email, case_insensitive=True, cache=True)
417 user = User.get_by_email(_email, case_insensitive=True, cache=True)
418 if user is not None:
418 if user is not None:
419 return person_getter(user)
419 return person_getter(user)
420 return _email
420 return _email
421
421
422 # Maybe it's a username?
422 # Maybe it's a username?
423 _author = author_name(author)
423 _author = author_name(author)
424 user = User.get_by_username(_author, case_insensitive=True,
424 user = User.get_by_username(_author, case_insensitive=True,
425 cache=True)
425 cache=True)
426 if user is not None:
426 if user is not None:
427 return person_getter(user)
427 return person_getter(user)
428
428
429 # Still nothing? Just pass back the author name then
429 # Still nothing? Just pass back the author name then
430 return _author
430 return _author
431
431
432
432
433 def bool2icon(value):
433 def bool2icon(value):
434 """Returns True/False values represented as small html image of true/false
434 """Returns True/False values represented as small html image of true/false
435 icons
435 icons
436
436
437 :param value: bool value
437 :param value: bool value
438 """
438 """
439
439
440 if value is True:
440 if value is True:
441 return HTML.tag('img', src=url("/images/icons/accept.png"),
441 return HTML.tag('img', src=url("/images/icons/accept.png"),
442 alt=_('True'))
442 alt=_('True'))
443
443
444 if value is False:
444 if value is False:
445 return HTML.tag('img', src=url("/images/icons/cancel.png"),
445 return HTML.tag('img', src=url("/images/icons/cancel.png"),
446 alt=_('False'))
446 alt=_('False'))
447
447
448 return value
448 return value
449
449
450
450
451 def action_parser(user_log, feed=False):
451 def action_parser(user_log, feed=False):
452 """
452 """
453 This helper will action_map the specified string action into translated
453 This helper will action_map the specified string action into translated
454 fancy names with icons and links
454 fancy names with icons and links
455
455
456 :param user_log: user log instance
456 :param user_log: user log instance
457 :param feed: use output for feeds (no html and fancy icons)
457 :param feed: use output for feeds (no html and fancy icons)
458 """
458 """
459
459
460 action = user_log.action
460 action = user_log.action
461 action_params = ' '
461 action_params = ' '
462
462
463 x = action.split(':')
463 x = action.split(':')
464
464
465 if len(x) > 1:
465 if len(x) > 1:
466 action, action_params = x
466 action, action_params = x
467
467
468 def get_cs_links():
468 def get_cs_links():
469 revs_limit = 3 # display this amount always
469 revs_limit = 3 # display this amount always
470 revs_top_limit = 50 # show upto this amount of changesets hidden
470 revs_top_limit = 50 # show upto this amount of changesets hidden
471 revs_ids = action_params.split(',')
471 revs_ids = action_params.split(',')
472 deleted = user_log.repository is None
472 deleted = user_log.repository is None
473 if deleted:
473 if deleted:
474 return ','.join(revs_ids)
474 return ','.join(revs_ids)
475
475
476 repo_name = user_log.repository.repo_name
476 repo_name = user_log.repository.repo_name
477
477
478 repo = user_log.repository.scm_instance
478 repo = user_log.repository.scm_instance
479
479
480 def lnk(rev, repo_name):
480 def lnk(rev, repo_name):
481
481
482 if isinstance(rev, BaseChangeset):
482 if isinstance(rev, BaseChangeset):
483 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
483 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
484 _url = url('changeset_home', repo_name=repo_name,
484 _url = url('changeset_home', repo_name=repo_name,
485 revision=rev.raw_id)
485 revision=rev.raw_id)
486 title = tooltip(rev.message)
486 title = tooltip(rev.message)
487 else:
487 else:
488 lbl = '%s' % rev
488 lbl = '%s' % rev
489 _url = '#'
489 _url = '#'
490 title = _('Changeset not found')
490 title = _('Changeset not found')
491
491
492 return link_to(lbl, _url, title=title, class_='tooltip',)
492 return link_to(lbl, _url, title=title, class_='tooltip',)
493
493
494 revs = []
494 revs = []
495 if len(filter(lambda v: v != '', revs_ids)) > 0:
495 if len(filter(lambda v: v != '', revs_ids)) > 0:
496 for rev in revs_ids[:revs_top_limit]:
496 for rev in revs_ids[:revs_top_limit]:
497 try:
497 try:
498 rev = repo.get_changeset(rev)
498 rev = repo.get_changeset(rev)
499 revs.append(rev)
499 revs.append(rev)
500 except ChangesetDoesNotExistError:
500 except ChangesetDoesNotExistError:
501 log.error('cannot find revision %s in this repo' % rev)
501 log.error('cannot find revision %s in this repo' % rev)
502 revs.append(rev)
502 revs.append(rev)
503 continue
503 continue
504 cs_links = []
504 cs_links = []
505 cs_links.append(" " + ', '.join(
505 cs_links.append(" " + ', '.join(
506 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
506 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
507 )
507 )
508 )
508 )
509
509
510 compare_view = (
510 compare_view = (
511 ' <div class="compare_view tooltip" title="%s">'
511 ' <div class="compare_view tooltip" title="%s">'
512 '<a href="%s">%s</a> </div>' % (
512 '<a href="%s">%s</a> </div>' % (
513 _('Show all combined changesets %s->%s') % (
513 _('Show all combined changesets %s->%s') % (
514 revs_ids[0], revs_ids[-1]
514 revs_ids[0], revs_ids[-1]
515 ),
515 ),
516 url('changeset_home', repo_name=repo_name,
516 url('changeset_home', repo_name=repo_name,
517 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
517 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
518 ),
518 ),
519 _('compare view')
519 _('compare view')
520 )
520 )
521 )
521 )
522
522
523 # if we have exactly one more than normally displayed
523 # if we have exactly one more than normally displayed
524 # just display it, takes less space than displaying
524 # just display it, takes less space than displaying
525 # "and 1 more revisions"
525 # "and 1 more revisions"
526 if len(revs_ids) == revs_limit + 1:
526 if len(revs_ids) == revs_limit + 1:
527 rev = revs[revs_limit]
527 rev = revs[revs_limit]
528 cs_links.append(", " + lnk(rev, repo_name))
528 cs_links.append(", " + lnk(rev, repo_name))
529
529
530 # hidden-by-default ones
530 # hidden-by-default ones
531 if len(revs_ids) > revs_limit + 1:
531 if len(revs_ids) > revs_limit + 1:
532 uniq_id = revs_ids[0]
532 uniq_id = revs_ids[0]
533 html_tmpl = (
533 html_tmpl = (
534 '<span> %s <a class="show_more" id="_%s" '
534 '<span> %s <a class="show_more" id="_%s" '
535 'href="#more">%s</a> %s</span>'
535 'href="#more">%s</a> %s</span>'
536 )
536 )
537 if not feed:
537 if not feed:
538 cs_links.append(html_tmpl % (
538 cs_links.append(html_tmpl % (
539 _('and'),
539 _('and'),
540 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
540 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
541 _('revisions')
541 _('revisions')
542 )
542 )
543 )
543 )
544
544
545 if not feed:
545 if not feed:
546 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
546 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
547 else:
547 else:
548 html_tmpl = '<span id="%s"> %s </span>'
548 html_tmpl = '<span id="%s"> %s </span>'
549
549
550 morelinks = ', '.join(
550 morelinks = ', '.join(
551 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
551 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
552 )
552 )
553
553
554 if len(revs_ids) > revs_top_limit:
554 if len(revs_ids) > revs_top_limit:
555 morelinks += ', ...'
555 morelinks += ', ...'
556
556
557 cs_links.append(html_tmpl % (uniq_id, morelinks))
557 cs_links.append(html_tmpl % (uniq_id, morelinks))
558 if len(revs) > 1:
558 if len(revs) > 1:
559 cs_links.append(compare_view)
559 cs_links.append(compare_view)
560 return ''.join(cs_links)
560 return ''.join(cs_links)
561
561
562 def get_fork_name():
562 def get_fork_name():
563 repo_name = action_params
563 repo_name = action_params
564 return _('fork name ') + str(link_to(action_params, url('summary_home',
564 return _('fork name ') + str(link_to(action_params, url('summary_home',
565 repo_name=repo_name,)))
565 repo_name=repo_name,)))
566
566
567 def get_user_name():
567 def get_user_name():
568 user_name = action_params
568 user_name = action_params
569 return user_name
569 return user_name
570
570
571 def get_users_group():
571 def get_users_group():
572 group_name = action_params
572 group_name = action_params
573 return group_name
573 return group_name
574
574
575 # action : translated str, callback(extractor), icon
575 # action : translated str, callback(extractor), icon
576 action_map = {
576 action_map = {
577 'user_deleted_repo': (_('[deleted] repository'),
577 'user_deleted_repo': (_('[deleted] repository'),
578 None, 'database_delete.png'),
578 None, 'database_delete.png'),
579 'user_created_repo': (_('[created] repository'),
579 'user_created_repo': (_('[created] repository'),
580 None, 'database_add.png'),
580 None, 'database_add.png'),
581 'user_created_fork': (_('[created] repository as fork'),
581 'user_created_fork': (_('[created] repository as fork'),
582 None, 'arrow_divide.png'),
582 None, 'arrow_divide.png'),
583 'user_forked_repo': (_('[forked] repository'),
583 'user_forked_repo': (_('[forked] repository'),
584 get_fork_name, 'arrow_divide.png'),
584 get_fork_name, 'arrow_divide.png'),
585 'user_updated_repo': (_('[updated] repository'),
585 'user_updated_repo': (_('[updated] repository'),
586 None, 'database_edit.png'),
586 None, 'database_edit.png'),
587 'admin_deleted_repo': (_('[delete] repository'),
587 'admin_deleted_repo': (_('[delete] repository'),
588 None, 'database_delete.png'),
588 None, 'database_delete.png'),
589 'admin_created_repo': (_('[created] repository'),
589 'admin_created_repo': (_('[created] repository'),
590 None, 'database_add.png'),
590 None, 'database_add.png'),
591 'admin_forked_repo': (_('[forked] repository'),
591 'admin_forked_repo': (_('[forked] repository'),
592 None, 'arrow_divide.png'),
592 None, 'arrow_divide.png'),
593 'admin_updated_repo': (_('[updated] repository'),
593 'admin_updated_repo': (_('[updated] repository'),
594 None, 'database_edit.png'),
594 None, 'database_edit.png'),
595 'admin_created_user': (_('[created] user'),
595 'admin_created_user': (_('[created] user'),
596 get_user_name, 'user_add.png'),
596 get_user_name, 'user_add.png'),
597 'admin_updated_user': (_('[updated] user'),
597 'admin_updated_user': (_('[updated] user'),
598 get_user_name, 'user_edit.png'),
598 get_user_name, 'user_edit.png'),
599 'admin_created_users_group': (_('[created] users group'),
599 'admin_created_users_group': (_('[created] users group'),
600 get_users_group, 'group_add.png'),
600 get_users_group, 'group_add.png'),
601 'admin_updated_users_group': (_('[updated] users group'),
601 'admin_updated_users_group': (_('[updated] users group'),
602 get_users_group, 'group_edit.png'),
602 get_users_group, 'group_edit.png'),
603 'user_commented_revision': (_('[commented] on revision in repository'),
603 'user_commented_revision': (_('[commented] on revision in repository'),
604 get_cs_links, 'comment_add.png'),
604 get_cs_links, 'comment_add.png'),
605 'user_commented_pull_request': (_('[commented] on pull request'),
605 'user_commented_pull_request': (_('[commented] on pull request'),
606 get_cs_links, 'comment_add.png'),
606 get_cs_links, 'comment_add.png'),
607 'push': (_('[pushed] into'),
607 'push': (_('[pushed] into'),
608 get_cs_links, 'script_add.png'),
608 get_cs_links, 'script_add.png'),
609 'push_local': (_('[committed via RhodeCode] into repository'),
609 'push_local': (_('[committed via RhodeCode] into repository'),
610 get_cs_links, 'script_edit.png'),
610 get_cs_links, 'script_edit.png'),
611 'push_remote': (_('[pulled from remote] into repository'),
611 'push_remote': (_('[pulled from remote] into repository'),
612 get_cs_links, 'connect.png'),
612 get_cs_links, 'connect.png'),
613 'pull': (_('[pulled] from'),
613 'pull': (_('[pulled] from'),
614 None, 'down_16.png'),
614 None, 'down_16.png'),
615 'started_following_repo': (_('[started following] repository'),
615 'started_following_repo': (_('[started following] repository'),
616 None, 'heart_add.png'),
616 None, 'heart_add.png'),
617 'stopped_following_repo': (_('[stopped following] repository'),
617 'stopped_following_repo': (_('[stopped following] repository'),
618 None, 'heart_delete.png'),
618 None, 'heart_delete.png'),
619 }
619 }
620
620
621 action_str = action_map.get(action, action)
621 action_str = action_map.get(action, action)
622 if feed:
622 if feed:
623 action = action_str[0].replace('[', '').replace(']', '')
623 action = action_str[0].replace('[', '').replace(']', '')
624 else:
624 else:
625 action = action_str[0]\
625 action = action_str[0]\
626 .replace('[', '<span class="journal_highlight">')\
626 .replace('[', '<span class="journal_highlight">')\
627 .replace(']', '</span>')
627 .replace(']', '</span>')
628
628
629 action_params_func = lambda: ""
629 action_params_func = lambda: ""
630
630
631 if callable(action_str[1]):
631 if callable(action_str[1]):
632 action_params_func = action_str[1]
632 action_params_func = action_str[1]
633
633
634 def action_parser_icon():
634 def action_parser_icon():
635 action = user_log.action
635 action = user_log.action
636 action_params = None
636 action_params = None
637 x = action.split(':')
637 x = action.split(':')
638
638
639 if len(x) > 1:
639 if len(x) > 1:
640 action, action_params = x
640 action, action_params = x
641
641
642 tmpl = """<img src="%s%s" alt="%s"/>"""
642 tmpl = """<img src="%s%s" alt="%s"/>"""
643 ico = action_map.get(action, ['', '', ''])[2]
643 ico = action_map.get(action, ['', '', ''])[2]
644 return literal(tmpl % ((url('/images/icons/')), ico, action))
644 return literal(tmpl % ((url('/images/icons/')), ico, action))
645
645
646 # returned callbacks we need to call to get
646 # returned callbacks we need to call to get
647 return [lambda: literal(action), action_params_func, action_parser_icon]
647 return [lambda: literal(action), action_params_func, action_parser_icon]
648
648
649
649
650
650
651 #==============================================================================
651 #==============================================================================
652 # PERMS
652 # PERMS
653 #==============================================================================
653 #==============================================================================
654 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
654 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
655 HasRepoPermissionAny, HasRepoPermissionAll
655 HasRepoPermissionAny, HasRepoPermissionAll
656
656
657
657
658 #==============================================================================
658 #==============================================================================
659 # GRAVATAR URL
659 # GRAVATAR URL
660 #==============================================================================
660 #==============================================================================
661
661
662 def gravatar_url(email_address, size=30):
662 def gravatar_url(email_address, size=30):
663 if (not str2bool(config['app_conf'].get('use_gravatar')) or
663 if (not str2bool(config['app_conf'].get('use_gravatar')) or
664 not email_address or email_address == 'anonymous@rhodecode.org'):
664 not email_address or email_address == 'anonymous@rhodecode.org'):
665 f = lambda a, l: min(l, key=lambda x: abs(x - a))
665 f = lambda a, l: min(l, key=lambda x: abs(x - a))
666 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
666 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
667
667
668 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
668 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
669 default = 'identicon'
669 default = 'identicon'
670 baseurl_nossl = "http://www.gravatar.com/avatar/"
670 baseurl_nossl = "http://www.gravatar.com/avatar/"
671 baseurl_ssl = "https://secure.gravatar.com/avatar/"
671 baseurl_ssl = "https://secure.gravatar.com/avatar/"
672 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
672 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
673
673
674 if isinstance(email_address, unicode):
674 if isinstance(email_address, unicode):
675 #hashlib crashes on unicode items
675 #hashlib crashes on unicode items
676 email_address = safe_str(email_address)
676 email_address = safe_str(email_address)
677 # construct the url
677 # construct the url
678 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
678 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
679 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
679 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
680
680
681 return gravatar_url
681 return gravatar_url
682
682
683
683
684 #==============================================================================
684 #==============================================================================
685 # REPO PAGER, PAGER FOR REPOSITORY
685 # REPO PAGER, PAGER FOR REPOSITORY
686 #==============================================================================
686 #==============================================================================
687 class RepoPage(Page):
687 class RepoPage(Page):
688
688
689 def __init__(self, collection, page=1, items_per_page=20,
689 def __init__(self, collection, page=1, items_per_page=20,
690 item_count=None, url=None, **kwargs):
690 item_count=None, url=None, **kwargs):
691
691
692 """Create a "RepoPage" instance. special pager for paging
692 """Create a "RepoPage" instance. special pager for paging
693 repository
693 repository
694 """
694 """
695 self._url_generator = url
695 self._url_generator = url
696
696
697 # Safe the kwargs class-wide so they can be used in the pager() method
697 # Safe the kwargs class-wide so they can be used in the pager() method
698 self.kwargs = kwargs
698 self.kwargs = kwargs
699
699
700 # Save a reference to the collection
700 # Save a reference to the collection
701 self.original_collection = collection
701 self.original_collection = collection
702
702
703 self.collection = collection
703 self.collection = collection
704
704
705 # The self.page is the number of the current page.
705 # The self.page is the number of the current page.
706 # The first page has the number 1!
706 # The first page has the number 1!
707 try:
707 try:
708 self.page = int(page) # make it int() if we get it as a string
708 self.page = int(page) # make it int() if we get it as a string
709 except (ValueError, TypeError):
709 except (ValueError, TypeError):
710 self.page = 1
710 self.page = 1
711
711
712 self.items_per_page = items_per_page
712 self.items_per_page = items_per_page
713
713
714 # Unless the user tells us how many items the collections has
714 # Unless the user tells us how many items the collections has
715 # we calculate that ourselves.
715 # we calculate that ourselves.
716 if item_count is not None:
716 if item_count is not None:
717 self.item_count = item_count
717 self.item_count = item_count
718 else:
718 else:
719 self.item_count = len(self.collection)
719 self.item_count = len(self.collection)
720
720
721 # Compute the number of the first and last available page
721 # Compute the number of the first and last available page
722 if self.item_count > 0:
722 if self.item_count > 0:
723 self.first_page = 1
723 self.first_page = 1
724 self.page_count = int(math.ceil(float(self.item_count) /
724 self.page_count = int(math.ceil(float(self.item_count) /
725 self.items_per_page))
725 self.items_per_page))
726 self.last_page = self.first_page + self.page_count - 1
726 self.last_page = self.first_page + self.page_count - 1
727
727
728 # Make sure that the requested page number is the range of
728 # Make sure that the requested page number is the range of
729 # valid pages
729 # valid pages
730 if self.page > self.last_page:
730 if self.page > self.last_page:
731 self.page = self.last_page
731 self.page = self.last_page
732 elif self.page < self.first_page:
732 elif self.page < self.first_page:
733 self.page = self.first_page
733 self.page = self.first_page
734
734
735 # Note: the number of items on this page can be less than
735 # Note: the number of items on this page can be less than
736 # items_per_page if the last page is not full
736 # items_per_page if the last page is not full
737 self.first_item = max(0, (self.item_count) - (self.page *
737 self.first_item = max(0, (self.item_count) - (self.page *
738 items_per_page))
738 items_per_page))
739 self.last_item = ((self.item_count - 1) - items_per_page *
739 self.last_item = ((self.item_count - 1) - items_per_page *
740 (self.page - 1))
740 (self.page - 1))
741
741
742 self.items = list(self.collection[self.first_item:self.last_item + 1])
742 self.items = list(self.collection[self.first_item:self.last_item + 1])
743
743
744 # Links to previous and next page
744 # Links to previous and next page
745 if self.page > self.first_page:
745 if self.page > self.first_page:
746 self.previous_page = self.page - 1
746 self.previous_page = self.page - 1
747 else:
747 else:
748 self.previous_page = None
748 self.previous_page = None
749
749
750 if self.page < self.last_page:
750 if self.page < self.last_page:
751 self.next_page = self.page + 1
751 self.next_page = self.page + 1
752 else:
752 else:
753 self.next_page = None
753 self.next_page = None
754
754
755 # No items available
755 # No items available
756 else:
756 else:
757 self.first_page = None
757 self.first_page = None
758 self.page_count = 0
758 self.page_count = 0
759 self.last_page = None
759 self.last_page = None
760 self.first_item = None
760 self.first_item = None
761 self.last_item = None
761 self.last_item = None
762 self.previous_page = None
762 self.previous_page = None
763 self.next_page = None
763 self.next_page = None
764 self.items = []
764 self.items = []
765
765
766 # This is a subclass of the 'list' type. Initialise the list now.
766 # This is a subclass of the 'list' type. Initialise the list now.
767 list.__init__(self, reversed(self.items))
767 list.__init__(self, reversed(self.items))
768
768
769
769
770 def changed_tooltip(nodes):
770 def changed_tooltip(nodes):
771 """
771 """
772 Generates a html string for changed nodes in changeset page.
772 Generates a html string for changed nodes in changeset page.
773 It limits the output to 30 entries
773 It limits the output to 30 entries
774
774
775 :param nodes: LazyNodesGenerator
775 :param nodes: LazyNodesGenerator
776 """
776 """
777 if nodes:
777 if nodes:
778 pref = ': <br/> '
778 pref = ': <br/> '
779 suf = ''
779 suf = ''
780 if len(nodes) > 30:
780 if len(nodes) > 30:
781 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
781 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
782 return literal(pref + '<br/> '.join([safe_unicode(x.path)
782 return literal(pref + '<br/> '.join([safe_unicode(x.path)
783 for x in nodes[:30]]) + suf)
783 for x in nodes[:30]]) + suf)
784 else:
784 else:
785 return ': ' + _('No Files')
785 return ': ' + _('No Files')
786
786
787
787
788 def repo_link(groups_and_repos):
788 def repo_link(groups_and_repos):
789 """
789 """
790 Makes a breadcrumbs link to repo within a group
790 Makes a breadcrumbs link to repo within a group
791 joins &raquo; on each group to create a fancy link
791 joins &raquo; on each group to create a fancy link
792
792
793 ex::
793 ex::
794 group >> subgroup >> repo
794 group >> subgroup >> repo
795
795
796 :param groups_and_repos:
796 :param groups_and_repos:
797 """
797 """
798 groups, repo_name = groups_and_repos
798 groups, repo_name = groups_and_repos
799
799
800 if not groups:
800 if not groups:
801 return repo_name
801 return repo_name
802 else:
802 else:
803 def make_link(group):
803 def make_link(group):
804 return link_to(group.name, url('repos_group_home',
804 return link_to(group.name, url('repos_group_home',
805 group_name=group.group_name))
805 group_name=group.group_name))
806 return literal(' &raquo; '.join(map(make_link, groups)) + \
806 return literal(' &raquo; '.join(map(make_link, groups)) + \
807 " &raquo; " + repo_name)
807 " &raquo; " + repo_name)
808
808
809
809
810 def fancy_file_stats(stats):
810 def fancy_file_stats(stats):
811 """
811 """
812 Displays a fancy two colored bar for number of added/deleted
812 Displays a fancy two colored bar for number of added/deleted
813 lines of code on file
813 lines of code on file
814
814
815 :param stats: two element list of added/deleted lines of code
815 :param stats: two element list of added/deleted lines of code
816 """
816 """
817
817
818 a, d, t = stats[0], stats[1], stats[0] + stats[1]
818 a, d, t = stats[0], stats[1], stats[0] + stats[1]
819 width = 100
819 width = 100
820 unit = float(width) / (t or 1)
820 unit = float(width) / (t or 1)
821
821
822 # needs > 9% of width to be visible or 0 to be hidden
822 # needs > 9% of width to be visible or 0 to be hidden
823 a_p = max(9, unit * a) if a > 0 else 0
823 a_p = max(9, unit * a) if a > 0 else 0
824 d_p = max(9, unit * d) if d > 0 else 0
824 d_p = max(9, unit * d) if d > 0 else 0
825 p_sum = a_p + d_p
825 p_sum = a_p + d_p
826
826
827 if p_sum > width:
827 if p_sum > width:
828 #adjust the percentage to be == 100% since we adjusted to 9
828 #adjust the percentage to be == 100% since we adjusted to 9
829 if a_p > d_p:
829 if a_p > d_p:
830 a_p = a_p - (p_sum - width)
830 a_p = a_p - (p_sum - width)
831 else:
831 else:
832 d_p = d_p - (p_sum - width)
832 d_p = d_p - (p_sum - width)
833
833
834 a_v = a if a > 0 else ''
834 a_v = a if a > 0 else ''
835 d_v = d if d > 0 else ''
835 d_v = d if d > 0 else ''
836
836
837 def cgen(l_type):
837 def cgen(l_type):
838 mapping = {'tr': 'top-right-rounded-corner-mid',
838 mapping = {'tr': 'top-right-rounded-corner-mid',
839 'tl': 'top-left-rounded-corner-mid',
839 'tl': 'top-left-rounded-corner-mid',
840 'br': 'bottom-right-rounded-corner-mid',
840 'br': 'bottom-right-rounded-corner-mid',
841 'bl': 'bottom-left-rounded-corner-mid'}
841 'bl': 'bottom-left-rounded-corner-mid'}
842 map_getter = lambda x: mapping[x]
842 map_getter = lambda x: mapping[x]
843
843
844 if l_type == 'a' and d_v:
844 if l_type == 'a' and d_v:
845 #case when added and deleted are present
845 #case when added and deleted are present
846 return ' '.join(map(map_getter, ['tl', 'bl']))
846 return ' '.join(map(map_getter, ['tl', 'bl']))
847
847
848 if l_type == 'a' and not d_v:
848 if l_type == 'a' and not d_v:
849 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
849 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
850
850
851 if l_type == 'd' and a_v:
851 if l_type == 'd' and a_v:
852 return ' '.join(map(map_getter, ['tr', 'br']))
852 return ' '.join(map(map_getter, ['tr', 'br']))
853
853
854 if l_type == 'd' and not a_v:
854 if l_type == 'd' and not a_v:
855 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
855 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
856
856
857 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
857 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
858 cgen('a'), a_p, a_v
858 cgen('a'), a_p, a_v
859 )
859 )
860 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
860 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
861 cgen('d'), d_p, d_v
861 cgen('d'), d_p, d_v
862 )
862 )
863 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
863 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
864
864
865
865
866 def urlify_text(text_):
866 def urlify_text(text_):
867 import re
867 import re
868
868
869 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
869 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
870 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
870 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
871
871
872 def url_func(match_obj):
872 def url_func(match_obj):
873 url_full = match_obj.groups()[0]
873 url_full = match_obj.groups()[0]
874 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
874 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
875
875
876 return literal(url_pat.sub(url_func, text_))
876 return literal(url_pat.sub(url_func, text_))
877
877
878
878
879 def urlify_changesets(text_, repository):
879 def urlify_changesets(text_, repository):
880 """
880 """
881 Extract revision ids from changeset and make link from them
881 Extract revision ids from changeset and make link from them
882
882
883 :param text_:
883 :param text_:
884 :param repository:
884 :param repository:
885 """
885 """
886 import re
886 import re
887 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
887 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
888
888
889 def url_func(match_obj):
889 def url_func(match_obj):
890 rev = match_obj.groups()[0]
890 rev = match_obj.groups()[0]
891 pref = ''
891 pref = ''
892 if match_obj.group().startswith(' '):
892 if match_obj.group().startswith(' '):
893 pref = ' '
893 pref = ' '
894 tmpl = (
894 tmpl = (
895 '%(pref)s<a class="%(cls)s" href="%(url)s">'
895 '%(pref)s<a class="%(cls)s" href="%(url)s">'
896 '%(rev)s'
896 '%(rev)s'
897 '</a>'
897 '</a>'
898 )
898 )
899 return tmpl % {
899 return tmpl % {
900 'pref': pref,
900 'pref': pref,
901 'cls': 'revision-link',
901 'cls': 'revision-link',
902 'url': url('changeset_home', repo_name=repository, revision=rev),
902 'url': url('changeset_home', repo_name=repository, revision=rev),
903 'rev': rev,
903 'rev': rev,
904 }
904 }
905
905
906 newtext = URL_PAT.sub(url_func, text_)
906 newtext = URL_PAT.sub(url_func, text_)
907
907
908 return newtext
908 return newtext
909
909
910
910
911 def urlify_commit(text_, repository=None, link_=None):
911 def urlify_commit(text_, repository=None, link_=None):
912 """
912 """
913 Parses given text message and makes proper links.
913 Parses given text message and makes proper links.
914 issues are linked to given issue-server, and rest is a changeset link
914 issues are linked to given issue-server, and rest is a changeset link
915 if link_ is given, in other case it's a plain text
915 if link_ is given, in other case it's a plain text
916
916
917 :param text_:
917 :param text_:
918 :param repository:
918 :param repository:
919 :param link_: changeset link
919 :param link_: changeset link
920 """
920 """
921 import re
921 import re
922 import traceback
922 import traceback
923
923
924 def escaper(string):
924 def escaper(string):
925 return string.replace('<', '&lt;').replace('>', '&gt;')
925 return string.replace('<', '&lt;').replace('>', '&gt;')
926
926
927 def linkify_others(t, l):
927 def linkify_others(t, l):
928 urls = re.compile(r'(\<a.*?\<\/a\>)',)
928 urls = re.compile(r'(\<a.*?\<\/a\>)',)
929 links = []
929 links = []
930 for e in urls.split(t):
930 for e in urls.split(t):
931 if not urls.match(e):
931 if not urls.match(e):
932 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
932 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
933 else:
933 else:
934 links.append(e)
934 links.append(e)
935
935
936 return ''.join(links)
936 return ''.join(links)
937
937
938 # urlify changesets - extrac revisions and make link out of them
938 # urlify changesets - extrac revisions and make link out of them
939 text_ = urlify_changesets(escaper(text_), repository)
939 text_ = urlify_changesets(escaper(text_), repository)
940
940
941 try:
941 try:
942 conf = config['app_conf']
942 conf = config['app_conf']
943
943
944 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
944 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
945
945
946 if URL_PAT:
946 if URL_PAT:
947 ISSUE_SERVER_LNK = conf.get('issue_server_link')
947 ISSUE_SERVER_LNK = conf.get('issue_server_link')
948 ISSUE_PREFIX = conf.get('issue_prefix')
948 ISSUE_PREFIX = conf.get('issue_prefix')
949
949
950 def url_func(match_obj):
950 def url_func(match_obj):
951 pref = ''
951 pref = ''
952 if match_obj.group().startswith(' '):
952 if match_obj.group().startswith(' '):
953 pref = ' '
953 pref = ' '
954
954
955 issue_id = ''.join(match_obj.groups())
955 issue_id = ''.join(match_obj.groups())
956 tmpl = (
956 tmpl = (
957 '%(pref)s<a class="%(cls)s" href="%(url)s">'
957 '%(pref)s<a class="%(cls)s" href="%(url)s">'
958 '%(issue-prefix)s%(id-repr)s'
958 '%(issue-prefix)s%(id-repr)s'
959 '</a>'
959 '</a>'
960 )
960 )
961 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
961 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
962 if repository:
962 if repository:
963 url = url.replace('{repo}', repository)
963 url = url.replace('{repo}', repository)
964 repo_name = repository.split(URL_SEP)[-1]
964 repo_name = repository.split(URL_SEP)[-1]
965 url = url.replace('{repo_name}', repo_name)
965 url = url.replace('{repo_name}', repo_name)
966 return tmpl % {
966 return tmpl % {
967 'pref': pref,
967 'pref': pref,
968 'cls': 'issue-tracker-link',
968 'cls': 'issue-tracker-link',
969 'url': url,
969 'url': url,
970 'id-repr': issue_id,
970 'id-repr': issue_id,
971 'issue-prefix': ISSUE_PREFIX,
971 'issue-prefix': ISSUE_PREFIX,
972 'serv': ISSUE_SERVER_LNK,
972 'serv': ISSUE_SERVER_LNK,
973 }
973 }
974
974
975 newtext = URL_PAT.sub(url_func, text_)
975 newtext = URL_PAT.sub(url_func, text_)
976
976
977 if link_:
977 if link_:
978 # wrap not links into final link => link_
978 # wrap not links into final link => link_
979 newtext = linkify_others(newtext, link_)
979 newtext = linkify_others(newtext, link_)
980
980
981 return literal(newtext)
981 return literal(newtext)
982 except:
982 except:
983 log.error(traceback.format_exc())
983 log.error(traceback.format_exc())
984 pass
984 pass
985
985
986 return text_
986 return text_
987
987
988
988
989 def rst(source):
989 def rst(source):
990 return literal('<div class="rst-block">%s</div>' %
990 return literal('<div class="rst-block">%s</div>' %
991 MarkupRenderer.rst(source))
991 MarkupRenderer.rst(source))
992
992
993
993
994 def rst_w_mentions(source):
994 def rst_w_mentions(source):
995 """
995 """
996 Wrapped rst renderer with @mention highlighting
996 Wrapped rst renderer with @mention highlighting
997
997
998 :param source:
998 :param source:
999 """
999 """
1000 return literal('<div class="rst-block">%s</div>' %
1000 return literal('<div class="rst-block">%s</div>' %
1001 MarkupRenderer.rst_with_mentions(source))
1001 MarkupRenderer.rst_with_mentions(source))
1002
1002
1003
1003
1004 def changeset_status(repo, revision):
1004 def changeset_status(repo, revision):
1005 return ChangesetStatusModel().get_status(repo, revision)
1005 return ChangesetStatusModel().get_status(repo, revision)
1006
1006
1007
1007
1008 def changeset_status_lbl(changeset_status):
1008 def changeset_status_lbl(changeset_status):
1009 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1009 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1010
1011
1012 def get_permission_name(key):
1013 return dict(Permission.PERMS).get(key)
@@ -1,1635 +1,1657
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 from collections import defaultdict
31 from collections import defaultdict
32
32
33 from sqlalchemy import *
33 from sqlalchemy import *
34 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.exc import DatabaseError
36 from sqlalchemy.exc import DatabaseError
37 from beaker.cache import cache_region, region_invalidate
37 from beaker.cache import cache_region, region_invalidate
38 from webob.exc import HTTPNotFound
38 from webob.exc import HTTPNotFound
39
39
40 from pylons.i18n.translation import lazy_ugettext as _
40 from pylons.i18n.translation import lazy_ugettext as _
41
41
42 from rhodecode.lib.vcs import get_backend
42 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs.utils.helpers import get_scm
43 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
45 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46
46
47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
47 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 safe_unicode
48 safe_unicode
49 from rhodecode.lib.compat import json
49 from rhodecode.lib.compat import json
50 from rhodecode.lib.caching_query import FromCache
50 from rhodecode.lib.caching_query import FromCache
51
51
52 from rhodecode.model.meta import Base, Session
52 from rhodecode.model.meta import Base, Session
53
53
54 URL_SEP = '/'
54 URL_SEP = '/'
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57 #==============================================================================
57 #==============================================================================
58 # BASE CLASSES
58 # BASE CLASSES
59 #==============================================================================
59 #==============================================================================
60
60
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
61 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62
62
63
63
64 class ModelSerializer(json.JSONEncoder):
64 class ModelSerializer(json.JSONEncoder):
65 """
65 """
66 Simple Serializer for JSON,
66 Simple Serializer for JSON,
67
67
68 usage::
68 usage::
69
69
70 to make object customized for serialization implement a __json__
70 to make object customized for serialization implement a __json__
71 method that will return a dict for serialization into json
71 method that will return a dict for serialization into json
72
72
73 example::
73 example::
74
74
75 class Task(object):
75 class Task(object):
76
76
77 def __init__(self, name, value):
77 def __init__(self, name, value):
78 self.name = name
78 self.name = name
79 self.value = value
79 self.value = value
80
80
81 def __json__(self):
81 def __json__(self):
82 return dict(name=self.name,
82 return dict(name=self.name,
83 value=self.value)
83 value=self.value)
84
84
85 """
85 """
86
86
87 def default(self, obj):
87 def default(self, obj):
88
88
89 if hasattr(obj, '__json__'):
89 if hasattr(obj, '__json__'):
90 return obj.__json__()
90 return obj.__json__()
91 else:
91 else:
92 return json.JSONEncoder.default(self, obj)
92 return json.JSONEncoder.default(self, obj)
93
93
94
94
95 class BaseModel(object):
95 class BaseModel(object):
96 """
96 """
97 Base Model for all classess
97 Base Model for all classess
98 """
98 """
99
99
100 @classmethod
100 @classmethod
101 def _get_keys(cls):
101 def _get_keys(cls):
102 """return column names for this model """
102 """return column names for this model """
103 return class_mapper(cls).c.keys()
103 return class_mapper(cls).c.keys()
104
104
105 def get_dict(self):
105 def get_dict(self):
106 """
106 """
107 return dict with keys and values corresponding
107 return dict with keys and values corresponding
108 to this model data """
108 to this model data """
109
109
110 d = {}
110 d = {}
111 for k in self._get_keys():
111 for k in self._get_keys():
112 d[k] = getattr(self, k)
112 d[k] = getattr(self, k)
113
113
114 # also use __json__() if present to get additional fields
114 # also use __json__() if present to get additional fields
115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
115 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
116 d[k] = val
116 d[k] = val
117 return d
117 return d
118
118
119 def get_appstruct(self):
119 def get_appstruct(self):
120 """return list with keys and values tupples corresponding
120 """return list with keys and values tupples corresponding
121 to this model data """
121 to this model data """
122
122
123 l = []
123 l = []
124 for k in self._get_keys():
124 for k in self._get_keys():
125 l.append((k, getattr(self, k),))
125 l.append((k, getattr(self, k),))
126 return l
126 return l
127
127
128 def populate_obj(self, populate_dict):
128 def populate_obj(self, populate_dict):
129 """populate model with data from given populate_dict"""
129 """populate model with data from given populate_dict"""
130
130
131 for k in self._get_keys():
131 for k in self._get_keys():
132 if k in populate_dict:
132 if k in populate_dict:
133 setattr(self, k, populate_dict[k])
133 setattr(self, k, populate_dict[k])
134
134
135 @classmethod
135 @classmethod
136 def query(cls):
136 def query(cls):
137 return Session().query(cls)
137 return Session().query(cls)
138
138
139 @classmethod
139 @classmethod
140 def get(cls, id_):
140 def get(cls, id_):
141 if id_:
141 if id_:
142 return cls.query().get(id_)
142 return cls.query().get(id_)
143
143
144 @classmethod
144 @classmethod
145 def get_or_404(cls, id_):
145 def get_or_404(cls, id_):
146 if id_:
146 if id_:
147 res = cls.query().get(id_)
147 res = cls.query().get(id_)
148 if not res:
148 if not res:
149 raise HTTPNotFound
149 raise HTTPNotFound
150 return res
150 return res
151
151
152 @classmethod
152 @classmethod
153 def getAll(cls):
153 def getAll(cls):
154 return cls.query().all()
154 return cls.query().all()
155
155
156 @classmethod
156 @classmethod
157 def delete(cls, id_):
157 def delete(cls, id_):
158 obj = cls.query().get(id_)
158 obj = cls.query().get(id_)
159 Session().delete(obj)
159 Session().delete(obj)
160
160
161 def __repr__(self):
161 def __repr__(self):
162 if hasattr(self, '__unicode__'):
162 if hasattr(self, '__unicode__'):
163 # python repr needs to return str
163 # python repr needs to return str
164 return safe_str(self.__unicode__())
164 return safe_str(self.__unicode__())
165 return '<DB:%s>' % (self.__class__.__name__)
165 return '<DB:%s>' % (self.__class__.__name__)
166
166
167
167
168 class RhodeCodeSetting(Base, BaseModel):
168 class RhodeCodeSetting(Base, BaseModel):
169 __tablename__ = 'rhodecode_settings'
169 __tablename__ = 'rhodecode_settings'
170 __table_args__ = (
170 __table_args__ = (
171 UniqueConstraint('app_settings_name'),
171 UniqueConstraint('app_settings_name'),
172 {'extend_existing': True, 'mysql_engine': 'InnoDB',
172 {'extend_existing': True, 'mysql_engine': 'InnoDB',
173 'mysql_charset': 'utf8'}
173 'mysql_charset': 'utf8'}
174 )
174 )
175 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
175 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
176 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
176 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
177 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
177 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
178
178
179 def __init__(self, k='', v=''):
179 def __init__(self, k='', v=''):
180 self.app_settings_name = k
180 self.app_settings_name = k
181 self.app_settings_value = v
181 self.app_settings_value = v
182
182
183 @validates('_app_settings_value')
183 @validates('_app_settings_value')
184 def validate_settings_value(self, key, val):
184 def validate_settings_value(self, key, val):
185 assert type(val) == unicode
185 assert type(val) == unicode
186 return val
186 return val
187
187
188 @hybrid_property
188 @hybrid_property
189 def app_settings_value(self):
189 def app_settings_value(self):
190 v = self._app_settings_value
190 v = self._app_settings_value
191 if self.app_settings_name == 'ldap_active':
191 if self.app_settings_name == 'ldap_active':
192 v = str2bool(v)
192 v = str2bool(v)
193 return v
193 return v
194
194
195 @app_settings_value.setter
195 @app_settings_value.setter
196 def app_settings_value(self, val):
196 def app_settings_value(self, val):
197 """
197 """
198 Setter that will always make sure we use unicode in app_settings_value
198 Setter that will always make sure we use unicode in app_settings_value
199
199
200 :param val:
200 :param val:
201 """
201 """
202 self._app_settings_value = safe_unicode(val)
202 self._app_settings_value = safe_unicode(val)
203
203
204 def __unicode__(self):
204 def __unicode__(self):
205 return u"<%s('%s:%s')>" % (
205 return u"<%s('%s:%s')>" % (
206 self.__class__.__name__,
206 self.__class__.__name__,
207 self.app_settings_name, self.app_settings_value
207 self.app_settings_name, self.app_settings_value
208 )
208 )
209
209
210 @classmethod
210 @classmethod
211 def get_by_name(cls, ldap_key):
211 def get_by_name(cls, ldap_key):
212 return cls.query()\
212 return cls.query()\
213 .filter(cls.app_settings_name == ldap_key).scalar()
213 .filter(cls.app_settings_name == ldap_key).scalar()
214
214
215 @classmethod
215 @classmethod
216 def get_app_settings(cls, cache=False):
216 def get_app_settings(cls, cache=False):
217
217
218 ret = cls.query()
218 ret = cls.query()
219
219
220 if cache:
220 if cache:
221 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
221 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
222
222
223 if not ret:
223 if not ret:
224 raise Exception('Could not get application settings !')
224 raise Exception('Could not get application settings !')
225 settings = {}
225 settings = {}
226 for each in ret:
226 for each in ret:
227 settings['rhodecode_' + each.app_settings_name] = \
227 settings['rhodecode_' + each.app_settings_name] = \
228 each.app_settings_value
228 each.app_settings_value
229
229
230 return settings
230 return settings
231
231
232 @classmethod
232 @classmethod
233 def get_ldap_settings(cls, cache=False):
233 def get_ldap_settings(cls, cache=False):
234 ret = cls.query()\
234 ret = cls.query()\
235 .filter(cls.app_settings_name.startswith('ldap_')).all()
235 .filter(cls.app_settings_name.startswith('ldap_')).all()
236 fd = {}
236 fd = {}
237 for row in ret:
237 for row in ret:
238 fd.update({row.app_settings_name: row.app_settings_value})
238 fd.update({row.app_settings_name: row.app_settings_value})
239
239
240 return fd
240 return fd
241
241
242
242
243 class RhodeCodeUi(Base, BaseModel):
243 class RhodeCodeUi(Base, BaseModel):
244 __tablename__ = 'rhodecode_ui'
244 __tablename__ = 'rhodecode_ui'
245 __table_args__ = (
245 __table_args__ = (
246 UniqueConstraint('ui_key'),
246 UniqueConstraint('ui_key'),
247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
247 {'extend_existing': True, 'mysql_engine': 'InnoDB',
248 'mysql_charset': 'utf8'}
248 'mysql_charset': 'utf8'}
249 )
249 )
250
250
251 HOOK_UPDATE = 'changegroup.update'
251 HOOK_UPDATE = 'changegroup.update'
252 HOOK_REPO_SIZE = 'changegroup.repo_size'
252 HOOK_REPO_SIZE = 'changegroup.repo_size'
253 HOOK_PUSH = 'changegroup.push_logger'
253 HOOK_PUSH = 'changegroup.push_logger'
254 HOOK_PULL = 'preoutgoing.pull_logger'
254 HOOK_PULL = 'preoutgoing.pull_logger'
255
255
256 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
256 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
257 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
257 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
258 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
258 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
259 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
259 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
260 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
260 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
261
261
262 @classmethod
262 @classmethod
263 def get_by_key(cls, key):
263 def get_by_key(cls, key):
264 return cls.query().filter(cls.ui_key == key)
264 return cls.query().filter(cls.ui_key == key)
265
265
266 @classmethod
266 @classmethod
267 def get_builtin_hooks(cls):
267 def get_builtin_hooks(cls):
268 q = cls.query()
268 q = cls.query()
269 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
269 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
270 cls.HOOK_REPO_SIZE,
270 cls.HOOK_REPO_SIZE,
271 cls.HOOK_PUSH, cls.HOOK_PULL]))
271 cls.HOOK_PUSH, cls.HOOK_PULL]))
272 return q.all()
272 return q.all()
273
273
274 @classmethod
274 @classmethod
275 def get_custom_hooks(cls):
275 def get_custom_hooks(cls):
276 q = cls.query()
276 q = cls.query()
277 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
277 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
278 cls.HOOK_REPO_SIZE,
278 cls.HOOK_REPO_SIZE,
279 cls.HOOK_PUSH, cls.HOOK_PULL]))
279 cls.HOOK_PUSH, cls.HOOK_PULL]))
280 q = q.filter(cls.ui_section == 'hooks')
280 q = q.filter(cls.ui_section == 'hooks')
281 return q.all()
281 return q.all()
282
282
283 @classmethod
283 @classmethod
284 def get_repos_location(cls):
284 def get_repos_location(cls):
285 return cls.get_by_key('/').one().ui_value
285 return cls.get_by_key('/').one().ui_value
286
286
287 @classmethod
287 @classmethod
288 def create_or_update_hook(cls, key, val):
288 def create_or_update_hook(cls, key, val):
289 new_ui = cls.get_by_key(key).scalar() or cls()
289 new_ui = cls.get_by_key(key).scalar() or cls()
290 new_ui.ui_section = 'hooks'
290 new_ui.ui_section = 'hooks'
291 new_ui.ui_active = True
291 new_ui.ui_active = True
292 new_ui.ui_key = key
292 new_ui.ui_key = key
293 new_ui.ui_value = val
293 new_ui.ui_value = val
294
294
295 Session().add(new_ui)
295 Session().add(new_ui)
296
296
297
297
298 class User(Base, BaseModel):
298 class User(Base, BaseModel):
299 __tablename__ = 'users'
299 __tablename__ = 'users'
300 __table_args__ = (
300 __table_args__ = (
301 UniqueConstraint('username'), UniqueConstraint('email'),
301 UniqueConstraint('username'), UniqueConstraint('email'),
302 {'extend_existing': True, 'mysql_engine': 'InnoDB',
302 {'extend_existing': True, 'mysql_engine': 'InnoDB',
303 'mysql_charset': 'utf8'}
303 'mysql_charset': 'utf8'}
304 )
304 )
305 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
305 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
306 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
308 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
308 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
309 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
309 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
310 name = Column("firstname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
310 name = Column("firstname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
313 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
313 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
314 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
314 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
315 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
315 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
316
316
317 user_log = relationship('UserLog', cascade='all')
317 user_log = relationship('UserLog', cascade='all')
318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
319
319
320 repositories = relationship('Repository')
320 repositories = relationship('Repository')
321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
324
324
325 group_member = relationship('UsersGroupMember', cascade='all')
325 group_member = relationship('UsersGroupMember', cascade='all')
326
326
327 notifications = relationship('UserNotification', cascade='all')
327 notifications = relationship('UserNotification', cascade='all')
328 # notifications assigned to this user
328 # notifications assigned to this user
329 user_created_notifications = relationship('Notification', cascade='all')
329 user_created_notifications = relationship('Notification', cascade='all')
330 # comments created by this user
330 # comments created by this user
331 user_comments = relationship('ChangesetComment', cascade='all')
331 user_comments = relationship('ChangesetComment', cascade='all')
332
332
333 @hybrid_property
333 @hybrid_property
334 def email(self):
334 def email(self):
335 return self._email
335 return self._email
336
336
337 @email.setter
337 @email.setter
338 def email(self, val):
338 def email(self, val):
339 self._email = val.lower() if val else None
339 self._email = val.lower() if val else None
340
340
341 @property
341 @property
342 def emails(self):
342 def emails(self):
343 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
343 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
344 return [self.email] + [x.email for x in other]
344 return [self.email] + [x.email for x in other]
345
345
346 @property
346 @property
347 def full_name(self):
347 def full_name(self):
348 return '%s %s' % (self.name, self.lastname)
348 return '%s %s' % (self.name, self.lastname)
349
349
350 @property
350 @property
351 def full_name_or_username(self):
351 def full_name_or_username(self):
352 return ('%s %s' % (self.name, self.lastname)
352 return ('%s %s' % (self.name, self.lastname)
353 if (self.name and self.lastname) else self.username)
353 if (self.name and self.lastname) else self.username)
354
354
355 @property
355 @property
356 def full_contact(self):
356 def full_contact(self):
357 return '%s %s <%s>' % (self.name, self.lastname, self.email)
357 return '%s %s <%s>' % (self.name, self.lastname, self.email)
358
358
359 @property
359 @property
360 def short_contact(self):
360 def short_contact(self):
361 return '%s %s' % (self.name, self.lastname)
361 return '%s %s' % (self.name, self.lastname)
362
362
363 @property
363 @property
364 def is_admin(self):
364 def is_admin(self):
365 return self.admin
365 return self.admin
366
366
367 def __unicode__(self):
367 def __unicode__(self):
368 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
368 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
369 self.user_id, self.username)
369 self.user_id, self.username)
370
370
371 @classmethod
371 @classmethod
372 def get_by_username(cls, username, case_insensitive=False, cache=False):
372 def get_by_username(cls, username, case_insensitive=False, cache=False):
373 if case_insensitive:
373 if case_insensitive:
374 q = cls.query().filter(cls.username.ilike(username))
374 q = cls.query().filter(cls.username.ilike(username))
375 else:
375 else:
376 q = cls.query().filter(cls.username == username)
376 q = cls.query().filter(cls.username == username)
377
377
378 if cache:
378 if cache:
379 q = q.options(FromCache(
379 q = q.options(FromCache(
380 "sql_cache_short",
380 "sql_cache_short",
381 "get_user_%s" % _hash_key(username)
381 "get_user_%s" % _hash_key(username)
382 )
382 )
383 )
383 )
384 return q.scalar()
384 return q.scalar()
385
385
386 @classmethod
386 @classmethod
387 def get_by_api_key(cls, api_key, cache=False):
387 def get_by_api_key(cls, api_key, cache=False):
388 q = cls.query().filter(cls.api_key == api_key)
388 q = cls.query().filter(cls.api_key == api_key)
389
389
390 if cache:
390 if cache:
391 q = q.options(FromCache("sql_cache_short",
391 q = q.options(FromCache("sql_cache_short",
392 "get_api_key_%s" % api_key))
392 "get_api_key_%s" % api_key))
393 return q.scalar()
393 return q.scalar()
394
394
395 @classmethod
395 @classmethod
396 def get_by_email(cls, email, case_insensitive=False, cache=False):
396 def get_by_email(cls, email, case_insensitive=False, cache=False):
397 if case_insensitive:
397 if case_insensitive:
398 q = cls.query().filter(cls.email.ilike(email))
398 q = cls.query().filter(cls.email.ilike(email))
399 else:
399 else:
400 q = cls.query().filter(cls.email == email)
400 q = cls.query().filter(cls.email == email)
401
401
402 if cache:
402 if cache:
403 q = q.options(FromCache("sql_cache_short",
403 q = q.options(FromCache("sql_cache_short",
404 "get_email_key_%s" % email))
404 "get_email_key_%s" % email))
405
405
406 ret = q.scalar()
406 ret = q.scalar()
407 if ret is None:
407 if ret is None:
408 q = UserEmailMap.query()
408 q = UserEmailMap.query()
409 # try fetching in alternate email map
409 # try fetching in alternate email map
410 if case_insensitive:
410 if case_insensitive:
411 q = q.filter(UserEmailMap.email.ilike(email))
411 q = q.filter(UserEmailMap.email.ilike(email))
412 else:
412 else:
413 q = q.filter(UserEmailMap.email == email)
413 q = q.filter(UserEmailMap.email == email)
414 q = q.options(joinedload(UserEmailMap.user))
414 q = q.options(joinedload(UserEmailMap.user))
415 if cache:
415 if cache:
416 q = q.options(FromCache("sql_cache_short",
416 q = q.options(FromCache("sql_cache_short",
417 "get_email_map_key_%s" % email))
417 "get_email_map_key_%s" % email))
418 ret = getattr(q.scalar(), 'user', None)
418 ret = getattr(q.scalar(), 'user', None)
419
419
420 return ret
420 return ret
421
421
422 def update_lastlogin(self):
422 def update_lastlogin(self):
423 """Update user lastlogin"""
423 """Update user lastlogin"""
424 self.last_login = datetime.datetime.now()
424 self.last_login = datetime.datetime.now()
425 Session().add(self)
425 Session().add(self)
426 log.debug('updated user %s lastlogin' % self.username)
426 log.debug('updated user %s lastlogin' % self.username)
427
427
428 def get_api_data(self):
428 def get_api_data(self):
429 """
429 """
430 Common function for generating user related data for API
430 Common function for generating user related data for API
431 """
431 """
432 user = self
432 user = self
433 data = dict(
433 data = dict(
434 user_id=user.user_id,
434 user_id=user.user_id,
435 username=user.username,
435 username=user.username,
436 firstname=user.name,
436 firstname=user.name,
437 lastname=user.lastname,
437 lastname=user.lastname,
438 email=user.email,
438 email=user.email,
439 emails=user.emails,
439 emails=user.emails,
440 api_key=user.api_key,
440 api_key=user.api_key,
441 active=user.active,
441 active=user.active,
442 admin=user.admin,
442 admin=user.admin,
443 ldap_dn=user.ldap_dn,
443 ldap_dn=user.ldap_dn,
444 last_login=user.last_login,
444 last_login=user.last_login,
445 )
445 )
446 return data
446 return data
447
447
448 def __json__(self):
448 def __json__(self):
449 return dict(
449 return dict(
450 user_id=self.user_id,
450 user_id=self.user_id,
451 first_name=self.name,
451 first_name=self.name,
452 last_name=self.lastname,
452 last_name=self.lastname,
453 email=self.email,
453 email=self.email,
454 full_name=self.full_name,
454 full_name=self.full_name,
455 full_name_or_username=self.full_name_or_username,
455 full_name_or_username=self.full_name_or_username,
456 short_contact=self.short_contact,
456 short_contact=self.short_contact,
457 full_contact=self.full_contact
457 full_contact=self.full_contact
458 )
458 )
459
459
460
460
461 class UserEmailMap(Base, BaseModel):
461 class UserEmailMap(Base, BaseModel):
462 __tablename__ = 'user_email_map'
462 __tablename__ = 'user_email_map'
463 __table_args__ = (
463 __table_args__ = (
464 Index('uem_email_idx', 'email'),
464 Index('uem_email_idx', 'email'),
465 UniqueConstraint('email'),
465 UniqueConstraint('email'),
466 {'extend_existing': True, 'mysql_engine': 'InnoDB',
466 {'extend_existing': True, 'mysql_engine': 'InnoDB',
467 'mysql_charset': 'utf8'}
467 'mysql_charset': 'utf8'}
468 )
468 )
469 __mapper_args__ = {}
469 __mapper_args__ = {}
470
470
471 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
471 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
472 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
472 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
473 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
473 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
474
474
475 user = relationship('User', lazy='joined')
475 user = relationship('User', lazy='joined')
476
476
477 @validates('_email')
477 @validates('_email')
478 def validate_email(self, key, email):
478 def validate_email(self, key, email):
479 # check if this email is not main one
479 # check if this email is not main one
480 main_email = Session().query(User).filter(User.email == email).scalar()
480 main_email = Session().query(User).filter(User.email == email).scalar()
481 if main_email is not None:
481 if main_email is not None:
482 raise AttributeError('email %s is present is user table' % email)
482 raise AttributeError('email %s is present is user table' % email)
483 return email
483 return email
484
484
485 @hybrid_property
485 @hybrid_property
486 def email(self):
486 def email(self):
487 return self._email
487 return self._email
488
488
489 @email.setter
489 @email.setter
490 def email(self, val):
490 def email(self, val):
491 self._email = val.lower() if val else None
491 self._email = val.lower() if val else None
492
492
493
493
494 class UserLog(Base, BaseModel):
494 class UserLog(Base, BaseModel):
495 __tablename__ = 'user_logs'
495 __tablename__ = 'user_logs'
496 __table_args__ = (
496 __table_args__ = (
497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
497 {'extend_existing': True, 'mysql_engine': 'InnoDB',
498 'mysql_charset': 'utf8'},
498 'mysql_charset': 'utf8'},
499 )
499 )
500 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
500 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
502 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
502 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
503 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
503 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
505 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
505 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
506 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
506 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
507
507
508 @property
508 @property
509 def action_as_day(self):
509 def action_as_day(self):
510 return datetime.date(*self.action_date.timetuple()[:3])
510 return datetime.date(*self.action_date.timetuple()[:3])
511
511
512 user = relationship('User')
512 user = relationship('User')
513 repository = relationship('Repository', cascade='')
513 repository = relationship('Repository', cascade='')
514
514
515
515
516 class UsersGroup(Base, BaseModel):
516 class UsersGroup(Base, BaseModel):
517 __tablename__ = 'users_groups'
517 __tablename__ = 'users_groups'
518 __table_args__ = (
518 __table_args__ = (
519 {'extend_existing': True, 'mysql_engine': 'InnoDB',
519 {'extend_existing': True, 'mysql_engine': 'InnoDB',
520 'mysql_charset': 'utf8'},
520 'mysql_charset': 'utf8'},
521 )
521 )
522
522
523 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
523 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
524 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
524 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
525 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
525 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
526
526
527 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
527 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
528 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
528 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
529 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
529 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
530
530
531 def __unicode__(self):
531 def __unicode__(self):
532 return u'<userGroup(%s)>' % (self.users_group_name)
532 return u'<userGroup(%s)>' % (self.users_group_name)
533
533
534 @classmethod
534 @classmethod
535 def get_by_group_name(cls, group_name, cache=False,
535 def get_by_group_name(cls, group_name, cache=False,
536 case_insensitive=False):
536 case_insensitive=False):
537 if case_insensitive:
537 if case_insensitive:
538 q = cls.query().filter(cls.users_group_name.ilike(group_name))
538 q = cls.query().filter(cls.users_group_name.ilike(group_name))
539 else:
539 else:
540 q = cls.query().filter(cls.users_group_name == group_name)
540 q = cls.query().filter(cls.users_group_name == group_name)
541 if cache:
541 if cache:
542 q = q.options(FromCache(
542 q = q.options(FromCache(
543 "sql_cache_short",
543 "sql_cache_short",
544 "get_user_%s" % _hash_key(group_name)
544 "get_user_%s" % _hash_key(group_name)
545 )
545 )
546 )
546 )
547 return q.scalar()
547 return q.scalar()
548
548
549 @classmethod
549 @classmethod
550 def get(cls, users_group_id, cache=False):
550 def get(cls, users_group_id, cache=False):
551 users_group = cls.query()
551 users_group = cls.query()
552 if cache:
552 if cache:
553 users_group = users_group.options(FromCache("sql_cache_short",
553 users_group = users_group.options(FromCache("sql_cache_short",
554 "get_users_group_%s" % users_group_id))
554 "get_users_group_%s" % users_group_id))
555 return users_group.get(users_group_id)
555 return users_group.get(users_group_id)
556
556
557 def get_api_data(self):
557 def get_api_data(self):
558 users_group = self
558 users_group = self
559
559
560 data = dict(
560 data = dict(
561 users_group_id=users_group.users_group_id,
561 users_group_id=users_group.users_group_id,
562 group_name=users_group.users_group_name,
562 group_name=users_group.users_group_name,
563 active=users_group.users_group_active,
563 active=users_group.users_group_active,
564 )
564 )
565
565
566 return data
566 return data
567
567
568
568
569 class UsersGroupMember(Base, BaseModel):
569 class UsersGroupMember(Base, BaseModel):
570 __tablename__ = 'users_groups_members'
570 __tablename__ = 'users_groups_members'
571 __table_args__ = (
571 __table_args__ = (
572 {'extend_existing': True, 'mysql_engine': 'InnoDB',
572 {'extend_existing': True, 'mysql_engine': 'InnoDB',
573 'mysql_charset': 'utf8'},
573 'mysql_charset': 'utf8'},
574 )
574 )
575
575
576 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
576 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
577 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
577 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
578 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
578 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
579
579
580 user = relationship('User', lazy='joined')
580 user = relationship('User', lazy='joined')
581 users_group = relationship('UsersGroup')
581 users_group = relationship('UsersGroup')
582
582
583 def __init__(self, gr_id='', u_id=''):
583 def __init__(self, gr_id='', u_id=''):
584 self.users_group_id = gr_id
584 self.users_group_id = gr_id
585 self.user_id = u_id
585 self.user_id = u_id
586
586
587
587
588 class Repository(Base, BaseModel):
588 class Repository(Base, BaseModel):
589 __tablename__ = 'repositories'
589 __tablename__ = 'repositories'
590 __table_args__ = (
590 __table_args__ = (
591 UniqueConstraint('repo_name'),
591 UniqueConstraint('repo_name'),
592 {'extend_existing': True, 'mysql_engine': 'InnoDB',
592 {'extend_existing': True, 'mysql_engine': 'InnoDB',
593 'mysql_charset': 'utf8'},
593 'mysql_charset': 'utf8'},
594 )
594 )
595
595
596 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
596 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
597 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
597 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
598 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
598 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
599 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
599 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
600 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
600 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
601 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
601 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
602 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
602 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
603 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
603 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
604 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
604 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
605 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
605 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
606 landing_rev = Column("landing_revision", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
606 landing_rev = Column("landing_revision", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
607
607
608 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
608 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
609 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
609 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
610
610
611 user = relationship('User')
611 user = relationship('User')
612 fork = relationship('Repository', remote_side=repo_id)
612 fork = relationship('Repository', remote_side=repo_id)
613 group = relationship('RepoGroup')
613 group = relationship('RepoGroup')
614 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
614 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
615 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
615 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
616 stats = relationship('Statistics', cascade='all', uselist=False)
616 stats = relationship('Statistics', cascade='all', uselist=False)
617
617
618 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
618 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
619
619
620 logs = relationship('UserLog')
620 logs = relationship('UserLog')
621 comments = relationship('ChangesetComment')
621 comments = relationship('ChangesetComment')
622
622
623 def __unicode__(self):
623 def __unicode__(self):
624 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
624 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
625 self.repo_name)
625 self.repo_name)
626
626
627 @classmethod
627 @classmethod
628 def url_sep(cls):
628 def url_sep(cls):
629 return URL_SEP
629 return URL_SEP
630
630
631 @classmethod
631 @classmethod
632 def get_by_repo_name(cls, repo_name):
632 def get_by_repo_name(cls, repo_name):
633 q = Session().query(cls).filter(cls.repo_name == repo_name)
633 q = Session().query(cls).filter(cls.repo_name == repo_name)
634 q = q.options(joinedload(Repository.fork))\
634 q = q.options(joinedload(Repository.fork))\
635 .options(joinedload(Repository.user))\
635 .options(joinedload(Repository.user))\
636 .options(joinedload(Repository.group))
636 .options(joinedload(Repository.group))
637 return q.scalar()
637 return q.scalar()
638
638
639 @classmethod
639 @classmethod
640 def get_by_full_path(cls, repo_full_path):
640 def get_by_full_path(cls, repo_full_path):
641 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
641 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
642 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
642 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
643
643
644 @classmethod
644 @classmethod
645 def get_repo_forks(cls, repo_id):
645 def get_repo_forks(cls, repo_id):
646 return cls.query().filter(Repository.fork_id == repo_id)
646 return cls.query().filter(Repository.fork_id == repo_id)
647
647
648 @classmethod
648 @classmethod
649 def base_path(cls):
649 def base_path(cls):
650 """
650 """
651 Returns base path when all repos are stored
651 Returns base path when all repos are stored
652
652
653 :param cls:
653 :param cls:
654 """
654 """
655 q = Session().query(RhodeCodeUi)\
655 q = Session().query(RhodeCodeUi)\
656 .filter(RhodeCodeUi.ui_key == cls.url_sep())
656 .filter(RhodeCodeUi.ui_key == cls.url_sep())
657 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
657 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
658 return q.one().ui_value
658 return q.one().ui_value
659
659
660 @property
660 @property
661 def forks(self):
661 def forks(self):
662 """
662 """
663 Return forks of this repo
663 Return forks of this repo
664 """
664 """
665 return Repository.get_repo_forks(self.repo_id)
665 return Repository.get_repo_forks(self.repo_id)
666
666
667 @property
667 @property
668 def parent(self):
668 def parent(self):
669 """
669 """
670 Returns fork parent
670 Returns fork parent
671 """
671 """
672 return self.fork
672 return self.fork
673
673
674 @property
674 @property
675 def just_name(self):
675 def just_name(self):
676 return self.repo_name.split(Repository.url_sep())[-1]
676 return self.repo_name.split(Repository.url_sep())[-1]
677
677
678 @property
678 @property
679 def groups_with_parents(self):
679 def groups_with_parents(self):
680 groups = []
680 groups = []
681 if self.group is None:
681 if self.group is None:
682 return groups
682 return groups
683
683
684 cur_gr = self.group
684 cur_gr = self.group
685 groups.insert(0, cur_gr)
685 groups.insert(0, cur_gr)
686 while 1:
686 while 1:
687 gr = getattr(cur_gr, 'parent_group', None)
687 gr = getattr(cur_gr, 'parent_group', None)
688 cur_gr = cur_gr.parent_group
688 cur_gr = cur_gr.parent_group
689 if gr is None:
689 if gr is None:
690 break
690 break
691 groups.insert(0, gr)
691 groups.insert(0, gr)
692
692
693 return groups
693 return groups
694
694
695 @property
695 @property
696 def groups_and_repo(self):
696 def groups_and_repo(self):
697 return self.groups_with_parents, self.just_name
697 return self.groups_with_parents, self.just_name
698
698
699 @LazyProperty
699 @LazyProperty
700 def repo_path(self):
700 def repo_path(self):
701 """
701 """
702 Returns base full path for that repository means where it actually
702 Returns base full path for that repository means where it actually
703 exists on a filesystem
703 exists on a filesystem
704 """
704 """
705 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
705 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
706 Repository.url_sep())
706 Repository.url_sep())
707 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
707 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
708 return q.one().ui_value
708 return q.one().ui_value
709
709
710 @property
710 @property
711 def repo_full_path(self):
711 def repo_full_path(self):
712 p = [self.repo_path]
712 p = [self.repo_path]
713 # we need to split the name by / since this is how we store the
713 # we need to split the name by / since this is how we store the
714 # names in the database, but that eventually needs to be converted
714 # names in the database, but that eventually needs to be converted
715 # into a valid system path
715 # into a valid system path
716 p += self.repo_name.split(Repository.url_sep())
716 p += self.repo_name.split(Repository.url_sep())
717 return os.path.join(*p)
717 return os.path.join(*p)
718
718
719 def get_new_name(self, repo_name):
719 def get_new_name(self, repo_name):
720 """
720 """
721 returns new full repository name based on assigned group and new new
721 returns new full repository name based on assigned group and new new
722
722
723 :param group_name:
723 :param group_name:
724 """
724 """
725 path_prefix = self.group.full_path_splitted if self.group else []
725 path_prefix = self.group.full_path_splitted if self.group else []
726 return Repository.url_sep().join(path_prefix + [repo_name])
726 return Repository.url_sep().join(path_prefix + [repo_name])
727
727
728 @property
728 @property
729 def _ui(self):
729 def _ui(self):
730 """
730 """
731 Creates an db based ui object for this repository
731 Creates an db based ui object for this repository
732 """
732 """
733 from mercurial import ui
733 from mercurial import ui
734 from mercurial import config
734 from mercurial import config
735 baseui = ui.ui()
735 baseui = ui.ui()
736
736
737 #clean the baseui object
737 #clean the baseui object
738 baseui._ocfg = config.config()
738 baseui._ocfg = config.config()
739 baseui._ucfg = config.config()
739 baseui._ucfg = config.config()
740 baseui._tcfg = config.config()
740 baseui._tcfg = config.config()
741
741
742 ret = RhodeCodeUi.query()\
742 ret = RhodeCodeUi.query()\
743 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
743 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
744
744
745 hg_ui = ret
745 hg_ui = ret
746 for ui_ in hg_ui:
746 for ui_ in hg_ui:
747 if ui_.ui_active:
747 if ui_.ui_active:
748 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
748 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
749 ui_.ui_key, ui_.ui_value)
749 ui_.ui_key, ui_.ui_value)
750 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
750 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
751
751
752 return baseui
752 return baseui
753
753
754 @classmethod
754 @classmethod
755 def inject_ui(cls, repo, extras={}):
755 def inject_ui(cls, repo, extras={}):
756 from rhodecode.lib.vcs.backends.hg import MercurialRepository
756 from rhodecode.lib.vcs.backends.hg import MercurialRepository
757 from rhodecode.lib.vcs.backends.git import GitRepository
757 from rhodecode.lib.vcs.backends.git import GitRepository
758 required = (MercurialRepository, GitRepository)
758 required = (MercurialRepository, GitRepository)
759 if not isinstance(repo, required):
759 if not isinstance(repo, required):
760 raise Exception('repo must be instance of %s' % required)
760 raise Exception('repo must be instance of %s' % required)
761
761
762 # inject ui extra param to log this action via push logger
762 # inject ui extra param to log this action via push logger
763 for k, v in extras.items():
763 for k, v in extras.items():
764 repo._repo.ui.setconfig('rhodecode_extras', k, v)
764 repo._repo.ui.setconfig('rhodecode_extras', k, v)
765
765
766 @classmethod
766 @classmethod
767 def is_valid(cls, repo_name):
767 def is_valid(cls, repo_name):
768 """
768 """
769 returns True if given repo name is a valid filesystem repository
769 returns True if given repo name is a valid filesystem repository
770
770
771 :param cls:
771 :param cls:
772 :param repo_name:
772 :param repo_name:
773 """
773 """
774 from rhodecode.lib.utils import is_valid_repo
774 from rhodecode.lib.utils import is_valid_repo
775
775
776 return is_valid_repo(repo_name, cls.base_path())
776 return is_valid_repo(repo_name, cls.base_path())
777
777
778 def get_api_data(self):
778 def get_api_data(self):
779 """
779 """
780 Common function for generating repo api data
780 Common function for generating repo api data
781
781
782 """
782 """
783 repo = self
783 repo = self
784 data = dict(
784 data = dict(
785 repo_id=repo.repo_id,
785 repo_id=repo.repo_id,
786 repo_name=repo.repo_name,
786 repo_name=repo.repo_name,
787 repo_type=repo.repo_type,
787 repo_type=repo.repo_type,
788 clone_uri=repo.clone_uri,
788 clone_uri=repo.clone_uri,
789 private=repo.private,
789 private=repo.private,
790 created_on=repo.created_on,
790 created_on=repo.created_on,
791 description=repo.description,
791 description=repo.description,
792 landing_rev=repo.landing_rev,
792 landing_rev=repo.landing_rev,
793 owner=repo.user.username,
793 owner=repo.user.username,
794 fork_of=repo.fork.repo_name if repo.fork else None
794 fork_of=repo.fork.repo_name if repo.fork else None
795 )
795 )
796
796
797 return data
797 return data
798
798
799 #==========================================================================
799 #==========================================================================
800 # SCM PROPERTIES
800 # SCM PROPERTIES
801 #==========================================================================
801 #==========================================================================
802
802
803 def get_changeset(self, rev=None):
803 def get_changeset(self, rev=None):
804 return get_changeset_safe(self.scm_instance, rev)
804 return get_changeset_safe(self.scm_instance, rev)
805
805
806 @property
806 @property
807 def tip(self):
807 def tip(self):
808 return self.get_changeset('tip')
808 return self.get_changeset('tip')
809
809
810 @property
810 @property
811 def author(self):
811 def author(self):
812 return self.tip.author
812 return self.tip.author
813
813
814 @property
814 @property
815 def last_change(self):
815 def last_change(self):
816 return self.scm_instance.last_change
816 return self.scm_instance.last_change
817
817
818 def get_comments(self, revisions=None):
818 def get_comments(self, revisions=None):
819 """
819 """
820 Returns comments for this repository grouped by revisions
820 Returns comments for this repository grouped by revisions
821
821
822 :param revisions: filter query by revisions only
822 :param revisions: filter query by revisions only
823 """
823 """
824 cmts = ChangesetComment.query()\
824 cmts = ChangesetComment.query()\
825 .filter(ChangesetComment.repo == self)
825 .filter(ChangesetComment.repo == self)
826 if revisions:
826 if revisions:
827 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
827 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
828 grouped = defaultdict(list)
828 grouped = defaultdict(list)
829 for cmt in cmts.all():
829 for cmt in cmts.all():
830 grouped[cmt.revision].append(cmt)
830 grouped[cmt.revision].append(cmt)
831 return grouped
831 return grouped
832
832
833 def statuses(self, revisions=None):
833 def statuses(self, revisions=None):
834 """
834 """
835 Returns statuses for this repository
835 Returns statuses for this repository
836
836
837 :param revisions: list of revisions to get statuses for
837 :param revisions: list of revisions to get statuses for
838 :type revisions: list
838 :type revisions: list
839 """
839 """
840
840
841 statuses = ChangesetStatus.query()\
841 statuses = ChangesetStatus.query()\
842 .filter(ChangesetStatus.repo == self)\
842 .filter(ChangesetStatus.repo == self)\
843 .filter(ChangesetStatus.version == 0)
843 .filter(ChangesetStatus.version == 0)
844 if revisions:
844 if revisions:
845 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
845 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
846 grouped = {}
846 grouped = {}
847 for stat in statuses.all():
847 for stat in statuses.all():
848 pr_id = pr_repo = None
848 pr_id = pr_repo = None
849 if stat.pull_request:
849 if stat.pull_request:
850 pr_id = stat.pull_request.pull_request_id
850 pr_id = stat.pull_request.pull_request_id
851 pr_repo = stat.pull_request.other_repo.repo_name
851 pr_repo = stat.pull_request.other_repo.repo_name
852 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
852 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
853 pr_id, pr_repo]
853 pr_id, pr_repo]
854 return grouped
854 return grouped
855
855
856 #==========================================================================
856 #==========================================================================
857 # SCM CACHE INSTANCE
857 # SCM CACHE INSTANCE
858 #==========================================================================
858 #==========================================================================
859
859
860 @property
860 @property
861 def invalidate(self):
861 def invalidate(self):
862 return CacheInvalidation.invalidate(self.repo_name)
862 return CacheInvalidation.invalidate(self.repo_name)
863
863
864 def set_invalidate(self):
864 def set_invalidate(self):
865 """
865 """
866 set a cache for invalidation for this instance
866 set a cache for invalidation for this instance
867 """
867 """
868 CacheInvalidation.set_invalidate(self.repo_name)
868 CacheInvalidation.set_invalidate(self.repo_name)
869
869
870 @LazyProperty
870 @LazyProperty
871 def scm_instance(self):
871 def scm_instance(self):
872 return self.__get_instance()
872 return self.__get_instance()
873
873
874 def scm_instance_cached(self, cache_map=None):
874 def scm_instance_cached(self, cache_map=None):
875 @cache_region('long_term')
875 @cache_region('long_term')
876 def _c(repo_name):
876 def _c(repo_name):
877 return self.__get_instance()
877 return self.__get_instance()
878 rn = self.repo_name
878 rn = self.repo_name
879 log.debug('Getting cached instance of repo')
879 log.debug('Getting cached instance of repo')
880
880
881 if cache_map:
881 if cache_map:
882 # get using prefilled cache_map
882 # get using prefilled cache_map
883 invalidate_repo = cache_map[self.repo_name]
883 invalidate_repo = cache_map[self.repo_name]
884 if invalidate_repo:
884 if invalidate_repo:
885 invalidate_repo = (None if invalidate_repo.cache_active
885 invalidate_repo = (None if invalidate_repo.cache_active
886 else invalidate_repo)
886 else invalidate_repo)
887 else:
887 else:
888 # get from invalidate
888 # get from invalidate
889 invalidate_repo = self.invalidate
889 invalidate_repo = self.invalidate
890
890
891 if invalidate_repo is not None:
891 if invalidate_repo is not None:
892 region_invalidate(_c, None, rn)
892 region_invalidate(_c, None, rn)
893 # update our cache
893 # update our cache
894 CacheInvalidation.set_valid(invalidate_repo.cache_key)
894 CacheInvalidation.set_valid(invalidate_repo.cache_key)
895 return _c(rn)
895 return _c(rn)
896
896
897 def __get_instance(self):
897 def __get_instance(self):
898 repo_full_path = self.repo_full_path
898 repo_full_path = self.repo_full_path
899 try:
899 try:
900 alias = get_scm(repo_full_path)[0]
900 alias = get_scm(repo_full_path)[0]
901 log.debug('Creating instance of %s repository' % alias)
901 log.debug('Creating instance of %s repository' % alias)
902 backend = get_backend(alias)
902 backend = get_backend(alias)
903 except VCSError:
903 except VCSError:
904 log.error(traceback.format_exc())
904 log.error(traceback.format_exc())
905 log.error('Perhaps this repository is in db and not in '
905 log.error('Perhaps this repository is in db and not in '
906 'filesystem run rescan repositories with '
906 'filesystem run rescan repositories with '
907 '"destroy old data " option from admin panel')
907 '"destroy old data " option from admin panel')
908 return
908 return
909
909
910 if alias == 'hg':
910 if alias == 'hg':
911
911
912 repo = backend(safe_str(repo_full_path), create=False,
912 repo = backend(safe_str(repo_full_path), create=False,
913 baseui=self._ui)
913 baseui=self._ui)
914 # skip hidden web repository
914 # skip hidden web repository
915 if repo._get_hidden():
915 if repo._get_hidden():
916 return
916 return
917 else:
917 else:
918 repo = backend(repo_full_path, create=False)
918 repo = backend(repo_full_path, create=False)
919
919
920 return repo
920 return repo
921
921
922
922
923 class RepoGroup(Base, BaseModel):
923 class RepoGroup(Base, BaseModel):
924 __tablename__ = 'groups'
924 __tablename__ = 'groups'
925 __table_args__ = (
925 __table_args__ = (
926 UniqueConstraint('group_name', 'group_parent_id'),
926 UniqueConstraint('group_name', 'group_parent_id'),
927 CheckConstraint('group_id != group_parent_id'),
927 CheckConstraint('group_id != group_parent_id'),
928 {'extend_existing': True, 'mysql_engine': 'InnoDB',
928 {'extend_existing': True, 'mysql_engine': 'InnoDB',
929 'mysql_charset': 'utf8'},
929 'mysql_charset': 'utf8'},
930 )
930 )
931 __mapper_args__ = {'order_by': 'group_name'}
931 __mapper_args__ = {'order_by': 'group_name'}
932
932
933 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
933 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
934 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
934 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
935 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
935 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
936 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
936 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
937
937
938 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
938 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
939 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
939 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
940
940
941 parent_group = relationship('RepoGroup', remote_side=group_id)
941 parent_group = relationship('RepoGroup', remote_side=group_id)
942
942
943 def __init__(self, group_name='', parent_group=None):
943 def __init__(self, group_name='', parent_group=None):
944 self.group_name = group_name
944 self.group_name = group_name
945 self.parent_group = parent_group
945 self.parent_group = parent_group
946
946
947 def __unicode__(self):
947 def __unicode__(self):
948 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
948 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
949 self.group_name)
949 self.group_name)
950
950
951 @classmethod
951 @classmethod
952 def groups_choices(cls):
952 def groups_choices(cls):
953 from webhelpers.html import literal as _literal
953 from webhelpers.html import literal as _literal
954 repo_groups = [('', '')]
954 repo_groups = [('', '')]
955 sep = ' &raquo; '
955 sep = ' &raquo; '
956 _name = lambda k: _literal(sep.join(k))
956 _name = lambda k: _literal(sep.join(k))
957
957
958 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
958 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
959 for x in cls.query().all()])
959 for x in cls.query().all()])
960
960
961 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
961 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
962 return repo_groups
962 return repo_groups
963
963
964 @classmethod
964 @classmethod
965 def url_sep(cls):
965 def url_sep(cls):
966 return URL_SEP
966 return URL_SEP
967
967
968 @classmethod
968 @classmethod
969 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
969 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
970 if case_insensitive:
970 if case_insensitive:
971 gr = cls.query()\
971 gr = cls.query()\
972 .filter(cls.group_name.ilike(group_name))
972 .filter(cls.group_name.ilike(group_name))
973 else:
973 else:
974 gr = cls.query()\
974 gr = cls.query()\
975 .filter(cls.group_name == group_name)
975 .filter(cls.group_name == group_name)
976 if cache:
976 if cache:
977 gr = gr.options(FromCache(
977 gr = gr.options(FromCache(
978 "sql_cache_short",
978 "sql_cache_short",
979 "get_group_%s" % _hash_key(group_name)
979 "get_group_%s" % _hash_key(group_name)
980 )
980 )
981 )
981 )
982 return gr.scalar()
982 return gr.scalar()
983
983
984 @property
984 @property
985 def parents(self):
985 def parents(self):
986 parents_recursion_limit = 5
986 parents_recursion_limit = 5
987 groups = []
987 groups = []
988 if self.parent_group is None:
988 if self.parent_group is None:
989 return groups
989 return groups
990 cur_gr = self.parent_group
990 cur_gr = self.parent_group
991 groups.insert(0, cur_gr)
991 groups.insert(0, cur_gr)
992 cnt = 0
992 cnt = 0
993 while 1:
993 while 1:
994 cnt += 1
994 cnt += 1
995 gr = getattr(cur_gr, 'parent_group', None)
995 gr = getattr(cur_gr, 'parent_group', None)
996 cur_gr = cur_gr.parent_group
996 cur_gr = cur_gr.parent_group
997 if gr is None:
997 if gr is None:
998 break
998 break
999 if cnt == parents_recursion_limit:
999 if cnt == parents_recursion_limit:
1000 # this will prevent accidental infinit loops
1000 # this will prevent accidental infinit loops
1001 log.error('group nested more than %s' %
1001 log.error('group nested more than %s' %
1002 parents_recursion_limit)
1002 parents_recursion_limit)
1003 break
1003 break
1004
1004
1005 groups.insert(0, gr)
1005 groups.insert(0, gr)
1006 return groups
1006 return groups
1007
1007
1008 @property
1008 @property
1009 def children(self):
1009 def children(self):
1010 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1010 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1011
1011
1012 @property
1012 @property
1013 def name(self):
1013 def name(self):
1014 return self.group_name.split(RepoGroup.url_sep())[-1]
1014 return self.group_name.split(RepoGroup.url_sep())[-1]
1015
1015
1016 @property
1016 @property
1017 def full_path(self):
1017 def full_path(self):
1018 return self.group_name
1018 return self.group_name
1019
1019
1020 @property
1020 @property
1021 def full_path_splitted(self):
1021 def full_path_splitted(self):
1022 return self.group_name.split(RepoGroup.url_sep())
1022 return self.group_name.split(RepoGroup.url_sep())
1023
1023
1024 @property
1024 @property
1025 def repositories(self):
1025 def repositories(self):
1026 return Repository.query()\
1026 return Repository.query()\
1027 .filter(Repository.group == self)\
1027 .filter(Repository.group == self)\
1028 .order_by(Repository.repo_name)
1028 .order_by(Repository.repo_name)
1029
1029
1030 @property
1030 @property
1031 def repositories_recursive_count(self):
1031 def repositories_recursive_count(self):
1032 cnt = self.repositories.count()
1032 cnt = self.repositories.count()
1033
1033
1034 def children_count(group):
1034 def children_count(group):
1035 cnt = 0
1035 cnt = 0
1036 for child in group.children:
1036 for child in group.children:
1037 cnt += child.repositories.count()
1037 cnt += child.repositories.count()
1038 cnt += children_count(child)
1038 cnt += children_count(child)
1039 return cnt
1039 return cnt
1040
1040
1041 return cnt + children_count(self)
1041 return cnt + children_count(self)
1042
1042
1043 def get_new_name(self, group_name):
1043 def get_new_name(self, group_name):
1044 """
1044 """
1045 returns new full group name based on parent and new name
1045 returns new full group name based on parent and new name
1046
1046
1047 :param group_name:
1047 :param group_name:
1048 """
1048 """
1049 path_prefix = (self.parent_group.full_path_splitted if
1049 path_prefix = (self.parent_group.full_path_splitted if
1050 self.parent_group else [])
1050 self.parent_group else [])
1051 return RepoGroup.url_sep().join(path_prefix + [group_name])
1051 return RepoGroup.url_sep().join(path_prefix + [group_name])
1052
1052
1053
1053
1054 class Permission(Base, BaseModel):
1054 class Permission(Base, BaseModel):
1055 __tablename__ = 'permissions'
1055 __tablename__ = 'permissions'
1056 __table_args__ = (
1056 __table_args__ = (
1057 Index('p_perm_name_idx', 'permission_name'),
1057 Index('p_perm_name_idx', 'permission_name'),
1058 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1058 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1059 'mysql_charset': 'utf8'},
1059 'mysql_charset': 'utf8'},
1060 )
1060 )
1061 PERMS = [
1062 ('repository.none', _('Repository no access')),
1063 ('repository.read', _('Repository read access')),
1064 ('repository.write', _('Repository write access')),
1065 ('repository.admin', _('Repository admin access')),
1066
1067 ('group.none', _('Repositories Group no access')),
1068 ('group.read', _('Repositories Group read access')),
1069 ('group.write', _('Repositories Group write access')),
1070 ('group.admin', _('Repositories Group admin access')),
1071
1072 ('hg.admin', _('RhodeCode Administrator')),
1073 ('hg.create.none', _('Repository creation disabled')),
1074 ('hg.create.repository', _('Repository creation enabled')),
1075 ('hg.register.none', _('Register disabled')),
1076 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1077 'with manual activation')),
1078
1079 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1080 'with auto activation')),
1081 ]
1082
1061 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1083 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1062 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1084 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1063 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1085 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1064
1086
1065 def __unicode__(self):
1087 def __unicode__(self):
1066 return u"<%s('%s:%s')>" % (
1088 return u"<%s('%s:%s')>" % (
1067 self.__class__.__name__, self.permission_id, self.permission_name
1089 self.__class__.__name__, self.permission_id, self.permission_name
1068 )
1090 )
1069
1091
1070 @classmethod
1092 @classmethod
1071 def get_by_key(cls, key):
1093 def get_by_key(cls, key):
1072 return cls.query().filter(cls.permission_name == key).scalar()
1094 return cls.query().filter(cls.permission_name == key).scalar()
1073
1095
1074 @classmethod
1096 @classmethod
1075 def get_default_perms(cls, default_user_id):
1097 def get_default_perms(cls, default_user_id):
1076 q = Session().query(UserRepoToPerm, Repository, cls)\
1098 q = Session().query(UserRepoToPerm, Repository, cls)\
1077 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1099 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1078 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1100 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1079 .filter(UserRepoToPerm.user_id == default_user_id)
1101 .filter(UserRepoToPerm.user_id == default_user_id)
1080
1102
1081 return q.all()
1103 return q.all()
1082
1104
1083 @classmethod
1105 @classmethod
1084 def get_default_group_perms(cls, default_user_id):
1106 def get_default_group_perms(cls, default_user_id):
1085 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1107 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1086 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1108 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1087 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1109 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1088 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1110 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1089
1111
1090 return q.all()
1112 return q.all()
1091
1113
1092
1114
1093 class UserRepoToPerm(Base, BaseModel):
1115 class UserRepoToPerm(Base, BaseModel):
1094 __tablename__ = 'repo_to_perm'
1116 __tablename__ = 'repo_to_perm'
1095 __table_args__ = (
1117 __table_args__ = (
1096 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1118 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1097 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1119 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1098 'mysql_charset': 'utf8'}
1120 'mysql_charset': 'utf8'}
1099 )
1121 )
1100 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1122 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1101 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1123 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1102 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1124 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1103 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1125 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1104
1126
1105 user = relationship('User')
1127 user = relationship('User')
1106 repository = relationship('Repository')
1128 repository = relationship('Repository')
1107 permission = relationship('Permission')
1129 permission = relationship('Permission')
1108
1130
1109 @classmethod
1131 @classmethod
1110 def create(cls, user, repository, permission):
1132 def create(cls, user, repository, permission):
1111 n = cls()
1133 n = cls()
1112 n.user = user
1134 n.user = user
1113 n.repository = repository
1135 n.repository = repository
1114 n.permission = permission
1136 n.permission = permission
1115 Session().add(n)
1137 Session().add(n)
1116 return n
1138 return n
1117
1139
1118 def __unicode__(self):
1140 def __unicode__(self):
1119 return u'<user:%s => %s >' % (self.user, self.repository)
1141 return u'<user:%s => %s >' % (self.user, self.repository)
1120
1142
1121
1143
1122 class UserToPerm(Base, BaseModel):
1144 class UserToPerm(Base, BaseModel):
1123 __tablename__ = 'user_to_perm'
1145 __tablename__ = 'user_to_perm'
1124 __table_args__ = (
1146 __table_args__ = (
1125 UniqueConstraint('user_id', 'permission_id'),
1147 UniqueConstraint('user_id', 'permission_id'),
1126 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1148 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1127 'mysql_charset': 'utf8'}
1149 'mysql_charset': 'utf8'}
1128 )
1150 )
1129 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1151 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1130 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1152 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1131 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1153 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1132
1154
1133 user = relationship('User')
1155 user = relationship('User')
1134 permission = relationship('Permission', lazy='joined')
1156 permission = relationship('Permission', lazy='joined')
1135
1157
1136
1158
1137 class UsersGroupRepoToPerm(Base, BaseModel):
1159 class UsersGroupRepoToPerm(Base, BaseModel):
1138 __tablename__ = 'users_group_repo_to_perm'
1160 __tablename__ = 'users_group_repo_to_perm'
1139 __table_args__ = (
1161 __table_args__ = (
1140 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1162 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1141 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1163 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1142 'mysql_charset': 'utf8'}
1164 'mysql_charset': 'utf8'}
1143 )
1165 )
1144 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1166 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1145 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1167 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1146 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1168 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1147 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1169 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1148
1170
1149 users_group = relationship('UsersGroup')
1171 users_group = relationship('UsersGroup')
1150 permission = relationship('Permission')
1172 permission = relationship('Permission')
1151 repository = relationship('Repository')
1173 repository = relationship('Repository')
1152
1174
1153 @classmethod
1175 @classmethod
1154 def create(cls, users_group, repository, permission):
1176 def create(cls, users_group, repository, permission):
1155 n = cls()
1177 n = cls()
1156 n.users_group = users_group
1178 n.users_group = users_group
1157 n.repository = repository
1179 n.repository = repository
1158 n.permission = permission
1180 n.permission = permission
1159 Session().add(n)
1181 Session().add(n)
1160 return n
1182 return n
1161
1183
1162 def __unicode__(self):
1184 def __unicode__(self):
1163 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1185 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1164
1186
1165
1187
1166 class UsersGroupToPerm(Base, BaseModel):
1188 class UsersGroupToPerm(Base, BaseModel):
1167 __tablename__ = 'users_group_to_perm'
1189 __tablename__ = 'users_group_to_perm'
1168 __table_args__ = (
1190 __table_args__ = (
1169 UniqueConstraint('users_group_id', 'permission_id',),
1191 UniqueConstraint('users_group_id', 'permission_id',),
1170 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1192 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1171 'mysql_charset': 'utf8'}
1193 'mysql_charset': 'utf8'}
1172 )
1194 )
1173 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1195 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1174 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1196 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1175 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1197 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1176
1198
1177 users_group = relationship('UsersGroup')
1199 users_group = relationship('UsersGroup')
1178 permission = relationship('Permission')
1200 permission = relationship('Permission')
1179
1201
1180
1202
1181 class UserRepoGroupToPerm(Base, BaseModel):
1203 class UserRepoGroupToPerm(Base, BaseModel):
1182 __tablename__ = 'user_repo_group_to_perm'
1204 __tablename__ = 'user_repo_group_to_perm'
1183 __table_args__ = (
1205 __table_args__ = (
1184 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1206 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1185 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1207 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1186 'mysql_charset': 'utf8'}
1208 'mysql_charset': 'utf8'}
1187 )
1209 )
1188
1210
1189 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1211 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1190 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1212 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1191 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1213 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1192 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1214 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1193
1215
1194 user = relationship('User')
1216 user = relationship('User')
1195 group = relationship('RepoGroup')
1217 group = relationship('RepoGroup')
1196 permission = relationship('Permission')
1218 permission = relationship('Permission')
1197
1219
1198
1220
1199 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1221 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1200 __tablename__ = 'users_group_repo_group_to_perm'
1222 __tablename__ = 'users_group_repo_group_to_perm'
1201 __table_args__ = (
1223 __table_args__ = (
1202 UniqueConstraint('users_group_id', 'group_id'),
1224 UniqueConstraint('users_group_id', 'group_id'),
1203 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1225 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1204 'mysql_charset': 'utf8'}
1226 'mysql_charset': 'utf8'}
1205 )
1227 )
1206
1228
1207 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)
1229 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)
1208 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1230 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1209 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1231 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1210 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1232 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1211
1233
1212 users_group = relationship('UsersGroup')
1234 users_group = relationship('UsersGroup')
1213 permission = relationship('Permission')
1235 permission = relationship('Permission')
1214 group = relationship('RepoGroup')
1236 group = relationship('RepoGroup')
1215
1237
1216
1238
1217 class Statistics(Base, BaseModel):
1239 class Statistics(Base, BaseModel):
1218 __tablename__ = 'statistics'
1240 __tablename__ = 'statistics'
1219 __table_args__ = (
1241 __table_args__ = (
1220 UniqueConstraint('repository_id'),
1242 UniqueConstraint('repository_id'),
1221 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1243 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1222 'mysql_charset': 'utf8'}
1244 'mysql_charset': 'utf8'}
1223 )
1245 )
1224 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1246 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1225 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1247 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1226 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1248 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1227 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1249 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1228 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1250 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1229 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1251 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1230
1252
1231 repository = relationship('Repository', single_parent=True)
1253 repository = relationship('Repository', single_parent=True)
1232
1254
1233
1255
1234 class UserFollowing(Base, BaseModel):
1256 class UserFollowing(Base, BaseModel):
1235 __tablename__ = 'user_followings'
1257 __tablename__ = 'user_followings'
1236 __table_args__ = (
1258 __table_args__ = (
1237 UniqueConstraint('user_id', 'follows_repository_id'),
1259 UniqueConstraint('user_id', 'follows_repository_id'),
1238 UniqueConstraint('user_id', 'follows_user_id'),
1260 UniqueConstraint('user_id', 'follows_user_id'),
1239 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1261 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1240 'mysql_charset': 'utf8'}
1262 'mysql_charset': 'utf8'}
1241 )
1263 )
1242
1264
1243 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1265 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1244 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1266 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1245 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1267 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1246 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1268 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1247 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1269 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1248
1270
1249 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1271 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1250
1272
1251 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1273 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1252 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1274 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1253
1275
1254 @classmethod
1276 @classmethod
1255 def get_repo_followers(cls, repo_id):
1277 def get_repo_followers(cls, repo_id):
1256 return cls.query().filter(cls.follows_repo_id == repo_id)
1278 return cls.query().filter(cls.follows_repo_id == repo_id)
1257
1279
1258
1280
1259 class CacheInvalidation(Base, BaseModel):
1281 class CacheInvalidation(Base, BaseModel):
1260 __tablename__ = 'cache_invalidation'
1282 __tablename__ = 'cache_invalidation'
1261 __table_args__ = (
1283 __table_args__ = (
1262 UniqueConstraint('cache_key'),
1284 UniqueConstraint('cache_key'),
1263 Index('key_idx', 'cache_key'),
1285 Index('key_idx', 'cache_key'),
1264 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1286 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1265 'mysql_charset': 'utf8'},
1287 'mysql_charset': 'utf8'},
1266 )
1288 )
1267 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1289 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1268 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1290 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1269 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1291 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1270 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1292 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1271
1293
1272 def __init__(self, cache_key, cache_args=''):
1294 def __init__(self, cache_key, cache_args=''):
1273 self.cache_key = cache_key
1295 self.cache_key = cache_key
1274 self.cache_args = cache_args
1296 self.cache_args = cache_args
1275 self.cache_active = False
1297 self.cache_active = False
1276
1298
1277 def __unicode__(self):
1299 def __unicode__(self):
1278 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1300 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1279 self.cache_id, self.cache_key)
1301 self.cache_id, self.cache_key)
1280
1302
1281 @classmethod
1303 @classmethod
1282 def clear_cache(cls):
1304 def clear_cache(cls):
1283 cls.query().delete()
1305 cls.query().delete()
1284
1306
1285 @classmethod
1307 @classmethod
1286 def _get_key(cls, key):
1308 def _get_key(cls, key):
1287 """
1309 """
1288 Wrapper for generating a key, together with a prefix
1310 Wrapper for generating a key, together with a prefix
1289
1311
1290 :param key:
1312 :param key:
1291 """
1313 """
1292 import rhodecode
1314 import rhodecode
1293 prefix = ''
1315 prefix = ''
1294 iid = rhodecode.CONFIG.get('instance_id')
1316 iid = rhodecode.CONFIG.get('instance_id')
1295 if iid:
1317 if iid:
1296 prefix = iid
1318 prefix = iid
1297 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1319 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1298
1320
1299 @classmethod
1321 @classmethod
1300 def get_by_key(cls, key):
1322 def get_by_key(cls, key):
1301 return cls.query().filter(cls.cache_key == key).scalar()
1323 return cls.query().filter(cls.cache_key == key).scalar()
1302
1324
1303 @classmethod
1325 @classmethod
1304 def _get_or_create_key(cls, key, prefix, org_key):
1326 def _get_or_create_key(cls, key, prefix, org_key):
1305 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1327 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1306 if not inv_obj:
1328 if not inv_obj:
1307 try:
1329 try:
1308 inv_obj = CacheInvalidation(key, org_key)
1330 inv_obj = CacheInvalidation(key, org_key)
1309 Session().add(inv_obj)
1331 Session().add(inv_obj)
1310 Session().commit()
1332 Session().commit()
1311 except Exception:
1333 except Exception:
1312 log.error(traceback.format_exc())
1334 log.error(traceback.format_exc())
1313 Session().rollback()
1335 Session().rollback()
1314 return inv_obj
1336 return inv_obj
1315
1337
1316 @classmethod
1338 @classmethod
1317 def invalidate(cls, key):
1339 def invalidate(cls, key):
1318 """
1340 """
1319 Returns Invalidation object if this given key should be invalidated
1341 Returns Invalidation object if this given key should be invalidated
1320 None otherwise. `cache_active = False` means that this cache
1342 None otherwise. `cache_active = False` means that this cache
1321 state is not valid and needs to be invalidated
1343 state is not valid and needs to be invalidated
1322
1344
1323 :param key:
1345 :param key:
1324 """
1346 """
1325
1347
1326 key, _prefix, _org_key = cls._get_key(key)
1348 key, _prefix, _org_key = cls._get_key(key)
1327 inv = cls._get_or_create_key(key, _prefix, _org_key)
1349 inv = cls._get_or_create_key(key, _prefix, _org_key)
1328
1350
1329 if inv and inv.cache_active is False:
1351 if inv and inv.cache_active is False:
1330 return inv
1352 return inv
1331
1353
1332 @classmethod
1354 @classmethod
1333 def set_invalidate(cls, key):
1355 def set_invalidate(cls, key):
1334 """
1356 """
1335 Mark this Cache key for invalidation
1357 Mark this Cache key for invalidation
1336
1358
1337 :param key:
1359 :param key:
1338 """
1360 """
1339
1361
1340 key, _prefix, _org_key = cls._get_key(key)
1362 key, _prefix, _org_key = cls._get_key(key)
1341 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1363 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1342 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1364 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1343 _org_key))
1365 _org_key))
1344 try:
1366 try:
1345 for inv_obj in inv_objs:
1367 for inv_obj in inv_objs:
1346 if inv_obj:
1368 if inv_obj:
1347 inv_obj.cache_active = False
1369 inv_obj.cache_active = False
1348
1370
1349 Session().add(inv_obj)
1371 Session().add(inv_obj)
1350 Session().commit()
1372 Session().commit()
1351 except Exception:
1373 except Exception:
1352 log.error(traceback.format_exc())
1374 log.error(traceback.format_exc())
1353 Session().rollback()
1375 Session().rollback()
1354
1376
1355 @classmethod
1377 @classmethod
1356 def set_valid(cls, key):
1378 def set_valid(cls, key):
1357 """
1379 """
1358 Mark this cache key as active and currently cached
1380 Mark this cache key as active and currently cached
1359
1381
1360 :param key:
1382 :param key:
1361 """
1383 """
1362 inv_obj = cls.get_by_key(key)
1384 inv_obj = cls.get_by_key(key)
1363 inv_obj.cache_active = True
1385 inv_obj.cache_active = True
1364 Session().add(inv_obj)
1386 Session().add(inv_obj)
1365 Session().commit()
1387 Session().commit()
1366
1388
1367 @classmethod
1389 @classmethod
1368 def get_cache_map(cls):
1390 def get_cache_map(cls):
1369
1391
1370 class cachemapdict(dict):
1392 class cachemapdict(dict):
1371
1393
1372 def __init__(self, *args, **kwargs):
1394 def __init__(self, *args, **kwargs):
1373 fixkey = kwargs.get('fixkey')
1395 fixkey = kwargs.get('fixkey')
1374 if fixkey:
1396 if fixkey:
1375 del kwargs['fixkey']
1397 del kwargs['fixkey']
1376 self.fixkey = fixkey
1398 self.fixkey = fixkey
1377 super(cachemapdict, self).__init__(*args, **kwargs)
1399 super(cachemapdict, self).__init__(*args, **kwargs)
1378
1400
1379 def __getattr__(self, name):
1401 def __getattr__(self, name):
1380 key = name
1402 key = name
1381 if self.fixkey:
1403 if self.fixkey:
1382 key, _prefix, _org_key = cls._get_key(key)
1404 key, _prefix, _org_key = cls._get_key(key)
1383 if key in self.__dict__:
1405 if key in self.__dict__:
1384 return self.__dict__[key]
1406 return self.__dict__[key]
1385 else:
1407 else:
1386 return self[key]
1408 return self[key]
1387
1409
1388 def __getitem__(self, key):
1410 def __getitem__(self, key):
1389 if self.fixkey:
1411 if self.fixkey:
1390 key, _prefix, _org_key = cls._get_key(key)
1412 key, _prefix, _org_key = cls._get_key(key)
1391 try:
1413 try:
1392 return super(cachemapdict, self).__getitem__(key)
1414 return super(cachemapdict, self).__getitem__(key)
1393 except KeyError:
1415 except KeyError:
1394 return
1416 return
1395
1417
1396 cache_map = cachemapdict(fixkey=True)
1418 cache_map = cachemapdict(fixkey=True)
1397 for obj in cls.query().all():
1419 for obj in cls.query().all():
1398 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1420 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1399 return cache_map
1421 return cache_map
1400
1422
1401
1423
1402 class ChangesetComment(Base, BaseModel):
1424 class ChangesetComment(Base, BaseModel):
1403 __tablename__ = 'changeset_comments'
1425 __tablename__ = 'changeset_comments'
1404 __table_args__ = (
1426 __table_args__ = (
1405 Index('cc_revision_idx', 'revision'),
1427 Index('cc_revision_idx', 'revision'),
1406 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1428 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1407 'mysql_charset': 'utf8'},
1429 'mysql_charset': 'utf8'},
1408 )
1430 )
1409 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1431 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1410 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1432 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1411 revision = Column('revision', String(40), nullable=True)
1433 revision = Column('revision', String(40), nullable=True)
1412 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1434 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1413 line_no = Column('line_no', Unicode(10), nullable=True)
1435 line_no = Column('line_no', Unicode(10), nullable=True)
1414 f_path = Column('f_path', Unicode(1000), nullable=True)
1436 f_path = Column('f_path', Unicode(1000), nullable=True)
1415 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1437 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1416 text = Column('text', Unicode(25000), nullable=False)
1438 text = Column('text', Unicode(25000), nullable=False)
1417 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1439 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1418
1440
1419 author = relationship('User', lazy='joined')
1441 author = relationship('User', lazy='joined')
1420 repo = relationship('Repository')
1442 repo = relationship('Repository')
1421 status_change = relationship('ChangesetStatus', uselist=False)
1443 status_change = relationship('ChangesetStatus', uselist=False)
1422 pull_request = relationship('PullRequest', lazy='joined')
1444 pull_request = relationship('PullRequest', lazy='joined')
1423
1445
1424 @classmethod
1446 @classmethod
1425 def get_users(cls, revision=None, pull_request_id=None):
1447 def get_users(cls, revision=None, pull_request_id=None):
1426 """
1448 """
1427 Returns user associated with this ChangesetComment. ie those
1449 Returns user associated with this ChangesetComment. ie those
1428 who actually commented
1450 who actually commented
1429
1451
1430 :param cls:
1452 :param cls:
1431 :param revision:
1453 :param revision:
1432 """
1454 """
1433 q = Session().query(User)\
1455 q = Session().query(User)\
1434 .join(ChangesetComment.author)
1456 .join(ChangesetComment.author)
1435 if revision:
1457 if revision:
1436 q = q.filter(cls.revision == revision)
1458 q = q.filter(cls.revision == revision)
1437 elif pull_request_id:
1459 elif pull_request_id:
1438 q = q.filter(cls.pull_request_id == pull_request_id)
1460 q = q.filter(cls.pull_request_id == pull_request_id)
1439 return q.all()
1461 return q.all()
1440
1462
1441
1463
1442 class ChangesetStatus(Base, BaseModel):
1464 class ChangesetStatus(Base, BaseModel):
1443 __tablename__ = 'changeset_statuses'
1465 __tablename__ = 'changeset_statuses'
1444 __table_args__ = (
1466 __table_args__ = (
1445 Index('cs_revision_idx', 'revision'),
1467 Index('cs_revision_idx', 'revision'),
1446 Index('cs_version_idx', 'version'),
1468 Index('cs_version_idx', 'version'),
1447 UniqueConstraint('repo_id', 'revision', 'version'),
1469 UniqueConstraint('repo_id', 'revision', 'version'),
1448 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1470 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1449 'mysql_charset': 'utf8'}
1471 'mysql_charset': 'utf8'}
1450 )
1472 )
1451 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1473 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1452 STATUS_APPROVED = 'approved'
1474 STATUS_APPROVED = 'approved'
1453 STATUS_REJECTED = 'rejected'
1475 STATUS_REJECTED = 'rejected'
1454 STATUS_UNDER_REVIEW = 'under_review'
1476 STATUS_UNDER_REVIEW = 'under_review'
1455
1477
1456 STATUSES = [
1478 STATUSES = [
1457 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1479 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1458 (STATUS_APPROVED, _("Approved")),
1480 (STATUS_APPROVED, _("Approved")),
1459 (STATUS_REJECTED, _("Rejected")),
1481 (STATUS_REJECTED, _("Rejected")),
1460 (STATUS_UNDER_REVIEW, _("Under Review")),
1482 (STATUS_UNDER_REVIEW, _("Under Review")),
1461 ]
1483 ]
1462
1484
1463 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1485 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1464 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1486 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1465 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1487 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1466 revision = Column('revision', String(40), nullable=False)
1488 revision = Column('revision', String(40), nullable=False)
1467 status = Column('status', String(128), nullable=False, default=DEFAULT)
1489 status = Column('status', String(128), nullable=False, default=DEFAULT)
1468 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1490 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1469 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1491 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1470 version = Column('version', Integer(), nullable=False, default=0)
1492 version = Column('version', Integer(), nullable=False, default=0)
1471 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1493 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1472
1494
1473 author = relationship('User', lazy='joined')
1495 author = relationship('User', lazy='joined')
1474 repo = relationship('Repository')
1496 repo = relationship('Repository')
1475 comment = relationship('ChangesetComment', lazy='joined')
1497 comment = relationship('ChangesetComment', lazy='joined')
1476 pull_request = relationship('PullRequest', lazy='joined')
1498 pull_request = relationship('PullRequest', lazy='joined')
1477
1499
1478 def __unicode__(self):
1500 def __unicode__(self):
1479 return u"<%s('%s:%s')>" % (
1501 return u"<%s('%s:%s')>" % (
1480 self.__class__.__name__,
1502 self.__class__.__name__,
1481 self.status, self.author
1503 self.status, self.author
1482 )
1504 )
1483
1505
1484 @classmethod
1506 @classmethod
1485 def get_status_lbl(cls, value):
1507 def get_status_lbl(cls, value):
1486 return dict(cls.STATUSES).get(value)
1508 return dict(cls.STATUSES).get(value)
1487
1509
1488 @property
1510 @property
1489 def status_lbl(self):
1511 def status_lbl(self):
1490 return ChangesetStatus.get_status_lbl(self.status)
1512 return ChangesetStatus.get_status_lbl(self.status)
1491
1513
1492
1514
1493 class PullRequest(Base, BaseModel):
1515 class PullRequest(Base, BaseModel):
1494 __tablename__ = 'pull_requests'
1516 __tablename__ = 'pull_requests'
1495 __table_args__ = (
1517 __table_args__ = (
1496 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1518 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1497 'mysql_charset': 'utf8'},
1519 'mysql_charset': 'utf8'},
1498 )
1520 )
1499
1521
1500 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1522 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1501 title = Column('title', Unicode(256), nullable=True)
1523 title = Column('title', Unicode(256), nullable=True)
1502 description = Column('description', Unicode(10240), nullable=True)
1524 description = Column('description', Unicode(10240), nullable=True)
1503 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1525 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1504 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1526 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1505 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1527 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1506 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1528 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1507 org_ref = Column('org_ref', Unicode(256), nullable=False)
1529 org_ref = Column('org_ref', Unicode(256), nullable=False)
1508 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1530 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1509 other_ref = Column('other_ref', Unicode(256), nullable=False)
1531 other_ref = Column('other_ref', Unicode(256), nullable=False)
1510
1532
1511 @hybrid_property
1533 @hybrid_property
1512 def revisions(self):
1534 def revisions(self):
1513 return self._revisions.split(':')
1535 return self._revisions.split(':')
1514
1536
1515 @revisions.setter
1537 @revisions.setter
1516 def revisions(self, val):
1538 def revisions(self, val):
1517 self._revisions = ':'.join(val)
1539 self._revisions = ':'.join(val)
1518
1540
1519 author = relationship('User', lazy='joined')
1541 author = relationship('User', lazy='joined')
1520 reviewers = relationship('PullRequestReviewers')
1542 reviewers = relationship('PullRequestReviewers')
1521 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1543 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1522 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1544 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1523
1545
1524 def __json__(self):
1546 def __json__(self):
1525 return dict(
1547 return dict(
1526 revisions=self.revisions
1548 revisions=self.revisions
1527 )
1549 )
1528
1550
1529
1551
1530 class PullRequestReviewers(Base, BaseModel):
1552 class PullRequestReviewers(Base, BaseModel):
1531 __tablename__ = 'pull_request_reviewers'
1553 __tablename__ = 'pull_request_reviewers'
1532 __table_args__ = (
1554 __table_args__ = (
1533 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1555 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1534 'mysql_charset': 'utf8'},
1556 'mysql_charset': 'utf8'},
1535 )
1557 )
1536
1558
1537 def __init__(self, user=None, pull_request=None):
1559 def __init__(self, user=None, pull_request=None):
1538 self.user = user
1560 self.user = user
1539 self.pull_request = pull_request
1561 self.pull_request = pull_request
1540
1562
1541 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1563 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1542 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1564 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1543 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1544
1566
1545 user = relationship('User')
1567 user = relationship('User')
1546 pull_request = relationship('PullRequest')
1568 pull_request = relationship('PullRequest')
1547
1569
1548
1570
1549 class Notification(Base, BaseModel):
1571 class Notification(Base, BaseModel):
1550 __tablename__ = 'notifications'
1572 __tablename__ = 'notifications'
1551 __table_args__ = (
1573 __table_args__ = (
1552 Index('notification_type_idx', 'type'),
1574 Index('notification_type_idx', 'type'),
1553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1575 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1554 'mysql_charset': 'utf8'},
1576 'mysql_charset': 'utf8'},
1555 )
1577 )
1556
1578
1557 TYPE_CHANGESET_COMMENT = u'cs_comment'
1579 TYPE_CHANGESET_COMMENT = u'cs_comment'
1558 TYPE_MESSAGE = u'message'
1580 TYPE_MESSAGE = u'message'
1559 TYPE_MENTION = u'mention'
1581 TYPE_MENTION = u'mention'
1560 TYPE_REGISTRATION = u'registration'
1582 TYPE_REGISTRATION = u'registration'
1561 TYPE_PULL_REQUEST = u'pull_request'
1583 TYPE_PULL_REQUEST = u'pull_request'
1562 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1584 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1563
1585
1564 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1586 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1565 subject = Column('subject', Unicode(512), nullable=True)
1587 subject = Column('subject', Unicode(512), nullable=True)
1566 body = Column('body', Unicode(50000), nullable=True)
1588 body = Column('body', Unicode(50000), nullable=True)
1567 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1589 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1568 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1590 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1569 type_ = Column('type', Unicode(256))
1591 type_ = Column('type', Unicode(256))
1570
1592
1571 created_by_user = relationship('User')
1593 created_by_user = relationship('User')
1572 notifications_to_users = relationship('UserNotification', lazy='joined',
1594 notifications_to_users = relationship('UserNotification', lazy='joined',
1573 cascade="all, delete, delete-orphan")
1595 cascade="all, delete, delete-orphan")
1574
1596
1575 @property
1597 @property
1576 def recipients(self):
1598 def recipients(self):
1577 return [x.user for x in UserNotification.query()\
1599 return [x.user for x in UserNotification.query()\
1578 .filter(UserNotification.notification == self)\
1600 .filter(UserNotification.notification == self)\
1579 .order_by(UserNotification.user).all()]
1601 .order_by(UserNotification.user).all()]
1580
1602
1581 @classmethod
1603 @classmethod
1582 def create(cls, created_by, subject, body, recipients, type_=None):
1604 def create(cls, created_by, subject, body, recipients, type_=None):
1583 if type_ is None:
1605 if type_ is None:
1584 type_ = Notification.TYPE_MESSAGE
1606 type_ = Notification.TYPE_MESSAGE
1585
1607
1586 notification = cls()
1608 notification = cls()
1587 notification.created_by_user = created_by
1609 notification.created_by_user = created_by
1588 notification.subject = subject
1610 notification.subject = subject
1589 notification.body = body
1611 notification.body = body
1590 notification.type_ = type_
1612 notification.type_ = type_
1591 notification.created_on = datetime.datetime.now()
1613 notification.created_on = datetime.datetime.now()
1592
1614
1593 for u in recipients:
1615 for u in recipients:
1594 assoc = UserNotification()
1616 assoc = UserNotification()
1595 assoc.notification = notification
1617 assoc.notification = notification
1596 u.notifications.append(assoc)
1618 u.notifications.append(assoc)
1597 Session().add(notification)
1619 Session().add(notification)
1598 return notification
1620 return notification
1599
1621
1600 @property
1622 @property
1601 def description(self):
1623 def description(self):
1602 from rhodecode.model.notification import NotificationModel
1624 from rhodecode.model.notification import NotificationModel
1603 return NotificationModel().make_description(self)
1625 return NotificationModel().make_description(self)
1604
1626
1605
1627
1606 class UserNotification(Base, BaseModel):
1628 class UserNotification(Base, BaseModel):
1607 __tablename__ = 'user_to_notification'
1629 __tablename__ = 'user_to_notification'
1608 __table_args__ = (
1630 __table_args__ = (
1609 UniqueConstraint('user_id', 'notification_id'),
1631 UniqueConstraint('user_id', 'notification_id'),
1610 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1632 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1611 'mysql_charset': 'utf8'}
1633 'mysql_charset': 'utf8'}
1612 )
1634 )
1613 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1635 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1614 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1636 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1615 read = Column('read', Boolean, default=False)
1637 read = Column('read', Boolean, default=False)
1616 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1638 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1617
1639
1618 user = relationship('User', lazy="joined")
1640 user = relationship('User', lazy="joined")
1619 notification = relationship('Notification', lazy="joined",
1641 notification = relationship('Notification', lazy="joined",
1620 order_by=lambda: Notification.created_on.desc(),)
1642 order_by=lambda: Notification.created_on.desc(),)
1621
1643
1622 def mark_as_read(self):
1644 def mark_as_read(self):
1623 self.read = True
1645 self.read = True
1624 Session().add(self)
1646 Session().add(self)
1625
1647
1626
1648
1627 class DbMigrateVersion(Base, BaseModel):
1649 class DbMigrateVersion(Base, BaseModel):
1628 __tablename__ = 'db_migrate_version'
1650 __tablename__ = 'db_migrate_version'
1629 __table_args__ = (
1651 __table_args__ = (
1630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1652 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 'mysql_charset': 'utf8'},
1653 'mysql_charset': 'utf8'},
1632 )
1654 )
1633 repository_id = Column('repository_id', String(250), primary_key=True)
1655 repository_id = Column('repository_id', String(250), primary_key=True)
1634 repository_path = Column('repository_path', Text)
1656 repository_path = Column('repository_path', Text)
1635 version = Column('version', Integer)
1657 version = Column('version', Integer)
@@ -1,251 +1,251
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
5 ${_('Edit user')} ${c.user.username} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 ${h.link_to(_('Admin'),h.url('admin_home'))}
10 &raquo;
10 &raquo;
11 ${h.link_to(_('Users'),h.url('users'))}
11 ${h.link_to(_('Users'),h.url('users'))}
12 &raquo;
12 &raquo;
13 ${_('edit')} "${c.user.username}"
13 ${_('edit')} "${c.user.username}"
14 </%def>
14 </%def>
15
15
16 <%def name="page_nav()">
16 <%def name="page_nav()">
17 ${self.menu('admin')}
17 ${self.menu('admin')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box box-left">
21 <div class="box box-left">
22 <!-- box / title -->
22 <!-- box / title -->
23 <div class="title">
23 <div class="title">
24 ${self.breadcrumbs()}
24 ${self.breadcrumbs()}
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
27 ${h.form(url('update_user', id=c.user.user_id),method='put')}
28 <div class="form">
28 <div class="form">
29 <div class="field">
29 <div class="field">
30 <div class="gravatar_box">
30 <div class="gravatar_box">
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
32 <p>
32 <p>
33 %if c.use_gravatar:
33 %if c.use_gravatar:
34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
34 <strong>${_('Change your avatar at')} <a href="http://gravatar.com">gravatar.com</a></strong>
35 <br/>${_('Using')} ${c.user.email}
35 <br/>${_('Using')} ${c.user.email}
36 %else:
36 %else:
37 <br/>${c.user.email}
37 <br/>${c.user.email}
38 %endif
38 %endif
39 </div>
39 </div>
40 </div>
40 </div>
41 <div class="field">
41 <div class="field">
42 <div class="label">
42 <div class="label">
43 <label>${_('API key')}</label> ${c.user.api_key}
43 <label>${_('API key')}</label> ${c.user.api_key}
44 </div>
44 </div>
45 </div>
45 </div>
46
46
47 <div class="fields">
47 <div class="fields">
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="username">${_('Username')}:</label>
50 <label for="username">${_('Username')}:</label>
51 </div>
51 </div>
52 <div class="input">
52 <div class="input">
53 ${h.text('username',class_='medium')}
53 ${h.text('username',class_='medium')}
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="field">
57 <div class="field">
58 <div class="label">
58 <div class="label">
59 <label for="ldap_dn">${_('LDAP DN')}:</label>
59 <label for="ldap_dn">${_('LDAP DN')}:</label>
60 </div>
60 </div>
61 <div class="input">
61 <div class="input">
62 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
62 ${h.text('ldap_dn',class_='medium disabled',readonly="readonly")}
63 </div>
63 </div>
64 </div>
64 </div>
65
65
66 <div class="field">
66 <div class="field">
67 <div class="label">
67 <div class="label">
68 <label for="new_password">${_('New password')}:</label>
68 <label for="new_password">${_('New password')}:</label>
69 </div>
69 </div>
70 <div class="input">
70 <div class="input">
71 ${h.password('new_password',class_='medium',autocomplete="off")}
71 ${h.password('new_password',class_='medium',autocomplete="off")}
72 </div>
72 </div>
73 </div>
73 </div>
74
74
75 <div class="field">
75 <div class="field">
76 <div class="label">
76 <div class="label">
77 <label for="password_confirmation">${_('New password confirmation')}:</label>
77 <label for="password_confirmation">${_('New password confirmation')}:</label>
78 </div>
78 </div>
79 <div class="input">
79 <div class="input">
80 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
80 ${h.password('password_confirmation',class_="medium",autocomplete="off")}
81 </div>
81 </div>
82 </div>
82 </div>
83
83
84 <div class="field">
84 <div class="field">
85 <div class="label">
85 <div class="label">
86 <label for="name">${_('First Name')}:</label>
86 <label for="name">${_('First Name')}:</label>
87 </div>
87 </div>
88 <div class="input">
88 <div class="input">
89 ${h.text('name',class_='medium')}
89 ${h.text('name',class_='medium')}
90 </div>
90 </div>
91 </div>
91 </div>
92
92
93 <div class="field">
93 <div class="field">
94 <div class="label">
94 <div class="label">
95 <label for="lastname">${_('Last Name')}:</label>
95 <label for="lastname">${_('Last Name')}:</label>
96 </div>
96 </div>
97 <div class="input">
97 <div class="input">
98 ${h.text('lastname',class_='medium')}
98 ${h.text('lastname',class_='medium')}
99 </div>
99 </div>
100 </div>
100 </div>
101
101
102 <div class="field">
102 <div class="field">
103 <div class="label">
103 <div class="label">
104 <label for="email">${_('Email')}:</label>
104 <label for="email">${_('Email')}:</label>
105 </div>
105 </div>
106 <div class="input">
106 <div class="input">
107 ${h.text('email',class_='medium')}
107 ${h.text('email',class_='medium')}
108 </div>
108 </div>
109 </div>
109 </div>
110
110
111 <div class="field">
111 <div class="field">
112 <div class="label label-checkbox">
112 <div class="label label-checkbox">
113 <label for="active">${_('Active')}:</label>
113 <label for="active">${_('Active')}:</label>
114 </div>
114 </div>
115 <div class="checkboxes">
115 <div class="checkboxes">
116 ${h.checkbox('active',value=True)}
116 ${h.checkbox('active',value=True)}
117 </div>
117 </div>
118 </div>
118 </div>
119
119
120 <div class="field">
120 <div class="field">
121 <div class="label label-checkbox">
121 <div class="label label-checkbox">
122 <label for="admin">${_('Admin')}:</label>
122 <label for="admin">${_('Admin')}:</label>
123 </div>
123 </div>
124 <div class="checkboxes">
124 <div class="checkboxes">
125 ${h.checkbox('admin',value=True)}
125 ${h.checkbox('admin',value=True)}
126 </div>
126 </div>
127 </div>
127 </div>
128 <div class="buttons">
128 <div class="buttons">
129 ${h.submit('save',_('Save'),class_="ui-button")}
129 ${h.submit('save',_('Save'),class_="ui-button")}
130 ${h.reset('reset',_('Reset'),class_="ui-button")}
130 ${h.reset('reset',_('Reset'),class_="ui-button")}
131 </div>
131 </div>
132 </div>
132 </div>
133 </div>
133 </div>
134 ${h.end_form()}
134 ${h.end_form()}
135 </div>
135 </div>
136 <div class="box box-right">
136 <div class="box box-right">
137 <!-- box / title -->
137 <!-- box / title -->
138 <div class="title">
138 <div class="title">
139 <h5>${_('Permissions')}</h5>
139 <h5>${_('Permissions')}</h5>
140 </div>
140 </div>
141 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
141 ${h.form(url('user_perm', id=c.user.user_id),method='put')}
142 <div class="form">
142 <div class="form">
143 <!-- fields -->
143 <!-- fields -->
144 <div class="fields">
144 <div class="fields">
145 <div class="field">
145 <div class="field">
146 <div class="label label-checkbox">
146 <div class="label label-checkbox">
147 <label for="create_repo_perm">${_('Create repositories')}:</label>
147 <label for="create_repo_perm">${_('Create repositories')}:</label>
148 </div>
148 </div>
149 <div class="checkboxes">
149 <div class="checkboxes">
150 ${h.checkbox('create_repo_perm',value=True)}
150 ${h.checkbox('create_repo_perm',value=True)}
151 </div>
151 </div>
152 </div>
152 </div>
153 <div class="buttons">
153 <div class="buttons">
154 ${h.submit('save',_('Save'),class_="ui-button")}
154 ${h.submit('save',_('Save'),class_="ui-button")}
155 ${h.reset('reset',_('Reset'),class_="ui-button")}
155 ${h.reset('reset',_('Reset'),class_="ui-button")}
156 </div>
156 </div>
157 </div>
157 </div>
158 </div>
158 </div>
159 ${h.end_form()}
159 ${h.end_form()}
160
160
161 ## permissions overview
161 ## permissions overview
162 <div id="perms" class="table">
162 <div id="perms" class="table">
163 %for section in sorted(c.perm_user.permissions.keys()):
163 %for section in sorted(c.perm_user.permissions.keys()):
164 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
164 <div class="perms_section_head">${section.replace("_"," ").capitalize()}</div>
165
165
166 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
166 <div id='tbl_list_wrap_${section}' class="yui-skin-sam">
167 <table id="tbl_list_${section}">
167 <table id="tbl_list_${section}">
168 <thead>
168 <thead>
169 <tr>
169 <tr>
170 <th class="left">${_('Name')}</th>
170 <th class="left">${_('Name')}</th>
171 <th class="left">${_('Permission')}</th>
171 <th class="left">${_('Permission')}</th>
172 </thead>
172 </thead>
173 <tbody>
173 <tbody>
174 %for k in c.perm_user.permissions[section]:
174 %for k in c.perm_user.permissions[section]:
175 <%
175 <%
176 if section != 'global':
176 if section != 'global':
177 section_perm = c.perm_user.permissions[section].get(k)
177 section_perm = c.perm_user.permissions[section].get(k)
178 _perm = section_perm.split('.')[-1]
178 _perm = section_perm.split('.')[-1]
179 else:
179 else:
180 _perm = section_perm = None
180 _perm = section_perm = None
181 %>
181 %>
182 <tr>
182 <tr>
183 <td>
183 <td>
184 %if section == 'repositories':
184 %if section == 'repositories':
185 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
185 <a href="${h.url('summary_home',repo_name=k)}">${k}</a>
186 %elif section == 'repositories_groups':
186 %elif section == 'repositories_groups':
187 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
187 <a href="${h.url('repos_group_home',group_name=k)}">${k}</a>
188 %else:
188 %else:
189 ${k}
189 ${h.get_permission_name(k)}
190 %endif
190 %endif
191 </td>
191 </td>
192 <td>
192 <td>
193 %if section == 'global':
193 %if section == 'global':
194 ${h.bool2icon(True)}
194 ${h.bool2icon(k.split('.')[-1] != 'none')}
195 %else:
195 %else:
196 <span class="perm_tag ${_perm}">${section_perm}</span>
196 <span class="perm_tag ${_perm}">${section_perm}</span>
197 %endif
197 %endif
198 </td>
198 </td>
199 </tr>
199 </tr>
200 %endfor
200 %endfor
201 </tbody>
201 </tbody>
202 </table>
202 </table>
203 </div>
203 </div>
204 %endfor
204 %endfor
205 </div>
205 </div>
206 </div>
206 </div>
207 <div class="box box-right">
207 <div class="box box-left">
208 <!-- box / title -->
208 <!-- box / title -->
209 <div class="title">
209 <div class="title">
210 <h5>${_('Email addresses')}</h5>
210 <h5>${_('Email addresses')}</h5>
211 </div>
211 </div>
212
212
213 <div class="emails_wrap">
213 <div class="emails_wrap">
214 <table class="noborder">
214 <table class="noborder">
215 %for em in c.user_email_map:
215 %for em in c.user_email_map:
216 <tr>
216 <tr>
217 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
217 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(em.user.email,16)}"/> </div></td>
218 <td><div class="email">${em.email}</div></td>
218 <td><div class="email">${em.email}</div></td>
219 <td>
219 <td>
220 ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
220 ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')}
221 ${h.hidden('del_email',em.email_id)}
221 ${h.hidden('del_email',em.email_id)}
222 ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
222 ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id,
223 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
223 class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")}
224 ${h.end_form()}
224 ${h.end_form()}
225 </td>
225 </td>
226 </tr>
226 </tr>
227 %endfor
227 %endfor
228 </table>
228 </table>
229 </div>
229 </div>
230
230
231 ${h.form(url('user_emails', id=c.user.user_id),method='put')}
231 ${h.form(url('user_emails', id=c.user.user_id),method='put')}
232 <div class="form">
232 <div class="form">
233 <!-- fields -->
233 <!-- fields -->
234 <div class="fields">
234 <div class="fields">
235 <div class="field">
235 <div class="field">
236 <div class="label">
236 <div class="label">
237 <label for="email">${_('New email address')}:</label>
237 <label for="email">${_('New email address')}:</label>
238 </div>
238 </div>
239 <div class="input">
239 <div class="input">
240 ${h.text('new_email', class_='medium')}
240 ${h.text('new_email', class_='medium')}
241 </div>
241 </div>
242 </div>
242 </div>
243 <div class="buttons">
243 <div class="buttons">
244 ${h.submit('save',_('Add'),class_="ui-button")}
244 ${h.submit('save',_('Add'),class_="ui-button")}
245 ${h.reset('reset',_('Reset'),class_="ui-button")}
245 ${h.reset('reset',_('Reset'),class_="ui-button")}
246 </div>
246 </div>
247 </div>
247 </div>
248 </div>
248 </div>
249 ${h.end_form()}
249 ${h.end_form()}
250 </div>
250 </div>
251 </%def>
251 </%def>
General Comments 0
You need to be logged in to leave comments. Login now