##// END OF EJS Templates
white space cleanup
marcink -
r1963:9bbde542 beta
parent child Browse files
Show More
@@ -1,1173 +1,1173 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25
26 26 import os
27 27 import logging
28 28 import datetime
29 29 import traceback
30 30 from collections import defaultdict
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.ext.hybrid import hybrid_property
34 34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 35 from beaker.cache import cache_region, region_invalidate
36 36
37 37 from vcs import get_backend
38 38 from vcs.utils.helpers import get_scm
39 39 from vcs.exceptions import VCSError
40 40 from vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
43 43 from rhodecode.lib.exceptions import UsersGroupsAssignedException
44 44 from rhodecode.lib.compat import json
45 45 from rhodecode.lib.caching_query import FromCache
46 46
47 47 from rhodecode.model.meta import Base, Session
48 48
49 49 log = logging.getLogger(__name__)
50 50
51 51 #==============================================================================
52 52 # BASE CLASSES
53 53 #==============================================================================
54 54
55 55
56 56 class ModelSerializer(json.JSONEncoder):
57 57 """
58 58 Simple Serializer for JSON,
59 59
60 60 usage::
61 61
62 62 to make object customized for serialization implement a __json__
63 63 method that will return a dict for serialization into json
64 64
65 65 example::
66 66
67 67 class Task(object):
68 68
69 69 def __init__(self, name, value):
70 70 self.name = name
71 71 self.value = value
72 72
73 73 def __json__(self):
74 74 return dict(name=self.name,
75 75 value=self.value)
76 76
77 77 """
78 78
79 79 def default(self, obj):
80 80
81 81 if hasattr(obj, '__json__'):
82 82 return obj.__json__()
83 83 else:
84 84 return json.JSONEncoder.default(self, obj)
85 85
86 86
87 87 class BaseModel(object):
88 88 """
89 89 Base Model for all classess
90 90 """
91 91
92 92 @classmethod
93 93 def _get_keys(cls):
94 94 """return column names for this model """
95 95 return class_mapper(cls).c.keys()
96 96
97 97 def get_dict(self):
98 98 """
99 99 return dict with keys and values corresponding
100 100 to this model data """
101 101
102 102 d = {}
103 103 for k in self._get_keys():
104 104 d[k] = getattr(self, k)
105 105
106 106 # also use __json__() if present to get additional fields
107 107 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
108 108 d[k] = val
109 109 return d
110 110
111 111 def get_appstruct(self):
112 112 """return list with keys and values tupples corresponding
113 113 to this model data """
114 114
115 115 l = []
116 116 for k in self._get_keys():
117 117 l.append((k, getattr(self, k),))
118 118 return l
119 119
120 120 def populate_obj(self, populate_dict):
121 121 """populate model with data from given populate_dict"""
122 122
123 123 for k in self._get_keys():
124 124 if k in populate_dict:
125 125 setattr(self, k, populate_dict[k])
126 126
127 127 @classmethod
128 128 def query(cls):
129 129 return Session.query(cls)
130 130
131 131 @classmethod
132 132 def get(cls, id_):
133 133 if id_:
134 134 return cls.query().get(id_)
135 135
136 136 @classmethod
137 137 def getAll(cls):
138 138 return cls.query().all()
139 139
140 140 @classmethod
141 141 def delete(cls, id_):
142 142 obj = cls.query().get(id_)
143 143 Session.delete(obj)
144 144
145 145
146 146 class RhodeCodeSetting(Base, BaseModel):
147 147 __tablename__ = 'rhodecode_settings'
148 148 __table_args__ = (
149 149 UniqueConstraint('app_settings_name'),
150 150 {'extend_existing': True}
151 151 )
152 152 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
153 153 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
154 154 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155 155
156 156 def __init__(self, k='', v=''):
157 157 self.app_settings_name = k
158 158 self.app_settings_value = v
159 159
160 160 @validates('_app_settings_value')
161 161 def validate_settings_value(self, key, val):
162 162 assert type(val) == unicode
163 163 return val
164 164
165 165 @hybrid_property
166 166 def app_settings_value(self):
167 167 v = self._app_settings_value
168 168 if v == 'ldap_active':
169 169 v = str2bool(v)
170 170 return v
171 171
172 172 @app_settings_value.setter
173 173 def app_settings_value(self, val):
174 174 """
175 175 Setter that will always make sure we use unicode in app_settings_value
176 176
177 177 :param val:
178 178 """
179 179 self._app_settings_value = safe_unicode(val)
180 180
181 181 def __repr__(self):
182 182 return "<%s('%s:%s')>" % (
183 183 self.__class__.__name__,
184 184 self.app_settings_name, self.app_settings_value
185 185 )
186 186
187 187 @classmethod
188 188 def get_by_name(cls, ldap_key):
189 189 return cls.query()\
190 190 .filter(cls.app_settings_name == ldap_key).scalar()
191 191
192 192 @classmethod
193 193 def get_app_settings(cls, cache=False):
194 194
195 195 ret = cls.query()
196 196
197 197 if cache:
198 198 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
199 199
200 200 if not ret:
201 201 raise Exception('Could not get application settings !')
202 202 settings = {}
203 203 for each in ret:
204 204 settings['rhodecode_' + each.app_settings_name] = \
205 205 each.app_settings_value
206 206
207 207 return settings
208 208
209 209 @classmethod
210 210 def get_ldap_settings(cls, cache=False):
211 211 ret = cls.query()\
212 212 .filter(cls.app_settings_name.startswith('ldap_')).all()
213 213 fd = {}
214 214 for row in ret:
215 215 fd.update({row.app_settings_name:row.app_settings_value})
216 216
217 217 return fd
218 218
219 219
220 220 class RhodeCodeUi(Base, BaseModel):
221 221 __tablename__ = 'rhodecode_ui'
222 222 __table_args__ = (
223 UniqueConstraint('ui_key'),
223 UniqueConstraint('ui_key'),
224 224 {'extend_existing': True}
225 225 )
226 226
227 227 HOOK_UPDATE = 'changegroup.update'
228 228 HOOK_REPO_SIZE = 'changegroup.repo_size'
229 229 HOOK_PUSH = 'pretxnchangegroup.push_logger'
230 230 HOOK_PULL = 'preoutgoing.pull_logger'
231 231
232 232 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
233 233 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
234 234 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
235 235 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
236 236 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
237 237
238 238 @classmethod
239 239 def get_by_key(cls, key):
240 240 return cls.query().filter(cls.ui_key == key)
241 241
242 242 @classmethod
243 243 def get_builtin_hooks(cls):
244 244 q = cls.query()
245 245 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
246 246 cls.HOOK_REPO_SIZE,
247 247 cls.HOOK_PUSH, cls.HOOK_PULL]))
248 248 return q.all()
249 249
250 250 @classmethod
251 251 def get_custom_hooks(cls):
252 252 q = cls.query()
253 253 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
254 254 cls.HOOK_REPO_SIZE,
255 255 cls.HOOK_PUSH, cls.HOOK_PULL]))
256 256 q = q.filter(cls.ui_section == 'hooks')
257 257 return q.all()
258 258
259 259 @classmethod
260 260 def create_or_update_hook(cls, key, val):
261 261 new_ui = cls.get_by_key(key).scalar() or cls()
262 262 new_ui.ui_section = 'hooks'
263 263 new_ui.ui_active = True
264 264 new_ui.ui_key = key
265 265 new_ui.ui_value = val
266 266
267 267 Session.add(new_ui)
268 268
269 269
270 270 class User(Base, BaseModel):
271 271 __tablename__ = 'users'
272 272 __table_args__ = (
273 UniqueConstraint('username'), UniqueConstraint('email'),
273 UniqueConstraint('username'), UniqueConstraint('email'),
274 274 {'extend_existing': True}
275 275 )
276 276 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
277 277 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
278 278 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
279 279 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
280 280 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
281 281 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
282 282 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
283 283 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
284 284 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
285 285 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 286 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
287 287
288 288 user_log = relationship('UserLog', cascade='all')
289 289 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
290 290
291 291 repositories = relationship('Repository')
292 292 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
293 293 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
294 294
295 295 group_member = relationship('UsersGroupMember', cascade='all')
296 296
297 297 notifications = relationship('UserNotification',)
298 298
299 299 @hybrid_property
300 300 def email(self):
301 301 return self._email
302 302
303 303 @email.setter
304 304 def email(self, val):
305 305 self._email = val.lower() if val else None
306 306
307 307 @property
308 308 def full_name(self):
309 309 return '%s %s' % (self.name, self.lastname)
310 310
311 311 @property
312 312 def full_name_or_username(self):
313 313 return ('%s %s' % (self.name, self.lastname)
314 314 if (self.name and self.lastname) else self.username)
315 315
316 316 @property
317 317 def full_contact(self):
318 318 return '%s %s <%s>' % (self.name, self.lastname, self.email)
319 319
320 320 @property
321 321 def short_contact(self):
322 322 return '%s %s' % (self.name, self.lastname)
323 323
324 324 @property
325 325 def is_admin(self):
326 326 return self.admin
327 327
328 328 def __repr__(self):
329 329 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
330 330 self.user_id, self.username)
331 331
332 332 @classmethod
333 333 def get_by_username(cls, username, case_insensitive=False, cache=False):
334 334 if case_insensitive:
335 335 q = cls.query().filter(cls.username.ilike(username))
336 336 else:
337 337 q = cls.query().filter(cls.username == username)
338 338
339 339 if cache:
340 340 q = q.options(FromCache("sql_cache_short",
341 341 "get_user_%s" % username))
342 342 return q.scalar()
343 343
344 344 @classmethod
345 345 def get_by_api_key(cls, api_key, cache=False):
346 346 q = cls.query().filter(cls.api_key == api_key)
347 347
348 348 if cache:
349 349 q = q.options(FromCache("sql_cache_short",
350 350 "get_api_key_%s" % api_key))
351 351 return q.scalar()
352 352
353 353 @classmethod
354 354 def get_by_email(cls, email, case_insensitive=False, cache=False):
355 355 if case_insensitive:
356 356 q = cls.query().filter(cls.email.ilike(email))
357 357 else:
358 358 q = cls.query().filter(cls.email == email)
359 359
360 360 if cache:
361 361 q = q.options(FromCache("sql_cache_short",
362 362 "get_api_key_%s" % email))
363 363 return q.scalar()
364 364
365 365 def update_lastlogin(self):
366 366 """Update user lastlogin"""
367 367 self.last_login = datetime.datetime.now()
368 368 Session.add(self)
369 369 log.debug('updated user %s lastlogin', self.username)
370 370
371 371 def __json__(self):
372 372 return dict(
373 373 email=self.email,
374 374 full_name=self.full_name,
375 375 full_name_or_username=self.full_name_or_username,
376 376 short_contact=self.short_contact,
377 377 full_contact=self.full_contact
378 378 )
379 379
380 380
381 381 class UserLog(Base, BaseModel):
382 382 __tablename__ = 'user_logs'
383 383 __table_args__ = {'extend_existing': True}
384 384 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
385 385 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
386 386 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
387 387 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
388 388 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
389 389 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
390 390 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
391 391
392 392 @property
393 393 def action_as_day(self):
394 394 return datetime.date(*self.action_date.timetuple()[:3])
395 395
396 396 user = relationship('User')
397 397 repository = relationship('Repository',cascade='')
398 398
399 399
400 400 class UsersGroup(Base, BaseModel):
401 401 __tablename__ = 'users_groups'
402 402 __table_args__ = {'extend_existing': True}
403 403
404 404 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
405 405 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
406 406 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
407 407
408 408 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
409 409
410 410 def __repr__(self):
411 411 return '<userGroup(%s)>' % (self.users_group_name)
412 412
413 413 @classmethod
414 414 def get_by_group_name(cls, group_name, cache=False,
415 415 case_insensitive=False):
416 416 if case_insensitive:
417 417 q = cls.query().filter(cls.users_group_name.ilike(group_name))
418 418 else:
419 419 q = cls.query().filter(cls.users_group_name == group_name)
420 420 if cache:
421 421 q = q.options(FromCache("sql_cache_short",
422 422 "get_user_%s" % group_name))
423 423 return q.scalar()
424 424
425 425 @classmethod
426 426 def get(cls, users_group_id, cache=False):
427 427 users_group = cls.query()
428 428 if cache:
429 429 users_group = users_group.options(FromCache("sql_cache_short",
430 430 "get_users_group_%s" % users_group_id))
431 431 return users_group.get(users_group_id)
432 432
433 433
434 434 class UsersGroupMember(Base, BaseModel):
435 435 __tablename__ = 'users_groups_members'
436 436 __table_args__ = {'extend_existing': True}
437 437
438 438 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
439 439 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
440 440 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
441 441
442 442 user = relationship('User', lazy='joined')
443 443 users_group = relationship('UsersGroup')
444 444
445 445 def __init__(self, gr_id='', u_id=''):
446 446 self.users_group_id = gr_id
447 447 self.user_id = u_id
448 448
449 449 @staticmethod
450 450 def add_user_to_group(group, user):
451 451 ugm = UsersGroupMember()
452 452 ugm.users_group = group
453 453 ugm.user = user
454 454 Session.add(ugm)
455 455 Session.commit()
456 456 return ugm
457 457
458 458
459 459 class Repository(Base, BaseModel):
460 460 __tablename__ = 'repositories'
461 461 __table_args__ = (
462 UniqueConstraint('repo_name'),
462 UniqueConstraint('repo_name'),
463 463 {'extend_existing': True},
464 464 )
465 465
466 466 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
467 467 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
468 468 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
469 469 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
470 470 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
471 471 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
472 472 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
473 473 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
474 474 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
475 475 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
476 476
477 477 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
478 478 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
479 479
480 480 user = relationship('User')
481 481 fork = relationship('Repository', remote_side=repo_id)
482 482 group = relationship('RepoGroup')
483 483 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
484 484 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
485 485 stats = relationship('Statistics', cascade='all', uselist=False)
486 486
487 487 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
488 488
489 489 logs = relationship('UserLog')
490 490
491 491 def __repr__(self):
492 492 return "<%s('%s:%s')>" % (self.__class__.__name__,
493 493 self.repo_id, self.repo_name)
494 494
495 495 @classmethod
496 496 def url_sep(cls):
497 497 return '/'
498 498
499 499 @classmethod
500 500 def get_by_repo_name(cls, repo_name):
501 501 q = Session.query(cls).filter(cls.repo_name == repo_name)
502 502 q = q.options(joinedload(Repository.fork))\
503 503 .options(joinedload(Repository.user))\
504 504 .options(joinedload(Repository.group))
505 505 return q.scalar()
506 506
507 507 @classmethod
508 508 def get_repo_forks(cls, repo_id):
509 509 return cls.query().filter(Repository.fork_id == repo_id)
510 510
511 511 @classmethod
512 512 def base_path(cls):
513 513 """
514 514 Returns base path when all repos are stored
515 515
516 516 :param cls:
517 517 """
518 518 q = Session.query(RhodeCodeUi)\
519 519 .filter(RhodeCodeUi.ui_key == cls.url_sep())
520 520 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
521 521 return q.one().ui_value
522 522
523 523 @property
524 524 def just_name(self):
525 525 return self.repo_name.split(Repository.url_sep())[-1]
526 526
527 527 @property
528 528 def groups_with_parents(self):
529 529 groups = []
530 530 if self.group is None:
531 531 return groups
532 532
533 533 cur_gr = self.group
534 534 groups.insert(0, cur_gr)
535 535 while 1:
536 536 gr = getattr(cur_gr, 'parent_group', None)
537 537 cur_gr = cur_gr.parent_group
538 538 if gr is None:
539 539 break
540 540 groups.insert(0, gr)
541 541
542 542 return groups
543 543
544 544 @property
545 545 def groups_and_repo(self):
546 546 return self.groups_with_parents, self.just_name
547 547
548 548 @LazyProperty
549 549 def repo_path(self):
550 550 """
551 551 Returns base full path for that repository means where it actually
552 552 exists on a filesystem
553 553 """
554 554 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
555 555 Repository.url_sep())
556 556 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
557 557 return q.one().ui_value
558 558
559 559 @property
560 560 def repo_full_path(self):
561 561 p = [self.repo_path]
562 562 # we need to split the name by / since this is how we store the
563 563 # names in the database, but that eventually needs to be converted
564 564 # into a valid system path
565 565 p += self.repo_name.split(Repository.url_sep())
566 566 return os.path.join(*p)
567 567
568 568 def get_new_name(self, repo_name):
569 569 """
570 570 returns new full repository name based on assigned group and new new
571 571
572 572 :param group_name:
573 573 """
574 574 path_prefix = self.group.full_path_splitted if self.group else []
575 575 return Repository.url_sep().join(path_prefix + [repo_name])
576 576
577 577 @property
578 578 def _ui(self):
579 579 """
580 580 Creates an db based ui object for this repository
581 581 """
582 582 from mercurial import ui
583 583 from mercurial import config
584 584 baseui = ui.ui()
585 585
586 586 #clean the baseui object
587 587 baseui._ocfg = config.config()
588 588 baseui._ucfg = config.config()
589 589 baseui._tcfg = config.config()
590 590
591 591 ret = RhodeCodeUi.query()\
592 592 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
593 593
594 594 hg_ui = ret
595 595 for ui_ in hg_ui:
596 596 if ui_.ui_active:
597 597 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
598 598 ui_.ui_key, ui_.ui_value)
599 599 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
600 600
601 601 return baseui
602 602
603 603 @classmethod
604 604 def is_valid(cls, repo_name):
605 605 """
606 606 returns True if given repo name is a valid filesystem repository
607 607
608 608 :param cls:
609 609 :param repo_name:
610 610 """
611 611 from rhodecode.lib.utils import is_valid_repo
612 612
613 613 return is_valid_repo(repo_name, cls.base_path())
614 614
615 615 #==========================================================================
616 616 # SCM PROPERTIES
617 617 #==========================================================================
618 618
619 619 def get_changeset(self, rev):
620 620 return get_changeset_safe(self.scm_instance, rev)
621 621
622 622 @property
623 623 def tip(self):
624 624 return self.get_changeset('tip')
625 625
626 626 @property
627 627 def author(self):
628 628 return self.tip.author
629 629
630 630 @property
631 631 def last_change(self):
632 632 return self.scm_instance.last_change
633 633
634 634 def comments(self, revisions=None):
635 635 """
636 636 Returns comments for this repository grouped by revisions
637 637
638 638 :param revisions: filter query by revisions only
639 639 """
640 640 cmts = ChangesetComment.query()\
641 641 .filter(ChangesetComment.repo == self)
642 642 if revisions:
643 643 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
644 644 grouped = defaultdict(list)
645 645 for cmt in cmts.all():
646 646 grouped[cmt.revision].append(cmt)
647 647 return grouped
648 648
649 649 #==========================================================================
650 650 # SCM CACHE INSTANCE
651 651 #==========================================================================
652 652
653 653 @property
654 654 def invalidate(self):
655 655 return CacheInvalidation.invalidate(self.repo_name)
656 656
657 657 def set_invalidate(self):
658 658 """
659 659 set a cache for invalidation for this instance
660 660 """
661 661 CacheInvalidation.set_invalidate(self.repo_name)
662 662
663 663 @LazyProperty
664 664 def scm_instance(self):
665 665 return self.__get_instance()
666 666
667 667 @property
668 668 def scm_instance_cached(self):
669 669 @cache_region('long_term')
670 670 def _c(repo_name):
671 671 return self.__get_instance()
672 672 rn = self.repo_name
673 673 log.debug('Getting cached instance of repo')
674 674 inv = self.invalidate
675 675 if inv is not None:
676 676 region_invalidate(_c, None, rn)
677 677 # update our cache
678 678 CacheInvalidation.set_valid(inv.cache_key)
679 679 return _c(rn)
680 680
681 681 def __get_instance(self):
682 682 repo_full_path = self.repo_full_path
683 683 try:
684 684 alias = get_scm(repo_full_path)[0]
685 685 log.debug('Creating instance of %s repository', alias)
686 686 backend = get_backend(alias)
687 687 except VCSError:
688 688 log.error(traceback.format_exc())
689 689 log.error('Perhaps this repository is in db and not in '
690 690 'filesystem run rescan repositories with '
691 691 '"destroy old data " option from admin panel')
692 692 return
693 693
694 694 if alias == 'hg':
695 695 repo = backend(safe_str(repo_full_path), create=False,
696 696 baseui=self._ui)
697 697 # skip hidden web repository
698 698 if repo._get_hidden():
699 699 return
700 700 else:
701 701 repo = backend(repo_full_path, create=False)
702 702
703 703 return repo
704 704
705 705
706 706 class RepoGroup(Base, BaseModel):
707 707 __tablename__ = 'groups'
708 708 __table_args__ = (
709 709 UniqueConstraint('group_name', 'group_parent_id'),
710 CheckConstraint('group_id != group_parent_id'),
710 CheckConstraint('group_id != group_parent_id'),
711 711 {'extend_existing': True},
712 712 )
713 713 __mapper_args__ = {'order_by': 'group_name'}
714 714
715 715 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
716 716 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
717 717 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
718 718 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
719 719
720 720 parent_group = relationship('RepoGroup', remote_side=group_id)
721 721
722 722 def __init__(self, group_name='', parent_group=None):
723 723 self.group_name = group_name
724 724 self.parent_group = parent_group
725 725
726 726 def __repr__(self):
727 727 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
728 728 self.group_name)
729 729
730 730 @classmethod
731 731 def groups_choices(cls):
732 732 from webhelpers.html import literal as _literal
733 733 repo_groups = [('', '')]
734 734 sep = ' &raquo; '
735 735 _name = lambda k: _literal(sep.join(k))
736 736
737 737 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
738 738 for x in cls.query().all()])
739 739
740 740 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
741 741 return repo_groups
742 742
743 743 @classmethod
744 744 def url_sep(cls):
745 745 return '/'
746 746
747 747 @classmethod
748 748 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
749 749 if case_insensitive:
750 750 gr = cls.query()\
751 751 .filter(cls.group_name.ilike(group_name))
752 752 else:
753 753 gr = cls.query()\
754 754 .filter(cls.group_name == group_name)
755 755 if cache:
756 756 gr = gr.options(FromCache("sql_cache_short",
757 757 "get_group_%s" % group_name))
758 758 return gr.scalar()
759 759
760 760 @property
761 761 def parents(self):
762 762 parents_recursion_limit = 5
763 763 groups = []
764 764 if self.parent_group is None:
765 765 return groups
766 766 cur_gr = self.parent_group
767 767 groups.insert(0, cur_gr)
768 768 cnt = 0
769 769 while 1:
770 770 cnt += 1
771 771 gr = getattr(cur_gr, 'parent_group', None)
772 772 cur_gr = cur_gr.parent_group
773 773 if gr is None:
774 774 break
775 775 if cnt == parents_recursion_limit:
776 776 # this will prevent accidental infinit loops
777 777 log.error('group nested more than %s' %
778 778 parents_recursion_limit)
779 779 break
780 780
781 781 groups.insert(0, gr)
782 782 return groups
783 783
784 784 @property
785 785 def children(self):
786 786 return RepoGroup.query().filter(RepoGroup.parent_group == self)
787 787
788 788 @property
789 789 def name(self):
790 790 return self.group_name.split(RepoGroup.url_sep())[-1]
791 791
792 792 @property
793 793 def full_path(self):
794 794 return self.group_name
795 795
796 796 @property
797 797 def full_path_splitted(self):
798 798 return self.group_name.split(RepoGroup.url_sep())
799 799
800 800 @property
801 801 def repositories(self):
802 802 return Repository.query().filter(Repository.group == self)
803 803
804 804 @property
805 805 def repositories_recursive_count(self):
806 806 cnt = self.repositories.count()
807 807
808 808 def children_count(group):
809 809 cnt = 0
810 810 for child in group.children:
811 811 cnt += child.repositories.count()
812 812 cnt += children_count(child)
813 813 return cnt
814 814
815 815 return cnt + children_count(self)
816 816
817 817 def get_new_name(self, group_name):
818 818 """
819 819 returns new full group name based on parent and new name
820 820
821 821 :param group_name:
822 822 """
823 823 path_prefix = (self.parent_group.full_path_splitted if
824 824 self.parent_group else [])
825 825 return RepoGroup.url_sep().join(path_prefix + [group_name])
826 826
827 827
828 828 class Permission(Base, BaseModel):
829 829 __tablename__ = 'permissions'
830 830 __table_args__ = {'extend_existing': True}
831 831 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
832 832 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
833 833 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
834 834
835 835 def __repr__(self):
836 836 return "<%s('%s:%s')>" % (self.__class__.__name__,
837 837 self.permission_id, self.permission_name)
838 838
839 839 @classmethod
840 840 def get_by_key(cls, key):
841 841 return cls.query().filter(cls.permission_name == key).scalar()
842 842
843 843 @classmethod
844 844 def get_default_perms(cls, default_user_id):
845 845 q = Session.query(UserRepoToPerm, Repository, cls)\
846 846 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
847 847 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
848 848 .filter(UserRepoToPerm.user_id == default_user_id)
849 849
850 850 return q.all()
851 851
852 852
853 853 class UserRepoToPerm(Base, BaseModel):
854 854 __tablename__ = 'repo_to_perm'
855 855 __table_args__ = (
856 856 UniqueConstraint('user_id', 'repository_id'), {'extend_existing': True}
857 857 )
858 858 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
859 859 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
860 860 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
861 861 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
862 862
863 863 user = relationship('User')
864 864 permission = relationship('Permission')
865 865 repository = relationship('Repository')
866 866
867 867 @classmethod
868 868 def create(cls, user, repository, permission):
869 869 n = cls()
870 870 n.user = user
871 871 n.repository = repository
872 872 n.permission = permission
873 873 Session.add(n)
874 874 return n
875 875
876 876 def __repr__(self):
877 877 return '<user:%s => %s >' % (self.user, self.repository)
878 878
879 879
880 880 class UserToPerm(Base, BaseModel):
881 881 __tablename__ = 'user_to_perm'
882 882 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing': True})
883 883 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
884 884 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
885 885 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
886 886
887 887 user = relationship('User')
888 888 permission = relationship('Permission', lazy='joined')
889 889
890 890
891 891 class UsersGroupRepoToPerm(Base, BaseModel):
892 892 __tablename__ = 'users_group_repo_to_perm'
893 893 __table_args__ = (
894 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
894 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
895 895 {'extend_existing': True}
896 896 )
897 897 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
898 898 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
899 899 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
900 900 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
901 901
902 902 users_group = relationship('UsersGroup')
903 903 permission = relationship('Permission')
904 904 repository = relationship('Repository')
905 905
906 906 @classmethod
907 907 def create(cls, users_group, repository, permission):
908 908 n = cls()
909 909 n.users_group = users_group
910 910 n.repository = repository
911 911 n.permission = permission
912 912 Session.add(n)
913 913 return n
914 914
915 915 def __repr__(self):
916 916 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
917 917
918 918
919 919 class UsersGroupToPerm(Base, BaseModel):
920 920 __tablename__ = 'users_group_to_perm'
921 921 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
922 922 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
923 923 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
924 924
925 925 users_group = relationship('UsersGroup')
926 926 permission = relationship('Permission')
927 927
928 928
929 929 class UserRepoGroupToPerm(Base, BaseModel):
930 930 __tablename__ = 'user_repo_group_to_perm'
931 931 __table_args__ = (
932 UniqueConstraint('group_id', 'permission_id'),
932 UniqueConstraint('group_id', 'permission_id'),
933 933 {'extend_existing': True}
934 934 )
935 935
936 936 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
937 937 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
938 938 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
939 939 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
940 940
941 941 user = relationship('User')
942 942 permission = relationship('Permission')
943 943 group = relationship('RepoGroup')
944 944
945 945
946 946 class UsersGroupRepoGroupToPerm(Base, BaseModel):
947 947 __tablename__ = 'users_group_repo_group_to_perm'
948 948 __table_args__ = (
949 UniqueConstraint('group_id', 'permission_id'),
949 UniqueConstraint('group_id', 'permission_id'),
950 950 {'extend_existing': True}
951 951 )
952 952
953 953 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)
954 954 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
955 955 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
956 956 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
957 957
958 958 users_group = relationship('UsersGroup')
959 959 permission = relationship('Permission')
960 960 group = relationship('RepoGroup')
961 961
962 962
963 963 class Statistics(Base, BaseModel):
964 964 __tablename__ = 'statistics'
965 965 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing': True})
966 966 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
967 967 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
968 968 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
969 969 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
970 970 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
971 971 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
972 972
973 973 repository = relationship('Repository', single_parent=True)
974 974
975 975
976 976 class UserFollowing(Base, BaseModel):
977 977 __tablename__ = 'user_followings'
978 978 __table_args__ = (
979 979 UniqueConstraint('user_id', 'follows_repository_id'),
980 980 UniqueConstraint('user_id', 'follows_user_id'),
981 981 {'extend_existing': True}
982 982 )
983 983
984 984 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
985 985 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
986 986 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
987 987 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
988 988 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
989 989
990 990 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
991 991
992 992 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
993 993 follows_repository = relationship('Repository', order_by='Repository.repo_name')
994 994
995 995 @classmethod
996 996 def get_repo_followers(cls, repo_id):
997 997 return cls.query().filter(cls.follows_repo_id == repo_id)
998 998
999 999
1000 1000 class CacheInvalidation(Base, BaseModel):
1001 1001 __tablename__ = 'cache_invalidation'
1002 1002 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing': True})
1003 1003 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1004 1004 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1005 1005 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1006 1006 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1007 1007
1008 1008 def __init__(self, cache_key, cache_args=''):
1009 1009 self.cache_key = cache_key
1010 1010 self.cache_args = cache_args
1011 1011 self.cache_active = False
1012 1012
1013 1013 def __repr__(self):
1014 1014 return "<%s('%s:%s')>" % (self.__class__.__name__,
1015 1015 self.cache_id, self.cache_key)
1016 1016
1017 1017 @classmethod
1018 1018 def invalidate(cls, key):
1019 1019 """
1020 1020 Returns Invalidation object if this given key should be invalidated
1021 1021 None otherwise. `cache_active = False` means that this cache
1022 1022 state is not valid and needs to be invalidated
1023 1023
1024 1024 :param key:
1025 1025 """
1026 1026 return cls.query()\
1027 1027 .filter(CacheInvalidation.cache_key == key)\
1028 1028 .filter(CacheInvalidation.cache_active == False)\
1029 1029 .scalar()
1030 1030
1031 1031 @classmethod
1032 1032 def set_invalidate(cls, key):
1033 1033 """
1034 1034 Mark this Cache key for invalidation
1035 1035
1036 1036 :param key:
1037 1037 """
1038 1038
1039 1039 log.debug('marking %s for invalidation' % key)
1040 1040 inv_obj = Session.query(cls)\
1041 1041 .filter(cls.cache_key == key).scalar()
1042 1042 if inv_obj:
1043 1043 inv_obj.cache_active = False
1044 1044 else:
1045 1045 log.debug('cache key not found in invalidation db -> creating one')
1046 1046 inv_obj = CacheInvalidation(key)
1047 1047
1048 1048 try:
1049 1049 Session.add(inv_obj)
1050 1050 Session.commit()
1051 1051 except Exception:
1052 1052 log.error(traceback.format_exc())
1053 1053 Session.rollback()
1054 1054
1055 1055 @classmethod
1056 1056 def set_valid(cls, key):
1057 1057 """
1058 1058 Mark this cache key as active and currently cached
1059 1059
1060 1060 :param key:
1061 1061 """
1062 1062 inv_obj = CacheInvalidation.query()\
1063 1063 .filter(CacheInvalidation.cache_key == key).scalar()
1064 1064 inv_obj.cache_active = True
1065 1065 Session.add(inv_obj)
1066 1066 Session.commit()
1067 1067
1068 1068
1069 1069 class ChangesetComment(Base, BaseModel):
1070 1070 __tablename__ = 'changeset_comments'
1071 1071 __table_args__ = ({'extend_existing': True},)
1072 1072 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1073 1073 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1074 1074 revision = Column('revision', String(40), nullable=False)
1075 1075 line_no = Column('line_no', Unicode(10), nullable=True)
1076 1076 f_path = Column('f_path', Unicode(1000), nullable=True)
1077 1077 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1078 1078 text = Column('text', Unicode(25000), nullable=False)
1079 1079 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1080 1080
1081 1081 author = relationship('User', lazy='joined')
1082 1082 repo = relationship('Repository')
1083 1083
1084 1084 @classmethod
1085 1085 def get_users(cls, revision):
1086 1086 """
1087 1087 Returns user associated with this changesetComment. ie those
1088 1088 who actually commented
1089 1089
1090 1090 :param cls:
1091 1091 :param revision:
1092 1092 """
1093 1093 return Session.query(User)\
1094 1094 .filter(cls.revision == revision)\
1095 1095 .join(ChangesetComment.author).all()
1096 1096
1097 1097
1098 1098 class Notification(Base, BaseModel):
1099 1099 __tablename__ = 'notifications'
1100 1100 __table_args__ = ({'extend_existing': True},)
1101 1101
1102 1102 TYPE_CHANGESET_COMMENT = u'cs_comment'
1103 1103 TYPE_MESSAGE = u'message'
1104 1104 TYPE_MENTION = u'mention'
1105 1105 TYPE_REGISTRATION = u'registration'
1106 1106
1107 1107 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1108 1108 subject = Column('subject', Unicode(512), nullable=True)
1109 1109 body = Column('body', Unicode(50000), nullable=True)
1110 1110 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1111 1111 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1112 1112 type_ = Column('type', Unicode(256))
1113 1113
1114 1114 created_by_user = relationship('User')
1115 1115 notifications_to_users = relationship('UserNotification', lazy='joined',
1116 1116 cascade="all, delete, delete-orphan")
1117 1117
1118 1118 @property
1119 1119 def recipients(self):
1120 1120 return [x.user for x in UserNotification.query()\
1121 1121 .filter(UserNotification.notification == self).all()]
1122 1122
1123 1123 @classmethod
1124 1124 def create(cls, created_by, subject, body, recipients, type_=None):
1125 1125 if type_ is None:
1126 1126 type_ = Notification.TYPE_MESSAGE
1127 1127
1128 1128 notification = cls()
1129 1129 notification.created_by_user = created_by
1130 1130 notification.subject = subject
1131 1131 notification.body = body
1132 1132 notification.type_ = type_
1133 1133 notification.created_on = datetime.datetime.now()
1134 1134
1135 1135 for u in recipients:
1136 1136 assoc = UserNotification()
1137 1137 assoc.notification = notification
1138 1138 u.notifications.append(assoc)
1139 1139 Session.add(notification)
1140 1140 return notification
1141 1141
1142 1142 @property
1143 1143 def description(self):
1144 1144 from rhodecode.model.notification import NotificationModel
1145 1145 return NotificationModel().make_description(self)
1146 1146
1147 1147
1148 1148 class UserNotification(Base, BaseModel):
1149 1149 __tablename__ = 'user_to_notification'
1150 1150 __table_args__ = (
1151 1151 UniqueConstraint('user_id', 'notification_id'),
1152 1152 {'extend_existing': True}
1153 1153 )
1154 1154 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1155 1155 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1156 1156 read = Column('read', Boolean, default=False)
1157 1157 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1158 1158
1159 1159 user = relationship('User', lazy="joined")
1160 1160 notification = relationship('Notification', lazy="joined",
1161 1161 order_by=lambda: Notification.created_on.desc(),)
1162 1162
1163 1163 def mark_as_read(self):
1164 1164 self.read = True
1165 1165 Session.add(self)
1166 1166
1167 1167
1168 1168 class DbMigrateVersion(Base, BaseModel):
1169 1169 __tablename__ = 'db_migrate_version'
1170 1170 __table_args__ = {'extend_existing': True}
1171 1171 repository_id = Column('repository_id', String(250), primary_key=True)
1172 1172 repository_path = Column('repository_path', Text)
1173 1173 version = Column('version', Integer)
@@ -1,749 +1,749 b''
1 1 """ this is forms validation classes
2 2 http://formencode.org/module-formencode.validators.html
3 3 for list off all availible validators
4 4
5 5 we can create our own validators
6 6
7 7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 8 pre_validators [] These validators will be applied before the schema
9 9 chained_validators [] These validators will be applied after the schema
10 10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14 14
15 15
16 16 <name> = formencode.validators.<name of validator>
17 17 <name> must equal form name
18 18 list=[1,2,3,4,5]
19 19 for SELECT use formencode.All(OneOf(list), Int())
20 20
21 21 """
22 22 import os
23 23 import re
24 24 import logging
25 25 import traceback
26 26
27 27 import formencode
28 28 from formencode import All
29 29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 30 Email, Bool, StringBoolean, Set
31 31
32 32 from pylons.i18n.translation import _
33 33 from webhelpers.pylonslib.secure_form import authentication_token
34 34
35 35 from rhodecode.config.routing import ADMIN_PREFIX
36 36 from rhodecode.lib.utils import repo_name_slug
37 37 from rhodecode.lib.auth import authenticate, get_crypt_password
38 38 from rhodecode.lib.exceptions import LdapImportError
39 39 from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
40 40 from rhodecode import BACKENDS
41 41
42 42 log = logging.getLogger(__name__)
43 43
44 44
45 45 #this is needed to translate the messages using _() in validators
46 46 class State_obj(object):
47 47 _ = staticmethod(_)
48 48
49 49
50 50 #==============================================================================
51 51 # VALIDATORS
52 52 #==============================================================================
53 53 class ValidAuthToken(formencode.validators.FancyValidator):
54 54 messages = {'invalid_token': _('Token mismatch')}
55 55
56 56 def validate_python(self, value, state):
57 57
58 58 if value != authentication_token():
59 59 raise formencode.Invalid(
60 60 self.message('invalid_token',
61 61 state, search_number=value),
62 62 value,
63 63 state
64 64 )
65 65
66 66
67 67 def ValidUsername(edit, old_data):
68 68 class _ValidUsername(formencode.validators.FancyValidator):
69 69
70 70 def validate_python(self, value, state):
71 71 if value in ['default', 'new_user']:
72 72 raise formencode.Invalid(_('Invalid username'), value, state)
73 73 #check if user is unique
74 74 old_un = None
75 75 if edit:
76 76 old_un = User.get(old_data.get('user_id')).username
77 77
78 78 if old_un != value or not edit:
79 79 if User.get_by_username(value, case_insensitive=True):
80 80 raise formencode.Invalid(_('This username already '
81 81 'exists') , value, state)
82 82
83 83 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
84 84 raise formencode.Invalid(
85 85 _('Username may only contain alphanumeric characters '
86 86 'underscores, periods or dashes and must begin with '
87 'alphanumeric character'),
88 value,
87 'alphanumeric character'),
88 value,
89 89 state
90 90 )
91 91
92 92 return _ValidUsername
93 93
94 94
95 95 def ValidUsersGroup(edit, old_data):
96 96
97 97 class _ValidUsersGroup(formencode.validators.FancyValidator):
98 98
99 99 def validate_python(self, value, state):
100 100 if value in ['default']:
101 101 raise formencode.Invalid(_('Invalid group name'), value, state)
102 102 #check if group is unique
103 103 old_ugname = None
104 104 if edit:
105 105 old_ugname = UsersGroup.get(
106 106 old_data.get('users_group_id')).users_group_name
107 107
108 108 if old_ugname != value or not edit:
109 109 if UsersGroup.get_by_group_name(value, cache=False,
110 110 case_insensitive=True):
111 111 raise formencode.Invalid(_('This users group '
112 112 'already exists'), value,
113 113 state)
114 114
115 115 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
116 116 raise formencode.Invalid(
117 117 _('RepoGroup name may only contain alphanumeric characters '
118 118 'underscores, periods or dashes and must begin with '
119 'alphanumeric character'),
120 value,
119 'alphanumeric character'),
120 value,
121 121 state
122 122 )
123 123
124 124 return _ValidUsersGroup
125 125
126 126
127 127 def ValidReposGroup(edit, old_data):
128 128 class _ValidReposGroup(formencode.validators.FancyValidator):
129 129
130 130 def validate_python(self, value, state):
131 131 # TODO WRITE VALIDATIONS
132 132 group_name = value.get('group_name')
133 133 group_parent_id = value.get('group_parent_id')
134 134
135 135 # slugify repo group just in case :)
136 136 slug = repo_name_slug(group_name)
137 137
138 138 # check for parent of self
139 139 parent_of_self = lambda: (
140 140 old_data['group_id'] == int(group_parent_id)
141 141 if group_parent_id else False
142 142 )
143 143 if edit and parent_of_self():
144 144 e_dict = {
145 145 'group_parent_id': _('Cannot assign this group as parent')
146 146 }
147 147 raise formencode.Invalid('', value, state,
148 148 error_dict=e_dict)
149 149
150 150 old_gname = None
151 151 if edit:
152 152 old_gname = RepoGroup.get(old_data.get('group_id')).group_name
153 153
154 154 if old_gname != group_name or not edit:
155 155
156 156 # check group
157 157 gr = RepoGroup.query()\
158 158 .filter(RepoGroup.group_name == slug)\
159 159 .filter(RepoGroup.group_parent_id == group_parent_id)\
160 160 .scalar()
161 161
162 162 if gr:
163 163 e_dict = {
164 164 'group_name': _('This group already exists')
165 165 }
166 166 raise formencode.Invalid('', value, state,
167 167 error_dict=e_dict)
168 168
169 169 # check for same repo
170 170 repo = Repository.query()\
171 171 .filter(Repository.repo_name == slug)\
172 172 .scalar()
173 173
174 174 if repo:
175 175 e_dict = {
176 176 'group_name': _('Repository with this name already exists')
177 177 }
178 178 raise formencode.Invalid('', value, state,
179 179 error_dict=e_dict)
180 180
181 181 return _ValidReposGroup
182 182
183 183
184 184 class ValidPassword(formencode.validators.FancyValidator):
185 185
186 186 def to_python(self, value, state):
187 187
188 188 if not value:
189 189 return
190 190
191 191 if value.get('password'):
192 192 try:
193 193 value['password'] = get_crypt_password(value['password'])
194 194 except UnicodeEncodeError:
195 195 e_dict = {'password': _('Invalid characters in password')}
196 196 raise formencode.Invalid('', value, state, error_dict=e_dict)
197 197
198 198 if value.get('password_confirmation'):
199 199 try:
200 200 value['password_confirmation'] = \
201 201 get_crypt_password(value['password_confirmation'])
202 202 except UnicodeEncodeError:
203 203 e_dict = {
204 204 'password_confirmation': _('Invalid characters in password')
205 205 }
206 206 raise formencode.Invalid('', value, state, error_dict=e_dict)
207 207
208 208 if value.get('new_password'):
209 209 try:
210 210 value['new_password'] = \
211 211 get_crypt_password(value['new_password'])
212 212 except UnicodeEncodeError:
213 213 e_dict = {'new_password': _('Invalid characters in password')}
214 214 raise formencode.Invalid('', value, state, error_dict=e_dict)
215 215
216 216 return value
217 217
218 218
219 219 class ValidPasswordsMatch(formencode.validators.FancyValidator):
220 220
221 221 def validate_python(self, value, state):
222 222
223 223 pass_val = value.get('password') or value.get('new_password')
224 224 if pass_val != value['password_confirmation']:
225 225 e_dict = {'password_confirmation':
226 226 _('Passwords do not match')}
227 227 raise formencode.Invalid('', value, state, error_dict=e_dict)
228 228
229 229
230 230 class ValidAuth(formencode.validators.FancyValidator):
231 231 messages = {
232 232 'invalid_password':_('invalid password'),
233 233 'invalid_login':_('invalid user name'),
234 234 'disabled_account':_('Your account is disabled')
235 235 }
236 236
237 237 # error mapping
238 238 e_dict = {'username': messages['invalid_login'],
239 239 'password': messages['invalid_password']}
240 240 e_dict_disable = {'username': messages['disabled_account']}
241 241
242 242 def validate_python(self, value, state):
243 243 password = value['password']
244 244 username = value['username']
245 245 user = User.get_by_username(username)
246 246
247 247 if authenticate(username, password):
248 248 return value
249 249 else:
250 250 if user and user.active is False:
251 251 log.warning('user %s is disabled', username)
252 252 raise formencode.Invalid(
253 253 self.message('disabled_account',
254 254 state=State_obj),
255 255 value, state,
256 256 error_dict=self.e_dict_disable
257 257 )
258 258 else:
259 259 log.warning('user %s not authenticated', username)
260 260 raise formencode.Invalid(
261 261 self.message('invalid_password',
262 262 state=State_obj), value, state,
263 263 error_dict=self.e_dict
264 264 )
265 265
266 266
267 267 class ValidRepoUser(formencode.validators.FancyValidator):
268 268
269 269 def to_python(self, value, state):
270 270 try:
271 271 User.query().filter(User.active == True)\
272 272 .filter(User.username == value).one()
273 273 except Exception:
274 274 raise formencode.Invalid(_('This username is not valid'),
275 275 value, state)
276 276 return value
277 277
278 278
279 279 def ValidRepoName(edit, old_data):
280 280 class _ValidRepoName(formencode.validators.FancyValidator):
281 281 def to_python(self, value, state):
282 282
283 283 repo_name = value.get('repo_name')
284 284
285 285 slug = repo_name_slug(repo_name)
286 286 if slug in [ADMIN_PREFIX, '']:
287 287 e_dict = {'repo_name': _('This repository name is disallowed')}
288 288 raise formencode.Invalid('', value, state, error_dict=e_dict)
289 289
290 290 if value.get('repo_group'):
291 291 gr = RepoGroup.get(value.get('repo_group'))
292 292 group_path = gr.full_path
293 293 # value needs to be aware of group name in order to check
294 294 # db key This is an actual just the name to store in the
295 295 # database
296 296 repo_name_full = group_path + RepoGroup.url_sep() + repo_name
297 297
298 298 else:
299 299 group_path = ''
300 300 repo_name_full = repo_name
301 301
302 302 value['repo_name_full'] = repo_name_full
303 303 rename = old_data.get('repo_name') != repo_name_full
304 304 create = not edit
305 305 if rename or create:
306 306
307 307 if group_path != '':
308 308 if Repository.get_by_repo_name(repo_name_full):
309 309 e_dict = {
310 310 'repo_name': _('This repository already exists in '
311 311 'a group "%s"') % gr.group_name
312 312 }
313 313 raise formencode.Invalid('', value, state,
314 314 error_dict=e_dict)
315 315 elif RepoGroup.get_by_group_name(repo_name_full):
316 316 e_dict = {
317 317 'repo_name': _('There is a group with this name '
318 318 'already "%s"') % repo_name_full
319 319 }
320 320 raise formencode.Invalid('', value, state,
321 321 error_dict=e_dict)
322 322
323 323 elif Repository.get_by_repo_name(repo_name_full):
324 324 e_dict = {'repo_name': _('This repository '
325 325 'already exists')}
326 326 raise formencode.Invalid('', value, state,
327 327 error_dict=e_dict)
328 328
329 329 return value
330 330
331 331 return _ValidRepoName
332 332
333 333
334 334 def ValidForkName(*args, **kwargs):
335 335 return ValidRepoName(*args, **kwargs)
336 336
337 337
338 338 def SlugifyName():
339 339 class _SlugifyName(formencode.validators.FancyValidator):
340 340
341 341 def to_python(self, value, state):
342 342 return repo_name_slug(value)
343 343
344 344 return _SlugifyName
345 345
346 346
347 347 def ValidCloneUri():
348 348 from mercurial.httprepo import httprepository, httpsrepository
349 349 from rhodecode.lib.utils import make_ui
350 350
351 351 class _ValidCloneUri(formencode.validators.FancyValidator):
352 352
353 353 def to_python(self, value, state):
354 354 if not value:
355 355 pass
356 356 elif value.startswith('https'):
357 357 try:
358 358 httpsrepository(make_ui('db'), value).capabilities
359 359 except Exception:
360 360 log.error(traceback.format_exc())
361 361 raise formencode.Invalid(_('invalid clone url'), value,
362 362 state)
363 363 elif value.startswith('http'):
364 364 try:
365 365 httprepository(make_ui('db'), value).capabilities
366 366 except Exception:
367 367 log.error(traceback.format_exc())
368 368 raise formencode.Invalid(_('invalid clone url'), value,
369 369 state)
370 370 else:
371 371 raise formencode.Invalid(_('Invalid clone url, provide a '
372 372 'valid clone http\s url'), value,
373 373 state)
374 374 return value
375 375
376 376 return _ValidCloneUri
377 377
378 378
379 379 def ValidForkType(old_data):
380 380 class _ValidForkType(formencode.validators.FancyValidator):
381 381
382 382 def to_python(self, value, state):
383 383 if old_data['repo_type'] != value:
384 384 raise formencode.Invalid(_('Fork have to be the same '
385 385 'type as original'), value, state)
386 386
387 387 return value
388 388 return _ValidForkType
389 389
390 390
391 391 class ValidPerms(formencode.validators.FancyValidator):
392 392 messages = {'perm_new_member_name': _('This username or users group name'
393 393 ' is not valid')}
394 394
395 395 def to_python(self, value, state):
396 396 perms_update = []
397 397 perms_new = []
398 398 #build a list of permission to update and new permission to create
399 399 for k, v in value.items():
400 400 #means new added member to permissions
401 401 if k.startswith('perm_new_member'):
402 402 new_perm = value.get('perm_new_member', False)
403 403 new_member = value.get('perm_new_member_name', False)
404 404 new_type = value.get('perm_new_member_type')
405 405
406 406 if new_member and new_perm:
407 407 if (new_member, new_perm, new_type) not in perms_new:
408 408 perms_new.append((new_member, new_perm, new_type))
409 409 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
410 410 member = k[7:]
411 411 t = {'u': 'user',
412 412 'g': 'users_group'
413 413 }[k[0]]
414 414 if member == 'default':
415 415 if value['private']:
416 416 #set none for default when updating to private repo
417 417 v = 'repository.none'
418 418 perms_update.append((member, v, t))
419 419
420 420 value['perms_updates'] = perms_update
421 421 value['perms_new'] = perms_new
422 422
423 423 #update permissions
424 424 for k, v, t in perms_new:
425 425 try:
426 426 if t is 'user':
427 427 self.user_db = User.query()\
428 428 .filter(User.active == True)\
429 429 .filter(User.username == k).one()
430 430 if t is 'users_group':
431 431 self.user_db = UsersGroup.query()\
432 432 .filter(UsersGroup.users_group_active == True)\
433 433 .filter(UsersGroup.users_group_name == k).one()
434 434
435 435 except Exception:
436 436 msg = self.message('perm_new_member_name',
437 437 state=State_obj)
438 438 raise formencode.Invalid(
439 439 msg, value, state, error_dict={'perm_new_member_name': msg}
440 440 )
441 441 return value
442 442
443 443
444 444 class ValidSettings(formencode.validators.FancyValidator):
445 445
446 446 def to_python(self, value, state):
447 447 # settings form can't edit user
448 448 if 'user' in value:
449 449 del['value']['user']
450 450 return value
451 451
452 452
453 453 class ValidPath(formencode.validators.FancyValidator):
454 454 def to_python(self, value, state):
455 455
456 456 if not os.path.isdir(value):
457 457 msg = _('This is not a valid path')
458 458 raise formencode.Invalid(msg, value, state,
459 459 error_dict={'paths_root_path': msg})
460 460 return value
461 461
462 462
463 463 def UniqSystemEmail(old_data):
464 464 class _UniqSystemEmail(formencode.validators.FancyValidator):
465 465 def to_python(self, value, state):
466 466 value = value.lower()
467 467 if old_data.get('email', '').lower() != value:
468 468 user = User.get_by_email(value, case_insensitive=True)
469 469 if user:
470 470 raise formencode.Invalid(
471 471 _("This e-mail address is already taken"), value, state
472 472 )
473 473 return value
474 474
475 475 return _UniqSystemEmail
476 476
477 477
478 478 class ValidSystemEmail(formencode.validators.FancyValidator):
479 479 def to_python(self, value, state):
480 480 value = value.lower()
481 481 user = User.get_by_email(value, case_insensitive=True)
482 482 if user is None:
483 483 raise formencode.Invalid(
484 484 _("This e-mail address doesn't exist."), value, state
485 485 )
486 486
487 487 return value
488 488
489 489
490 490 class LdapLibValidator(formencode.validators.FancyValidator):
491 491
492 492 def to_python(self, value, state):
493 493
494 494 try:
495 495 import ldap
496 496 except ImportError:
497 497 raise LdapImportError
498 498 return value
499 499
500 500
501 501 class AttrLoginValidator(formencode.validators.FancyValidator):
502 502
503 503 def to_python(self, value, state):
504 504
505 505 if not value or not isinstance(value, (str, unicode)):
506 506 raise formencode.Invalid(
507 507 _("The LDAP Login attribute of the CN must be specified - "
508 508 "this is the name of the attribute that is equivalent "
509 509 "to 'username'"), value, state
510 510 )
511 511
512 512 return value
513 513
514 514
515 515 #==============================================================================
516 516 # FORMS
517 517 #==============================================================================
518 518 class LoginForm(formencode.Schema):
519 519 allow_extra_fields = True
520 520 filter_extra_fields = True
521 521 username = UnicodeString(
522 522 strip=True,
523 523 min=1,
524 524 not_empty=True,
525 525 messages={
526 526 'empty': _('Please enter a login'),
527 527 'tooShort': _('Enter a value %(min)i characters long or more')}
528 528 )
529 529
530 530 password = UnicodeString(
531 531 strip=True,
532 532 min=3,
533 533 not_empty=True,
534 534 messages={
535 535 'empty': _('Please enter a password'),
536 536 'tooShort': _('Enter %(min)i characters or more')}
537 537 )
538 538
539 539 remember = StringBoolean(if_missing=False)
540 540
541 541 chained_validators = [ValidAuth]
542 542
543 543
544 544 def UserForm(edit=False, old_data={}):
545 545 class _UserForm(formencode.Schema):
546 546 allow_extra_fields = True
547 547 filter_extra_fields = True
548 548 username = All(UnicodeString(strip=True, min=1, not_empty=True),
549 549 ValidUsername(edit, old_data))
550 550 if edit:
551 551 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
552 552 password_confirmation = All(UnicodeString(strip=True, min=6,
553 553 not_empty=False))
554 554 admin = StringBoolean(if_missing=False)
555 555 else:
556 556 password = All(UnicodeString(strip=True, min=6, not_empty=True))
557 557 password_confirmation = All(UnicodeString(strip=True, min=6,
558 558 not_empty=False))
559 559
560 560 active = StringBoolean(if_missing=False)
561 561 name = UnicodeString(strip=True, min=1, not_empty=False)
562 562 lastname = UnicodeString(strip=True, min=1, not_empty=False)
563 563 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
564 564
565 565 chained_validators = [ValidPasswordsMatch, ValidPassword]
566 566
567 567 return _UserForm
568 568
569 569
570 570 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
571 571 class _UsersGroupForm(formencode.Schema):
572 572 allow_extra_fields = True
573 573 filter_extra_fields = True
574 574
575 575 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
576 576 ValidUsersGroup(edit, old_data))
577 577
578 578 users_group_active = StringBoolean(if_missing=False)
579 579
580 580 if edit:
581 581 users_group_members = OneOf(available_members, hideList=False,
582 582 testValueList=True,
583 583 if_missing=None, not_empty=False)
584 584
585 585 return _UsersGroupForm
586 586
587 587
588 588 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
589 589 class _ReposGroupForm(formencode.Schema):
590 590 allow_extra_fields = True
591 591 filter_extra_fields = True
592 592
593 593 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
594 594 SlugifyName())
595 595 group_description = UnicodeString(strip=True, min=1,
596 596 not_empty=True)
597 597 group_parent_id = OneOf(available_groups, hideList=False,
598 598 testValueList=True,
599 599 if_missing=None, not_empty=False)
600 600
601 601 chained_validators = [ValidReposGroup(edit, old_data)]
602 602
603 603 return _ReposGroupForm
604 604
605 605
606 606 def RegisterForm(edit=False, old_data={}):
607 607 class _RegisterForm(formencode.Schema):
608 608 allow_extra_fields = True
609 609 filter_extra_fields = True
610 610 username = All(ValidUsername(edit, old_data),
611 611 UnicodeString(strip=True, min=1, not_empty=True))
612 612 password = All(UnicodeString(strip=True, min=6, not_empty=True))
613 613 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
614 614 active = StringBoolean(if_missing=False)
615 615 name = UnicodeString(strip=True, min=1, not_empty=False)
616 616 lastname = UnicodeString(strip=True, min=1, not_empty=False)
617 617 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
618 618
619 619 chained_validators = [ValidPasswordsMatch, ValidPassword]
620 620
621 621 return _RegisterForm
622 622
623 623
624 624 def PasswordResetForm():
625 625 class _PasswordResetForm(formencode.Schema):
626 626 allow_extra_fields = True
627 627 filter_extra_fields = True
628 628 email = All(ValidSystemEmail(), Email(not_empty=True))
629 629 return _PasswordResetForm
630 630
631 631
632 632 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
633 633 repo_groups=[]):
634 634 class _RepoForm(formencode.Schema):
635 635 allow_extra_fields = True
636 636 filter_extra_fields = False
637 637 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
638 638 SlugifyName())
639 639 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
640 640 ValidCloneUri()())
641 641 repo_group = OneOf(repo_groups, hideList=True)
642 642 repo_type = OneOf(supported_backends)
643 643 description = UnicodeString(strip=True, min=1, not_empty=True)
644 644 private = StringBoolean(if_missing=False)
645 645 enable_statistics = StringBoolean(if_missing=False)
646 646 enable_downloads = StringBoolean(if_missing=False)
647 647
648 648 if edit:
649 649 #this is repo owner
650 650 user = All(UnicodeString(not_empty=True), ValidRepoUser)
651 651
652 652 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
653 653 return _RepoForm
654 654
655 655
656 656 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
657 657 repo_groups=[]):
658 658 class _RepoForkForm(formencode.Schema):
659 659 allow_extra_fields = True
660 660 filter_extra_fields = False
661 661 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
662 662 SlugifyName())
663 663 repo_group = OneOf(repo_groups, hideList=True)
664 664 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
665 665 description = UnicodeString(strip=True, min=1, not_empty=True)
666 666 private = StringBoolean(if_missing=False)
667 667 copy_permissions = StringBoolean(if_missing=False)
668 668 update_after_clone = StringBoolean(if_missing=False)
669 669 fork_parent_id = UnicodeString()
670 670 chained_validators = [ValidForkName(edit, old_data)]
671 671
672 672 return _RepoForkForm
673 673
674 674
675 675 def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
676 676 repo_groups=[]):
677 677 class _RepoForm(formencode.Schema):
678 678 allow_extra_fields = True
679 679 filter_extra_fields = False
680 680 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
681 681 SlugifyName())
682 682 description = UnicodeString(strip=True, min=1, not_empty=True)
683 683 repo_group = OneOf(repo_groups, hideList=True)
684 684 private = StringBoolean(if_missing=False)
685 685
686 686 chained_validators = [ValidRepoName(edit, old_data), ValidPerms,
687 687 ValidSettings]
688 688 return _RepoForm
689 689
690 690
691 691 def ApplicationSettingsForm():
692 692 class _ApplicationSettingsForm(formencode.Schema):
693 693 allow_extra_fields = True
694 694 filter_extra_fields = False
695 695 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
696 696 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
697 697 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
698 698
699 699 return _ApplicationSettingsForm
700 700
701 701
702 702 def ApplicationUiSettingsForm():
703 703 class _ApplicationUiSettingsForm(formencode.Schema):
704 704 allow_extra_fields = True
705 705 filter_extra_fields = False
706 706 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
707 707 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
708 708 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
709 709 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
710 710 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
711 711 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
712 712
713 713 return _ApplicationUiSettingsForm
714 714
715 715
716 716 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
717 717 class _DefaultPermissionsForm(formencode.Schema):
718 718 allow_extra_fields = True
719 719 filter_extra_fields = True
720 720 overwrite_default = StringBoolean(if_missing=False)
721 721 anonymous = OneOf(['True', 'False'], if_missing=False)
722 722 default_perm = OneOf(perms_choices)
723 723 default_register = OneOf(register_choices)
724 724 default_create = OneOf(create_choices)
725 725
726 726 return _DefaultPermissionsForm
727 727
728 728
729 729 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
730 730 class _LdapSettingsForm(formencode.Schema):
731 731 allow_extra_fields = True
732 732 filter_extra_fields = True
733 733 pre_validators = [LdapLibValidator]
734 734 ldap_active = StringBoolean(if_missing=False)
735 735 ldap_host = UnicodeString(strip=True,)
736 736 ldap_port = Number(strip=True,)
737 737 ldap_tls_kind = OneOf(tls_kind_choices)
738 738 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
739 739 ldap_dn_user = UnicodeString(strip=True,)
740 740 ldap_dn_pass = UnicodeString(strip=True,)
741 741 ldap_base_dn = UnicodeString(strip=True,)
742 742 ldap_filter = UnicodeString(strip=True,)
743 743 ldap_search_scope = OneOf(search_scope_choices)
744 744 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
745 745 ldap_attr_firstname = UnicodeString(strip=True,)
746 746 ldap_attr_lastname = UnicodeString(strip=True,)
747 747 ldap_attr_email = UnicodeString(strip=True,)
748 748
749 749 return _LdapSettingsForm
@@ -1,192 +1,192 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${c.repo_name} ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
7 7 </%def>
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(u'Home',h.url('/'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23 <!-- box / title -->
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27 <div class="table">
28 28 <div class="diffblock">
29 29 <div class="code-header">
30 30 <div class="hash">
31 31 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
32 32 </div>
33 <div class="date">
33 <div class="date">
34 34 ${c.changeset.date}
35 35 </div>
36 36 <div class="diff-actions">
37 37 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
38 38 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
39 39 ${c.ignorews_url()}
40 40 ${c.context_url()}
41 41 </div>
42 42 <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
43 43 </div>
44 44 </div>
45 45 <div id="changeset_content">
46 46 <div class="container">
47 47 <div class="left">
48 48 <div class="author">
49 49 <div class="gravatar">
50 50 <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
51 51 </div>
52 52 <span>${h.person(c.changeset.author)}</span><br/>
53 53 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
54 54 </div>
55 55 <div class="message">${h.urlify_commit(h.wrap_paragraphs(c.changeset.message),c.repo_name)}</div>
56 56 </div>
57 57 <div class="right">
58 58 <div class="changes">
59 59 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
60 60 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
61 61 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
62 62 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
63 63 % else:
64 64 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
65 65 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
66 66 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
67 67 % endif
68 68 </div>
69 69
70 70 %if c.changeset.parents:
71 71 %for p_cs in reversed(c.changeset.parents):
72 72 <div class="parent">${_('Parent')}
73 73 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
74 74 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
75 75 </div>
76 76 %endfor
77 77 %else:
78 78 <div class="parent">${_('No parents')}</div>
79 79 %endif
80 80 <span class="logtags">
81 81 %if len(c.changeset.parents)>1:
82 82 <span class="merge">${_('merge')}</span>
83 83 %endif
84 84 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
85 85 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
86 86 %for tag in c.changeset.tags:
87 87 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
88 88 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
89 89 %endfor
90 90 </span>
91 91 </div>
92 92 </div>
93 93 <span>
94 94 ${_('%s files affected with %s additions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
95 95 </span>
96 96 <div class="cs_files">
97 97 %for change,filenode,diff,cs1,cs2,stat in c.changes:
98 98 <div class="cs_${change}">
99 99 <div class="node">
100 100 %if change != 'removed':
101 101 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path)+"_target")}
102 102 %else:
103 103 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
104 104 %endif
105 105 </div>
106 106 <div class="changes">${h.fancy_file_stats(stat)}</div>
107 107 </div>
108 108 %endfor
109 109 % if c.cut_off:
110 110 ${_('Changeset was too big and was cut off...')}
111 111 % endif
112 112 </div>
113 113 </div>
114 114
115 115 </div>
116 116
117 117 ## diff block
118 118 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
119 119 ${diff_block.diff_block(c.changes)}
120 120
121 121 ## template for inline comment form
122 122 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
123 123 ${comment.comment_inline_form(c.changeset)}
124 124
125 125 ${comment.comments(c.changeset)}
126 126
127 127 <script type="text/javascript">
128 128 var deleteComment = function(comment_id){
129 129
130 130 var url = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}".replace('__COMMENT_ID__',comment_id);
131 131 var postData = '_method=delete';
132 132 var success = function(o){
133 133 var n = YUD.get('comment-'+comment_id);
134 134 n.parentNode.removeChild(n);
135 135 }
136 136 ajaxPOST(url,postData,success);
137 137 }
138 138
139 139 YUE.onDOMReady(function(){
140 140
141 141 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
142 142 var show = 'none';
143 143 var target = e.currentTarget;
144 144 if(target.checked){
145 145 var show = ''
146 146 }
147 147 var boxid = YUD.getAttribute(target,'id_for');
148 148 var comments = YUQ('#{0} .inline-comments'.format(boxid));
149 149 for(c in comments){
150 150 YUD.setStyle(comments[c],'display',show);
151 151 }
152 152 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
153 153 for(c in btns){
154 154 YUD.setStyle(btns[c],'display',show);
155 155 }
156 156 })
157 157
158 158 YUE.on(YUQ('.line'),'click',function(e){
159 159 var tr = e.currentTarget;
160 160 injectInlineForm(tr);
161 161 });
162 162
163 163 // inject comments into they proper positions
164 164 var file_comments = YUQ('.inline-comment-placeholder');
165 165
166 166 for (f in file_comments){
167 167 var box = file_comments[f];
168 168 var inlines = box.children;
169 169 for(var i=0; i<inlines.length; i++){
170 170 try{
171 171
172 172 var inline = inlines[i];
173 173 var lineno = YUD.getAttribute(inlines[i],'line');
174 174 var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
175 175 var target_line = YUD.get(lineid);
176 176
177 177 var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
178 178 YUD.insertAfter(add,target_line.parentNode);
179 179
180 180 var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
181 181 YUD.insertAfter(comment,target_line.parentNode);
182 182 }catch(e){
183 183 console.log(e);
184 184 }
185 185 }
186 186 }
187 187 })
188 188
189 189 </script>
190 190
191 191 </div>
192 192 </%def>
General Comments 0
You need to be logged in to leave comments. Login now