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