##// END OF EJS Templates
White space cleanup
marcink -
r2150:a8c9c009 beta
parent child Browse files
Show More
@@ -1,148 +1,148 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.comment
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 comments model for RhodeCode
7 7
8 8 :created_on: Nov 11, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2011-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 logging
27 27 import traceback
28 28
29 29 from pylons.i18n.translation import _
30 30 from sqlalchemy.util.compat import defaultdict
31 31
32 32 from rhodecode.lib.utils2 import extract_mentioned_users
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.model import BaseModel
35 35 from rhodecode.model.db import ChangesetComment, User, Repository, Notification
36 36 from rhodecode.model.notification import NotificationModel
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 40
41 41 class ChangesetCommentsModel(BaseModel):
42 42
43 43 def __get_changeset_comment(self, changeset_comment):
44 44 return self._get_instance(ChangesetComment, changeset_comment)
45 45
46 46 def _extract_mentions(self, s):
47 47 user_objects = []
48 48 for username in extract_mentioned_users(s):
49 49 user_obj = User.get_by_username(username, case_insensitive=True)
50 50 if user_obj:
51 51 user_objects.append(user_obj)
52 52 return user_objects
53 53
54 54 def create(self, text, repo_id, user_id, revision, f_path=None,
55 55 line_no=None):
56 56 """
57 57 Creates new comment for changeset
58 58
59 59 :param text:
60 60 :param repo_id:
61 61 :param user_id:
62 62 :param revision:
63 63 :param f_path:
64 64 :param line_no:
65 65 """
66
66 67 if text:
67 68 repo = Repository.get(repo_id)
68 69 cs = repo.scm_instance.get_changeset(revision)
69 70 desc = cs.message
70 71 author_email = cs.author_email
71 72 comment = ChangesetComment()
72 73 comment.repo = repo
73 74 comment.user_id = user_id
74 75 comment.revision = revision
75 76 comment.text = text
76 77 comment.f_path = f_path
77 78 comment.line_no = line_no
78 79
79 80 self.sa.add(comment)
80 81 self.sa.flush()
81
82 82 # make notification
83 83 line = ''
84 84 if line_no:
85 85 line = _('on line %s') % line_no
86 86 subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
87 87 {'commit_desc': desc, 'line': line},
88 88 h.url('changeset_home', repo_name=repo.repo_name,
89 89 revision=revision,
90 90 anchor='comment-%s' % comment.comment_id,
91 91 qualified=True,
92 92 )
93 93 )
94 94 body = text
95 95
96 96 # get the current participants of this changeset
97 97 recipients = ChangesetComment.get_users(revision=revision)
98 98
99 99 # add changeset author if it's in rhodecode system
100 100 recipients += [User.get_by_email(author_email)]
101 101
102 102 NotificationModel().create(
103 103 created_by=user_id, subject=subj, body=body,
104 104 recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
105 105 )
106 106
107 107 mention_recipients = set(self._extract_mentions(body))\
108 108 .difference(recipients)
109 109 if mention_recipients:
110 110 subj = _('[Mention]') + ' ' + subj
111 111 NotificationModel().create(
112 112 created_by=user_id, subject=subj, body=body,
113 113 recipients=mention_recipients,
114 114 type_=Notification.TYPE_CHANGESET_COMMENT
115 115 )
116 116
117 117 return comment
118 118
119 119 def delete(self, comment):
120 120 """
121 121 Deletes given comment
122 122
123 123 :param comment_id:
124 124 """
125 125 comment = self.__get_changeset_comment(comment)
126 126 self.sa.delete(comment)
127 127
128 128 return comment
129 129
130 130 def get_comments(self, repo_id, revision):
131 131 return ChangesetComment.query()\
132 132 .filter(ChangesetComment.repo_id == repo_id)\
133 133 .filter(ChangesetComment.revision == revision)\
134 134 .filter(ChangesetComment.line_no == None)\
135 135 .filter(ChangesetComment.f_path == None).all()
136 136
137 137 def get_inline_comments(self, repo_id, revision):
138 138 comments = self.sa.query(ChangesetComment)\
139 139 .filter(ChangesetComment.repo_id == repo_id)\
140 140 .filter(ChangesetComment.revision == revision)\
141 141 .filter(ChangesetComment.line_no != None)\
142 142 .filter(ChangesetComment.f_path != None).all()
143 143
144 144 paths = defaultdict(lambda: defaultdict(list))
145 145
146 146 for co in comments:
147 147 paths[co.f_path][co.line_no].append(co)
148 148 return paths.items()
@@ -1,1280 +1,1280 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 rhodecode.lib.vcs import get_backend
38 38 from rhodecode.lib.vcs.utils.helpers import get_scm
39 39 from rhodecode.lib.vcs.exceptions import VCSError
40 40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41 41
42 42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
43 43 safe_unicode
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 import hashlib
49 49
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53 #==============================================================================
54 54 # BASE CLASSES
55 55 #==============================================================================
56 56
57 57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58 58
59 59
60 60 class ModelSerializer(json.JSONEncoder):
61 61 """
62 62 Simple Serializer for JSON,
63 63
64 64 usage::
65 65
66 66 to make object customized for serialization implement a __json__
67 67 method that will return a dict for serialization into json
68 68
69 69 example::
70 70
71 71 class Task(object):
72 72
73 73 def __init__(self, name, value):
74 74 self.name = name
75 75 self.value = value
76 76
77 77 def __json__(self):
78 78 return dict(name=self.name,
79 79 value=self.value)
80 80
81 81 """
82 82
83 83 def default(self, obj):
84 84
85 85 if hasattr(obj, '__json__'):
86 86 return obj.__json__()
87 87 else:
88 88 return json.JSONEncoder.default(self, obj)
89 89
90 90
91 91 class BaseModel(object):
92 92 """
93 93 Base Model for all classess
94 94 """
95 95
96 96 @classmethod
97 97 def _get_keys(cls):
98 98 """return column names for this model """
99 99 return class_mapper(cls).c.keys()
100 100
101 101 def get_dict(self):
102 102 """
103 103 return dict with keys and values corresponding
104 104 to this model data """
105 105
106 106 d = {}
107 107 for k in self._get_keys():
108 108 d[k] = getattr(self, k)
109 109
110 110 # also use __json__() if present to get additional fields
111 111 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
112 112 d[k] = val
113 113 return d
114 114
115 115 def get_appstruct(self):
116 116 """return list with keys and values tupples corresponding
117 117 to this model data """
118 118
119 119 l = []
120 120 for k in self._get_keys():
121 121 l.append((k, getattr(self, k),))
122 122 return l
123 123
124 124 def populate_obj(self, populate_dict):
125 125 """populate model with data from given populate_dict"""
126 126
127 127 for k in self._get_keys():
128 128 if k in populate_dict:
129 129 setattr(self, k, populate_dict[k])
130 130
131 131 @classmethod
132 132 def query(cls):
133 133 return Session.query(cls)
134 134
135 135 @classmethod
136 136 def get(cls, id_):
137 137 if id_:
138 138 return cls.query().get(id_)
139 139
140 140 @classmethod
141 141 def getAll(cls):
142 142 return cls.query().all()
143 143
144 144 @classmethod
145 145 def delete(cls, id_):
146 146 obj = cls.query().get(id_)
147 147 Session.delete(obj)
148 148
149 149
150 150 class RhodeCodeSetting(Base, BaseModel):
151 151 __tablename__ = 'rhodecode_settings'
152 152 __table_args__ = (
153 153 UniqueConstraint('app_settings_name'),
154 154 {'extend_existing': True, 'mysql_engine':'InnoDB',
155 155 'mysql_charset': 'utf8'}
156 156 )
157 157 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
158 158 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
159 159 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
160 160
161 161 def __init__(self, k='', v=''):
162 162 self.app_settings_name = k
163 163 self.app_settings_value = v
164 164
165 165 @validates('_app_settings_value')
166 166 def validate_settings_value(self, key, val):
167 167 assert type(val) == unicode
168 168 return val
169 169
170 170 @hybrid_property
171 171 def app_settings_value(self):
172 172 v = self._app_settings_value
173 173 if self.app_settings_name == 'ldap_active':
174 174 v = str2bool(v)
175 175 return v
176 176
177 177 @app_settings_value.setter
178 178 def app_settings_value(self, val):
179 179 """
180 180 Setter that will always make sure we use unicode in app_settings_value
181 181
182 182 :param val:
183 183 """
184 184 self._app_settings_value = safe_unicode(val)
185 185
186 186 def __repr__(self):
187 187 return "<%s('%s:%s')>" % (
188 188 self.__class__.__name__,
189 189 self.app_settings_name, self.app_settings_value
190 190 )
191 191
192 192 @classmethod
193 193 def get_by_name(cls, ldap_key):
194 194 return cls.query()\
195 195 .filter(cls.app_settings_name == ldap_key).scalar()
196 196
197 197 @classmethod
198 198 def get_app_settings(cls, cache=False):
199 199
200 200 ret = cls.query()
201 201
202 202 if cache:
203 203 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
204 204
205 205 if not ret:
206 206 raise Exception('Could not get application settings !')
207 207 settings = {}
208 208 for each in ret:
209 209 settings['rhodecode_' + each.app_settings_name] = \
210 210 each.app_settings_value
211 211
212 212 return settings
213 213
214 214 @classmethod
215 215 def get_ldap_settings(cls, cache=False):
216 216 ret = cls.query()\
217 217 .filter(cls.app_settings_name.startswith('ldap_')).all()
218 218 fd = {}
219 219 for row in ret:
220 220 fd.update({row.app_settings_name:row.app_settings_value})
221 221
222 222 return fd
223 223
224 224
225 225 class RhodeCodeUi(Base, BaseModel):
226 226 __tablename__ = 'rhodecode_ui'
227 227 __table_args__ = (
228 228 UniqueConstraint('ui_key'),
229 229 {'extend_existing': True, 'mysql_engine':'InnoDB',
230 230 'mysql_charset': 'utf8'}
231 231 )
232 232
233 233 HOOK_UPDATE = 'changegroup.update'
234 234 HOOK_REPO_SIZE = 'changegroup.repo_size'
235 235 HOOK_PUSH = 'pretxnchangegroup.push_logger'
236 236 HOOK_PULL = 'preoutgoing.pull_logger'
237 237
238 238 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
239 239 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
240 240 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
241 241 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
242 242 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
243 243
244 244 @classmethod
245 245 def get_by_key(cls, key):
246 246 return cls.query().filter(cls.ui_key == key)
247 247
248 248 @classmethod
249 249 def get_builtin_hooks(cls):
250 250 q = cls.query()
251 251 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
252 252 cls.HOOK_REPO_SIZE,
253 253 cls.HOOK_PUSH, cls.HOOK_PULL]))
254 254 return q.all()
255 255
256 256 @classmethod
257 257 def get_custom_hooks(cls):
258 258 q = cls.query()
259 259 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
260 260 cls.HOOK_REPO_SIZE,
261 261 cls.HOOK_PUSH, cls.HOOK_PULL]))
262 262 q = q.filter(cls.ui_section == 'hooks')
263 263 return q.all()
264 264
265 265 @classmethod
266 266 def create_or_update_hook(cls, key, val):
267 267 new_ui = cls.get_by_key(key).scalar() or cls()
268 268 new_ui.ui_section = 'hooks'
269 269 new_ui.ui_active = True
270 270 new_ui.ui_key = key
271 271 new_ui.ui_value = val
272 272
273 273 Session.add(new_ui)
274 274
275 275
276 276 class User(Base, BaseModel):
277 277 __tablename__ = 'users'
278 278 __table_args__ = (
279 279 UniqueConstraint('username'), UniqueConstraint('email'),
280 280 {'extend_existing': True, 'mysql_engine':'InnoDB',
281 281 'mysql_charset': 'utf8'}
282 282 )
283 283 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
284 284 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
285 285 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
286 286 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
287 287 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
288 288 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
289 289 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 290 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
291 291 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
292 292 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
293 293 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 294
295 295 user_log = relationship('UserLog', cascade='all')
296 296 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
297 297
298 298 repositories = relationship('Repository')
299 299 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
300 300 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
301 301 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
302
302
303 303 group_member = relationship('UsersGroupMember', cascade='all')
304 304
305 305 notifications = relationship('UserNotification',)
306 306
307 307 @hybrid_property
308 308 def email(self):
309 309 return self._email
310 310
311 311 @email.setter
312 312 def email(self, val):
313 313 self._email = val.lower() if val else None
314 314
315 315 @property
316 316 def full_name(self):
317 317 return '%s %s' % (self.name, self.lastname)
318 318
319 319 @property
320 320 def full_name_or_username(self):
321 321 return ('%s %s' % (self.name, self.lastname)
322 322 if (self.name and self.lastname) else self.username)
323 323
324 324 @property
325 325 def full_contact(self):
326 326 return '%s %s <%s>' % (self.name, self.lastname, self.email)
327 327
328 328 @property
329 329 def short_contact(self):
330 330 return '%s %s' % (self.name, self.lastname)
331 331
332 332 @property
333 333 def is_admin(self):
334 334 return self.admin
335 335
336 336 def __repr__(self):
337 337 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
338 338 self.user_id, self.username)
339 339
340 340 @classmethod
341 341 def get_by_username(cls, username, case_insensitive=False, cache=False):
342 342 if case_insensitive:
343 343 q = cls.query().filter(cls.username.ilike(username))
344 344 else:
345 345 q = cls.query().filter(cls.username == username)
346 346
347 347 if cache:
348 348 q = q.options(FromCache(
349 349 "sql_cache_short",
350 350 "get_user_%s" % _hash_key(username)
351 351 )
352 352 )
353 353 return q.scalar()
354 354
355 355 @classmethod
356 356 def get_by_api_key(cls, api_key, cache=False):
357 357 q = cls.query().filter(cls.api_key == api_key)
358 358
359 359 if cache:
360 360 q = q.options(FromCache("sql_cache_short",
361 361 "get_api_key_%s" % api_key))
362 362 return q.scalar()
363 363
364 364 @classmethod
365 365 def get_by_email(cls, email, case_insensitive=False, cache=False):
366 366 if case_insensitive:
367 367 q = cls.query().filter(cls.email.ilike(email))
368 368 else:
369 369 q = cls.query().filter(cls.email == email)
370 370
371 371 if cache:
372 372 q = q.options(FromCache("sql_cache_short",
373 373 "get_api_key_%s" % email))
374 374 return q.scalar()
375 375
376 376 def update_lastlogin(self):
377 377 """Update user lastlogin"""
378 378 self.last_login = datetime.datetime.now()
379 379 Session.add(self)
380 380 log.debug('updated user %s lastlogin' % self.username)
381 381
382 382 def __json__(self):
383 383 return dict(
384 384 email=self.email,
385 385 full_name=self.full_name,
386 386 full_name_or_username=self.full_name_or_username,
387 387 short_contact=self.short_contact,
388 388 full_contact=self.full_contact
389 389 )
390 390
391 391
392 392 class UserLog(Base, BaseModel):
393 393 __tablename__ = 'user_logs'
394 394 __table_args__ = (
395 395 {'extend_existing': True, 'mysql_engine':'InnoDB',
396 396 'mysql_charset': 'utf8'},
397 397 )
398 398 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
399 399 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
400 400 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
401 401 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
402 402 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
403 403 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
404 404 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
405 405
406 406 @property
407 407 def action_as_day(self):
408 408 return datetime.date(*self.action_date.timetuple()[:3])
409 409
410 410 user = relationship('User')
411 411 repository = relationship('Repository', cascade='')
412 412
413 413
414 414 class UsersGroup(Base, BaseModel):
415 415 __tablename__ = 'users_groups'
416 416 __table_args__ = (
417 417 {'extend_existing': True, 'mysql_engine':'InnoDB',
418 418 'mysql_charset': 'utf8'},
419 419 )
420 420
421 421 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
422 422 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
423 423 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
424 424
425 425 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
426 426 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
427 427 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
428 428
429 429 def __repr__(self):
430 430 return '<userGroup(%s)>' % (self.users_group_name)
431 431
432 432 @classmethod
433 433 def get_by_group_name(cls, group_name, cache=False,
434 434 case_insensitive=False):
435 435 if case_insensitive:
436 436 q = cls.query().filter(cls.users_group_name.ilike(group_name))
437 437 else:
438 438 q = cls.query().filter(cls.users_group_name == group_name)
439 439 if cache:
440 440 q = q.options(FromCache(
441 441 "sql_cache_short",
442 442 "get_user_%s" % _hash_key(group_name)
443 443 )
444 444 )
445 445 return q.scalar()
446 446
447 447 @classmethod
448 448 def get(cls, users_group_id, cache=False):
449 449 users_group = cls.query()
450 450 if cache:
451 451 users_group = users_group.options(FromCache("sql_cache_short",
452 452 "get_users_group_%s" % users_group_id))
453 453 return users_group.get(users_group_id)
454 454
455 455
456 456 class UsersGroupMember(Base, BaseModel):
457 457 __tablename__ = 'users_groups_members'
458 458 __table_args__ = (
459 459 {'extend_existing': True, 'mysql_engine':'InnoDB',
460 460 'mysql_charset': 'utf8'},
461 461 )
462 462
463 463 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
464 464 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
465 465 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
466 466
467 467 user = relationship('User', lazy='joined')
468 468 users_group = relationship('UsersGroup')
469 469
470 470 def __init__(self, gr_id='', u_id=''):
471 471 self.users_group_id = gr_id
472 472 self.user_id = u_id
473 473
474 474
475 475 class Repository(Base, BaseModel):
476 476 __tablename__ = 'repositories'
477 477 __table_args__ = (
478 478 UniqueConstraint('repo_name'),
479 479 {'extend_existing': True, 'mysql_engine':'InnoDB',
480 480 'mysql_charset': 'utf8'},
481 481 )
482 482
483 483 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
484 484 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
485 485 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
486 486 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
487 487 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
488 488 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
489 489 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
490 490 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
491 491 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
492 492 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
493 493
494 494 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
495 495 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
496 496
497 497 user = relationship('User')
498 498 fork = relationship('Repository', remote_side=repo_id)
499 499 group = relationship('RepoGroup')
500 500 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
501 501 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
502 502 stats = relationship('Statistics', cascade='all', uselist=False)
503 503
504 504 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
505 505
506 506 logs = relationship('UserLog')
507 507
508 508 def __repr__(self):
509 509 return "<%s('%s:%s')>" % (self.__class__.__name__,
510 510 self.repo_id, self.repo_name)
511 511
512 512 @classmethod
513 513 def url_sep(cls):
514 514 return '/'
515 515
516 516 @classmethod
517 517 def get_by_repo_name(cls, repo_name):
518 518 q = Session.query(cls).filter(cls.repo_name == repo_name)
519 519 q = q.options(joinedload(Repository.fork))\
520 520 .options(joinedload(Repository.user))\
521 521 .options(joinedload(Repository.group))
522 522 return q.scalar()
523 523
524 524 @classmethod
525 525 def get_repo_forks(cls, repo_id):
526 526 return cls.query().filter(Repository.fork_id == repo_id)
527 527
528 528 @classmethod
529 529 def base_path(cls):
530 530 """
531 531 Returns base path when all repos are stored
532 532
533 533 :param cls:
534 534 """
535 535 q = Session.query(RhodeCodeUi)\
536 536 .filter(RhodeCodeUi.ui_key == cls.url_sep())
537 537 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
538 538 return q.one().ui_value
539 539
540 540 @property
541 541 def just_name(self):
542 542 return self.repo_name.split(Repository.url_sep())[-1]
543 543
544 544 @property
545 545 def groups_with_parents(self):
546 546 groups = []
547 547 if self.group is None:
548 548 return groups
549 549
550 550 cur_gr = self.group
551 551 groups.insert(0, cur_gr)
552 552 while 1:
553 553 gr = getattr(cur_gr, 'parent_group', None)
554 554 cur_gr = cur_gr.parent_group
555 555 if gr is None:
556 556 break
557 557 groups.insert(0, gr)
558 558
559 559 return groups
560 560
561 561 @property
562 562 def groups_and_repo(self):
563 563 return self.groups_with_parents, self.just_name
564 564
565 565 @LazyProperty
566 566 def repo_path(self):
567 567 """
568 568 Returns base full path for that repository means where it actually
569 569 exists on a filesystem
570 570 """
571 571 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
572 572 Repository.url_sep())
573 573 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
574 574 return q.one().ui_value
575 575
576 576 @property
577 577 def repo_full_path(self):
578 578 p = [self.repo_path]
579 579 # we need to split the name by / since this is how we store the
580 580 # names in the database, but that eventually needs to be converted
581 581 # into a valid system path
582 582 p += self.repo_name.split(Repository.url_sep())
583 583 return os.path.join(*p)
584 584
585 585 def get_new_name(self, repo_name):
586 586 """
587 587 returns new full repository name based on assigned group and new new
588 588
589 589 :param group_name:
590 590 """
591 591 path_prefix = self.group.full_path_splitted if self.group else []
592 592 return Repository.url_sep().join(path_prefix + [repo_name])
593 593
594 594 @property
595 595 def _ui(self):
596 596 """
597 597 Creates an db based ui object for this repository
598 598 """
599 599 from mercurial import ui
600 600 from mercurial import config
601 601 baseui = ui.ui()
602 602
603 603 #clean the baseui object
604 604 baseui._ocfg = config.config()
605 605 baseui._ucfg = config.config()
606 606 baseui._tcfg = config.config()
607 607
608 608 ret = RhodeCodeUi.query()\
609 609 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
610 610
611 611 hg_ui = ret
612 612 for ui_ in hg_ui:
613 613 if ui_.ui_active:
614 614 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
615 615 ui_.ui_key, ui_.ui_value)
616 616 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
617 617
618 618 return baseui
619 619
620 620 @classmethod
621 621 def is_valid(cls, repo_name):
622 622 """
623 623 returns True if given repo name is a valid filesystem repository
624 624
625 625 :param cls:
626 626 :param repo_name:
627 627 """
628 628 from rhodecode.lib.utils import is_valid_repo
629 629
630 630 return is_valid_repo(repo_name, cls.base_path())
631 631
632 632 #==========================================================================
633 633 # SCM PROPERTIES
634 634 #==========================================================================
635 635
636 636 def get_changeset(self, rev):
637 637 return get_changeset_safe(self.scm_instance, rev)
638 638
639 639 @property
640 640 def tip(self):
641 641 return self.get_changeset('tip')
642 642
643 643 @property
644 644 def author(self):
645 645 return self.tip.author
646 646
647 647 @property
648 648 def last_change(self):
649 649 return self.scm_instance.last_change
650 650
651 651 def comments(self, revisions=None):
652 652 """
653 653 Returns comments for this repository grouped by revisions
654 654
655 655 :param revisions: filter query by revisions only
656 656 """
657 657 cmts = ChangesetComment.query()\
658 658 .filter(ChangesetComment.repo == self)
659 659 if revisions:
660 660 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
661 661 grouped = defaultdict(list)
662 662 for cmt in cmts.all():
663 663 grouped[cmt.revision].append(cmt)
664 664 return grouped
665 665
666 666 #==========================================================================
667 667 # SCM CACHE INSTANCE
668 668 #==========================================================================
669 669
670 670 @property
671 671 def invalidate(self):
672 672 return CacheInvalidation.invalidate(self.repo_name)
673 673
674 674 def set_invalidate(self):
675 675 """
676 676 set a cache for invalidation for this instance
677 677 """
678 678 CacheInvalidation.set_invalidate(self.repo_name)
679 679
680 680 @LazyProperty
681 681 def scm_instance(self):
682 682 return self.__get_instance()
683 683
684 684 @property
685 685 def scm_instance_cached(self):
686 686 @cache_region('long_term')
687 687 def _c(repo_name):
688 688 return self.__get_instance()
689 689 rn = self.repo_name
690 690 log.debug('Getting cached instance of repo')
691 691 inv = self.invalidate
692 692 if inv is not None:
693 693 region_invalidate(_c, None, rn)
694 694 # update our cache
695 695 CacheInvalidation.set_valid(inv.cache_key)
696 696 return _c(rn)
697 697
698 698 def __get_instance(self):
699 699 repo_full_path = self.repo_full_path
700 700 try:
701 701 alias = get_scm(repo_full_path)[0]
702 702 log.debug('Creating instance of %s repository' % alias)
703 703 backend = get_backend(alias)
704 704 except VCSError:
705 705 log.error(traceback.format_exc())
706 706 log.error('Perhaps this repository is in db and not in '
707 707 'filesystem run rescan repositories with '
708 708 '"destroy old data " option from admin panel')
709 709 return
710 710
711 711 if alias == 'hg':
712 712
713 713 repo = backend(safe_str(repo_full_path), create=False,
714 714 baseui=self._ui)
715 715 # skip hidden web repository
716 716 if repo._get_hidden():
717 717 return
718 718 else:
719 719 repo = backend(repo_full_path, create=False)
720 720
721 721 return repo
722 722
723 723
724 724 class RepoGroup(Base, BaseModel):
725 725 __tablename__ = 'groups'
726 726 __table_args__ = (
727 727 UniqueConstraint('group_name', 'group_parent_id'),
728 728 CheckConstraint('group_id != group_parent_id'),
729 729 {'extend_existing': True, 'mysql_engine':'InnoDB',
730 730 'mysql_charset': 'utf8'},
731 731 )
732 732 __mapper_args__ = {'order_by': 'group_name'}
733 733
734 734 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
735 735 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
736 736 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
737 737 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
738 738
739 739 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
740 740 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
741 741
742 742 parent_group = relationship('RepoGroup', remote_side=group_id)
743 743
744 744 def __init__(self, group_name='', parent_group=None):
745 745 self.group_name = group_name
746 746 self.parent_group = parent_group
747 747
748 748 def __repr__(self):
749 749 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
750 750 self.group_name)
751 751
752 752 @classmethod
753 753 def groups_choices(cls):
754 754 from webhelpers.html import literal as _literal
755 755 repo_groups = [('', '')]
756 756 sep = ' &raquo; '
757 757 _name = lambda k: _literal(sep.join(k))
758 758
759 759 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
760 760 for x in cls.query().all()])
761 761
762 762 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
763 763 return repo_groups
764 764
765 765 @classmethod
766 766 def url_sep(cls):
767 767 return '/'
768 768
769 769 @classmethod
770 770 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
771 771 if case_insensitive:
772 772 gr = cls.query()\
773 773 .filter(cls.group_name.ilike(group_name))
774 774 else:
775 775 gr = cls.query()\
776 776 .filter(cls.group_name == group_name)
777 777 if cache:
778 778 gr = gr.options(FromCache(
779 779 "sql_cache_short",
780 780 "get_group_%s" % _hash_key(group_name)
781 781 )
782 782 )
783 783 return gr.scalar()
784 784
785 785 @property
786 786 def parents(self):
787 787 parents_recursion_limit = 5
788 788 groups = []
789 789 if self.parent_group is None:
790 790 return groups
791 791 cur_gr = self.parent_group
792 792 groups.insert(0, cur_gr)
793 793 cnt = 0
794 794 while 1:
795 795 cnt += 1
796 796 gr = getattr(cur_gr, 'parent_group', None)
797 797 cur_gr = cur_gr.parent_group
798 798 if gr is None:
799 799 break
800 800 if cnt == parents_recursion_limit:
801 801 # this will prevent accidental infinit loops
802 802 log.error('group nested more than %s' %
803 803 parents_recursion_limit)
804 804 break
805 805
806 806 groups.insert(0, gr)
807 807 return groups
808 808
809 809 @property
810 810 def children(self):
811 811 return RepoGroup.query().filter(RepoGroup.parent_group == self)
812 812
813 813 @property
814 814 def name(self):
815 815 return self.group_name.split(RepoGroup.url_sep())[-1]
816 816
817 817 @property
818 818 def full_path(self):
819 819 return self.group_name
820 820
821 821 @property
822 822 def full_path_splitted(self):
823 823 return self.group_name.split(RepoGroup.url_sep())
824 824
825 825 @property
826 826 def repositories(self):
827 827 return Repository.query()\
828 828 .filter(Repository.group == self)\
829 829 .order_by(Repository.repo_name)
830 830
831 831 @property
832 832 def repositories_recursive_count(self):
833 833 cnt = self.repositories.count()
834 834
835 835 def children_count(group):
836 836 cnt = 0
837 837 for child in group.children:
838 838 cnt += child.repositories.count()
839 839 cnt += children_count(child)
840 840 return cnt
841 841
842 842 return cnt + children_count(self)
843 843
844 844 def get_new_name(self, group_name):
845 845 """
846 846 returns new full group name based on parent and new name
847 847
848 848 :param group_name:
849 849 """
850 850 path_prefix = (self.parent_group.full_path_splitted if
851 851 self.parent_group else [])
852 852 return RepoGroup.url_sep().join(path_prefix + [group_name])
853 853
854 854
855 855 class Permission(Base, BaseModel):
856 856 __tablename__ = 'permissions'
857 857 __table_args__ = (
858 858 {'extend_existing': True, 'mysql_engine':'InnoDB',
859 859 'mysql_charset': 'utf8'},
860 860 )
861 861 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
862 862 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
863 863 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
864 864
865 865 def __repr__(self):
866 866 return "<%s('%s:%s')>" % (
867 867 self.__class__.__name__, self.permission_id, self.permission_name
868 868 )
869 869
870 870 @classmethod
871 871 def get_by_key(cls, key):
872 872 return cls.query().filter(cls.permission_name == key).scalar()
873 873
874 874 @classmethod
875 875 def get_default_perms(cls, default_user_id):
876 876 q = Session.query(UserRepoToPerm, Repository, cls)\
877 877 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
878 878 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
879 879 .filter(UserRepoToPerm.user_id == default_user_id)
880 880
881 881 return q.all()
882 882
883 883 @classmethod
884 884 def get_default_group_perms(cls, default_user_id):
885 885 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
886 886 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
887 887 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
888 888 .filter(UserRepoGroupToPerm.user_id == default_user_id)
889 889
890 890 return q.all()
891 891
892 892
893 893 class UserRepoToPerm(Base, BaseModel):
894 894 __tablename__ = 'repo_to_perm'
895 895 __table_args__ = (
896 896 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
897 897 {'extend_existing': True, 'mysql_engine':'InnoDB',
898 898 'mysql_charset': 'utf8'}
899 899 )
900 900 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
901 901 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
902 902 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
903 903 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
904 904
905 905 user = relationship('User')
906 906 repository = relationship('Repository')
907 907 permission = relationship('Permission')
908 908
909 909 @classmethod
910 910 def create(cls, user, repository, permission):
911 911 n = cls()
912 912 n.user = user
913 913 n.repository = repository
914 914 n.permission = permission
915 915 Session.add(n)
916 916 return n
917 917
918 918 def __repr__(self):
919 919 return '<user:%s => %s >' % (self.user, self.repository)
920 920
921 921
922 922 class UserToPerm(Base, BaseModel):
923 923 __tablename__ = 'user_to_perm'
924 924 __table_args__ = (
925 925 UniqueConstraint('user_id', 'permission_id'),
926 926 {'extend_existing': True, 'mysql_engine':'InnoDB',
927 927 'mysql_charset': 'utf8'}
928 928 )
929 929 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
930 930 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
931 931 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
932 932
933 933 user = relationship('User')
934 934 permission = relationship('Permission', lazy='joined')
935 935
936 936
937 937 class UsersGroupRepoToPerm(Base, BaseModel):
938 938 __tablename__ = 'users_group_repo_to_perm'
939 939 __table_args__ = (
940 940 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
941 941 {'extend_existing': True, 'mysql_engine':'InnoDB',
942 942 'mysql_charset': 'utf8'}
943 943 )
944 944 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
945 945 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
946 946 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
947 947 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
948 948
949 949 users_group = relationship('UsersGroup')
950 950 permission = relationship('Permission')
951 951 repository = relationship('Repository')
952 952
953 953 @classmethod
954 954 def create(cls, users_group, repository, permission):
955 955 n = cls()
956 956 n.users_group = users_group
957 957 n.repository = repository
958 958 n.permission = permission
959 959 Session.add(n)
960 960 return n
961 961
962 962 def __repr__(self):
963 963 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
964 964
965 965
966 966 class UsersGroupToPerm(Base, BaseModel):
967 967 __tablename__ = 'users_group_to_perm'
968 968 __table_args__ = (
969 969 UniqueConstraint('users_group_id', 'permission_id',),
970 {'extend_existing': True, 'mysql_engine':'InnoDB',
970 {'extend_existing': True, 'mysql_engine':'InnoDB',
971 971 'mysql_charset': 'utf8'}
972 972 )
973 973 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
974 974 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
975 975 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
976 976
977 977 users_group = relationship('UsersGroup')
978 978 permission = relationship('Permission')
979 979
980 980
981 981 class UserRepoGroupToPerm(Base, BaseModel):
982 982 __tablename__ = 'user_repo_group_to_perm'
983 983 __table_args__ = (
984 984 UniqueConstraint('user_id', 'group_id', 'permission_id'),
985 {'extend_existing': True, 'mysql_engine':'InnoDB',
985 {'extend_existing': True, 'mysql_engine':'InnoDB',
986 986 'mysql_charset': 'utf8'}
987 987 )
988 988
989 989 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
990 990 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
991 991 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
992 992 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
993 993
994 994 user = relationship('User')
995 995 group = relationship('RepoGroup')
996 996 permission = relationship('Permission')
997 997
998 998
999 999 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1000 1000 __tablename__ = 'users_group_repo_group_to_perm'
1001 1001 __table_args__ = (
1002 1002 UniqueConstraint('users_group_id', 'group_id'),
1003 1003 {'extend_existing': True, 'mysql_engine':'InnoDB',
1004 1004 'mysql_charset': 'utf8'}
1005 1005 )
1006 1006
1007 1007 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)
1008 1008 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1009 1009 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1010 1010 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1011 1011
1012 1012 users_group = relationship('UsersGroup')
1013 1013 permission = relationship('Permission')
1014 1014 group = relationship('RepoGroup')
1015 1015
1016 1016
1017 1017 class Statistics(Base, BaseModel):
1018 1018 __tablename__ = 'statistics'
1019 1019 __table_args__ = (
1020 UniqueConstraint('repository_id'),
1021 {'extend_existing': True, 'mysql_engine':'InnoDB',
1020 UniqueConstraint('repository_id'),
1021 {'extend_existing': True, 'mysql_engine':'InnoDB',
1022 1022 'mysql_charset': 'utf8'}
1023 1023 )
1024 1024 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1025 1025 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1026 1026 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1027 1027 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1028 1028 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1029 1029 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1030 1030
1031 1031 repository = relationship('Repository', single_parent=True)
1032 1032
1033 1033
1034 1034 class UserFollowing(Base, BaseModel):
1035 1035 __tablename__ = 'user_followings'
1036 1036 __table_args__ = (
1037 1037 UniqueConstraint('user_id', 'follows_repository_id'),
1038 1038 UniqueConstraint('user_id', 'follows_user_id'),
1039 1039 {'extend_existing': True, 'mysql_engine':'InnoDB',
1040 1040 'mysql_charset': 'utf8'}
1041 1041 )
1042 1042
1043 1043 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1044 1044 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1045 1045 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1046 1046 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1047 1047 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1048 1048
1049 1049 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1050 1050
1051 1051 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1052 1052 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1053 1053
1054 1054 @classmethod
1055 1055 def get_repo_followers(cls, repo_id):
1056 1056 return cls.query().filter(cls.follows_repo_id == repo_id)
1057 1057
1058 1058
1059 1059 class CacheInvalidation(Base, BaseModel):
1060 1060 __tablename__ = 'cache_invalidation'
1061 1061 __table_args__ = (
1062 1062 UniqueConstraint('cache_key'),
1063 1063 {'extend_existing': True, 'mysql_engine':'InnoDB',
1064 1064 'mysql_charset': 'utf8'},
1065 1065 )
1066 1066 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1067 1067 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1068 1068 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1069 1069 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1070 1070
1071 1071 def __init__(self, cache_key, cache_args=''):
1072 1072 self.cache_key = cache_key
1073 1073 self.cache_args = cache_args
1074 1074 self.cache_active = False
1075 1075
1076 1076 def __repr__(self):
1077 1077 return "<%s('%s:%s')>" % (self.__class__.__name__,
1078 1078 self.cache_id, self.cache_key)
1079 1079 @classmethod
1080 1080 def clear_cache(cls):
1081 1081 cls.query().delete()
1082 1082
1083 1083 @classmethod
1084 1084 def _get_key(cls, key):
1085 1085 """
1086 1086 Wrapper for generating a key, together with a prefix
1087 1087
1088 1088 :param key:
1089 1089 """
1090 1090 import rhodecode
1091 1091 prefix = ''
1092 1092 iid = rhodecode.CONFIG.get('instance_id')
1093 1093 if iid:
1094 1094 prefix = iid
1095 1095 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1096 1096
1097 1097 @classmethod
1098 1098 def get_by_key(cls, key):
1099 1099 return cls.query().filter(cls.cache_key == key).scalar()
1100 1100
1101 1101 @classmethod
1102 1102 def _get_or_create_key(cls, key, prefix, org_key):
1103 1103 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1104 1104 if not inv_obj:
1105 1105 try:
1106 1106 inv_obj = CacheInvalidation(key, org_key)
1107 1107 Session.add(inv_obj)
1108 1108 Session.commit()
1109 1109 except Exception:
1110 1110 log.error(traceback.format_exc())
1111 1111 Session.rollback()
1112 1112 return inv_obj
1113 1113
1114 1114 @classmethod
1115 1115 def invalidate(cls, key):
1116 1116 """
1117 1117 Returns Invalidation object if this given key should be invalidated
1118 1118 None otherwise. `cache_active = False` means that this cache
1119 1119 state is not valid and needs to be invalidated
1120 1120
1121 1121 :param key:
1122 1122 """
1123 1123
1124 1124 key, _prefix, _org_key = cls._get_key(key)
1125 1125 inv = cls._get_or_create_key(key, _prefix, _org_key)
1126 1126
1127 1127 if inv and inv.cache_active is False:
1128 1128 return inv
1129 1129
1130 1130 @classmethod
1131 1131 def set_invalidate(cls, key):
1132 1132 """
1133 1133 Mark this Cache key for invalidation
1134 1134
1135 1135 :param key:
1136 1136 """
1137 1137
1138 1138 key, _prefix, _org_key = cls._get_key(key)
1139 1139 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1140 1140 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1141 1141 _org_key))
1142 1142 try:
1143 1143 for inv_obj in inv_objs:
1144 1144 if inv_obj:
1145 1145 inv_obj.cache_active = False
1146 1146
1147 1147 Session.add(inv_obj)
1148 1148 Session.commit()
1149 1149 except Exception:
1150 1150 log.error(traceback.format_exc())
1151 1151 Session.rollback()
1152 1152
1153 1153 @classmethod
1154 1154 def set_valid(cls, key):
1155 1155 """
1156 1156 Mark this cache key as active and currently cached
1157 1157
1158 1158 :param key:
1159 1159 """
1160 1160 inv_obj = cls.get_by_key(key)
1161 1161 inv_obj.cache_active = True
1162 1162 Session.add(inv_obj)
1163 1163 Session.commit()
1164 1164
1165 1165
1166 1166 class ChangesetComment(Base, BaseModel):
1167 1167 __tablename__ = 'changeset_comments'
1168 1168 __table_args__ = (
1169 1169 {'extend_existing': True, 'mysql_engine':'InnoDB',
1170 1170 'mysql_charset': 'utf8'},
1171 1171 )
1172 1172 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1173 1173 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1174 1174 revision = Column('revision', String(40), nullable=False)
1175 1175 line_no = Column('line_no', Unicode(10), nullable=True)
1176 1176 f_path = Column('f_path', Unicode(1000), nullable=True)
1177 1177 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1178 1178 text = Column('text', Unicode(25000), nullable=False)
1179 1179 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1180 1180
1181 1181 author = relationship('User', lazy='joined')
1182 1182 repo = relationship('Repository')
1183 1183
1184 1184 @classmethod
1185 1185 def get_users(cls, revision):
1186 1186 """
1187 1187 Returns user associated with this changesetComment. ie those
1188 1188 who actually commented
1189 1189
1190 1190 :param cls:
1191 1191 :param revision:
1192 1192 """
1193 1193 return Session.query(User)\
1194 1194 .filter(cls.revision == revision)\
1195 1195 .join(ChangesetComment.author).all()
1196 1196
1197 1197
1198 1198 class Notification(Base, BaseModel):
1199 1199 __tablename__ = 'notifications'
1200 1200 __table_args__ = (
1201 1201 {'extend_existing': True, 'mysql_engine':'InnoDB',
1202 1202 'mysql_charset': 'utf8'},
1203 1203 )
1204 1204
1205 1205 TYPE_CHANGESET_COMMENT = u'cs_comment'
1206 1206 TYPE_MESSAGE = u'message'
1207 1207 TYPE_MENTION = u'mention'
1208 1208 TYPE_REGISTRATION = u'registration'
1209 1209
1210 1210 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1211 1211 subject = Column('subject', Unicode(512), nullable=True)
1212 1212 body = Column('body', Unicode(50000), nullable=True)
1213 1213 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1214 1214 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1215 1215 type_ = Column('type', Unicode(256))
1216 1216
1217 1217 created_by_user = relationship('User')
1218 1218 notifications_to_users = relationship('UserNotification', lazy='joined',
1219 1219 cascade="all, delete, delete-orphan")
1220 1220
1221 1221 @property
1222 1222 def recipients(self):
1223 1223 return [x.user for x in UserNotification.query()\
1224 1224 .filter(UserNotification.notification == self).all()]
1225 1225
1226 1226 @classmethod
1227 1227 def create(cls, created_by, subject, body, recipients, type_=None):
1228 1228 if type_ is None:
1229 1229 type_ = Notification.TYPE_MESSAGE
1230 1230
1231 1231 notification = cls()
1232 1232 notification.created_by_user = created_by
1233 1233 notification.subject = subject
1234 1234 notification.body = body
1235 1235 notification.type_ = type_
1236 1236 notification.created_on = datetime.datetime.now()
1237 1237
1238 1238 for u in recipients:
1239 1239 assoc = UserNotification()
1240 1240 assoc.notification = notification
1241 1241 u.notifications.append(assoc)
1242 1242 Session.add(notification)
1243 1243 return notification
1244 1244
1245 1245 @property
1246 1246 def description(self):
1247 1247 from rhodecode.model.notification import NotificationModel
1248 1248 return NotificationModel().make_description(self)
1249 1249
1250 1250
1251 1251 class UserNotification(Base, BaseModel):
1252 1252 __tablename__ = 'user_to_notification'
1253 1253 __table_args__ = (
1254 1254 UniqueConstraint('user_id', 'notification_id'),
1255 1255 {'extend_existing': True, 'mysql_engine':'InnoDB',
1256 1256 'mysql_charset': 'utf8'}
1257 1257 )
1258 1258 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1259 1259 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1260 1260 read = Column('read', Boolean, default=False)
1261 1261 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1262 1262
1263 1263 user = relationship('User', lazy="joined")
1264 1264 notification = relationship('Notification', lazy="joined",
1265 1265 order_by=lambda: Notification.created_on.desc(),)
1266 1266
1267 1267 def mark_as_read(self):
1268 1268 self.read = True
1269 1269 Session.add(self)
1270 1270
1271 1271
1272 1272 class DbMigrateVersion(Base, BaseModel):
1273 1273 __tablename__ = 'db_migrate_version'
1274 1274 __table_args__ = (
1275 1275 {'extend_existing': True, 'mysql_engine':'InnoDB',
1276 1276 'mysql_charset': 'utf8'},
1277 1277 )
1278 1278 repository_id = Column('repository_id', String(250), primary_key=True)
1279 1279 repository_path = Column('repository_path', Text)
1280 1280 version = Column('version', Integer)
@@ -1,588 +1,588 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.user
4 4 ~~~~~~~~~~~~~~~~~~~~
5 5
6 6 users model for RhodeCode
7 7
8 8 :created_on: Apr 9, 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 logging
27 27 import traceback
28 28
29 29 from pylons import url
30 30 from pylons.i18n.translation import _
31 31
32 32 from rhodecode.lib.utils2 import safe_unicode, generate_api_key
33 33 from rhodecode.lib.caching_query import FromCache
34 34
35 35 from rhodecode.model import BaseModel
36 36 from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \
37 37 UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \
38 38 Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\
39 39 UsersGroupRepoGroupToPerm
40 40 from rhodecode.lib.exceptions import DefaultUserException, \
41 41 UserOwnsReposException
42 42
43 43 from sqlalchemy.exc import DatabaseError
44 44
45 45 from sqlalchemy.orm import joinedload
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49
50 50 PERM_WEIGHTS = {
51 51 'repository.none': 0,
52 52 'repository.read': 1,
53 53 'repository.write': 3,
54 54 'repository.admin': 4,
55 55 'group.none': 0,
56 56 'group.read': 1,
57 57 'group.write': 3,
58 58 'group.admin': 4,
59 59 }
60 60
61 61
62 62 class UserModel(BaseModel):
63 63
64 64 def __get_user(self, user):
65 65 return self._get_instance(User, user, callback=User.get_by_username)
66 66
67 67 def __get_perm(self, permission):
68 68 return self._get_instance(Permission, permission,
69 69 callback=Permission.get_by_key)
70 70
71 71 def get(self, user_id, cache=False):
72 72 user = self.sa.query(User)
73 73 if cache:
74 74 user = user.options(FromCache("sql_cache_short",
75 75 "get_user_%s" % user_id))
76 76 return user.get(user_id)
77 77
78 78 def get_user(self, user):
79 79 return self.__get_user(user)
80 80
81 81 def get_by_username(self, username, cache=False, case_insensitive=False):
82 82
83 83 if case_insensitive:
84 84 user = self.sa.query(User).filter(User.username.ilike(username))
85 85 else:
86 86 user = self.sa.query(User)\
87 87 .filter(User.username == username)
88 88 if cache:
89 89 user = user.options(FromCache("sql_cache_short",
90 90 "get_user_%s" % username))
91 91 return user.scalar()
92 92
93 93 def get_by_api_key(self, api_key, cache=False):
94 94 return User.get_by_api_key(api_key, cache)
95 95
96 96 def create(self, form_data):
97 97 try:
98 98 new_user = User()
99 99 for k, v in form_data.items():
100 100 setattr(new_user, k, v)
101 101
102 102 new_user.api_key = generate_api_key(form_data['username'])
103 103 self.sa.add(new_user)
104 104 return new_user
105 105 except:
106 106 log.error(traceback.format_exc())
107 107 raise
108 108
109 109 def create_or_update(self, username, password, email, name, lastname,
110 110 active=True, admin=False, ldap_dn=None):
111 111 """
112 112 Creates a new instance if not found, or updates current one
113 113
114 114 :param username:
115 115 :param password:
116 116 :param email:
117 117 :param active:
118 118 :param name:
119 119 :param lastname:
120 120 :param active:
121 121 :param admin:
122 122 :param ldap_dn:
123 123 """
124 124
125 125 from rhodecode.lib.auth import get_crypt_password
126 126
127 127 log.debug('Checking for %s account in RhodeCode database' % username)
128 128 user = User.get_by_username(username, case_insensitive=True)
129 129 if user is None:
130 130 log.debug('creating new user %s' % username)
131 131 new_user = User()
132 132 else:
133 133 log.debug('updating user %s' % username)
134 134 new_user = user
135 135
136 136 try:
137 137 new_user.username = username
138 138 new_user.admin = admin
139 139 new_user.password = get_crypt_password(password)
140 140 new_user.api_key = generate_api_key(username)
141 141 new_user.email = email
142 142 new_user.active = active
143 143 new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
144 144 new_user.name = name
145 145 new_user.lastname = lastname
146 146 self.sa.add(new_user)
147 147 return new_user
148 148 except (DatabaseError,):
149 149 log.error(traceback.format_exc())
150 150 raise
151 151
152 152 def create_for_container_auth(self, username, attrs):
153 153 """
154 154 Creates the given user if it's not already in the database
155 155
156 156 :param username:
157 157 :param attrs:
158 158 """
159 159 if self.get_by_username(username, case_insensitive=True) is None:
160 160
161 161 # autogenerate email for container account without one
162 162 generate_email = lambda usr: '%s@container_auth.account' % usr
163 163
164 164 try:
165 165 new_user = User()
166 166 new_user.username = username
167 167 new_user.password = None
168 168 new_user.api_key = generate_api_key(username)
169 169 new_user.email = attrs['email']
170 170 new_user.active = attrs.get('active', True)
171 171 new_user.name = attrs['name'] or generate_email(username)
172 172 new_user.lastname = attrs['lastname']
173 173
174 174 self.sa.add(new_user)
175 175 return new_user
176 176 except (DatabaseError,):
177 177 log.error(traceback.format_exc())
178 178 self.sa.rollback()
179 179 raise
180 180 log.debug('User %s already exists. Skipping creation of account'
181 181 ' for container auth.', username)
182 182 return None
183 183
184 184 def create_ldap(self, username, password, user_dn, attrs):
185 185 """
186 186 Checks if user is in database, if not creates this user marked
187 187 as ldap user
188 188
189 189 :param username:
190 190 :param password:
191 191 :param user_dn:
192 192 :param attrs:
193 193 """
194 194 from rhodecode.lib.auth import get_crypt_password
195 195 log.debug('Checking for such ldap account in RhodeCode database')
196 196 if self.get_by_username(username, case_insensitive=True) is None:
197 197
198 198 # autogenerate email for ldap account without one
199 199 generate_email = lambda usr: '%s@ldap.account' % usr
200 200
201 201 try:
202 202 new_user = User()
203 203 username = username.lower()
204 204 # add ldap account always lowercase
205 205 new_user.username = username
206 206 new_user.password = get_crypt_password(password)
207 207 new_user.api_key = generate_api_key(username)
208 208 new_user.email = attrs['email'] or generate_email(username)
209 209 new_user.active = attrs.get('active', True)
210 210 new_user.ldap_dn = safe_unicode(user_dn)
211 211 new_user.name = attrs['name']
212 212 new_user.lastname = attrs['lastname']
213 213
214 214 self.sa.add(new_user)
215 215 return new_user
216 216 except (DatabaseError,):
217 217 log.error(traceback.format_exc())
218 218 self.sa.rollback()
219 219 raise
220 220 log.debug('this %s user exists skipping creation of ldap account',
221 221 username)
222 222 return None
223 223
224 224 def create_registration(self, form_data):
225 225 from rhodecode.model.notification import NotificationModel
226 226
227 227 try:
228 228 new_user = User()
229 229 for k, v in form_data.items():
230 230 if k != 'admin':
231 231 setattr(new_user, k, v)
232 232
233 233 self.sa.add(new_user)
234 234 self.sa.flush()
235 235
236 236 # notification to admins
237 237 subject = _('new user registration')
238 238 body = ('New user registration\n'
239 239 '---------------------\n'
240 240 '- Username: %s\n'
241 241 '- Full Name: %s\n'
242 242 '- Email: %s\n')
243 243 body = body % (new_user.username, new_user.full_name,
244 244 new_user.email)
245 245 edit_url = url('edit_user', id=new_user.user_id, qualified=True)
246 246 kw = {'registered_user_url': edit_url}
247 247 NotificationModel().create(created_by=new_user, subject=subject,
248 248 body=body, recipients=None,
249 249 type_=Notification.TYPE_REGISTRATION,
250 250 email_kwargs=kw)
251 251
252 252 except:
253 253 log.error(traceback.format_exc())
254 254 raise
255 255
256 256 def update(self, user_id, form_data):
257 257 try:
258 258 user = self.get(user_id, cache=False)
259 259 if user.username == 'default':
260 260 raise DefaultUserException(
261 261 _("You can't Edit this user since it's"
262 262 " crucial for entire application"))
263 263
264 264 for k, v in form_data.items():
265 265 if k == 'new_password' and v != '':
266 266 user.password = v
267 267 user.api_key = generate_api_key(user.username)
268 268 else:
269 269 setattr(user, k, v)
270 270
271 271 self.sa.add(user)
272 272 except:
273 273 log.error(traceback.format_exc())
274 274 raise
275 275
276 276 def update_my_account(self, user_id, form_data):
277 277 try:
278 278 user = self.get(user_id, cache=False)
279 279 if user.username == 'default':
280 280 raise DefaultUserException(
281 281 _("You can't Edit this user since it's"
282 282 " crucial for entire application"))
283 283 for k, v in form_data.items():
284 284 if k == 'new_password' and v != '':
285 285 user.password = v
286 286 user.api_key = generate_api_key(user.username)
287 287 else:
288 288 if k not in ['admin', 'active']:
289 289 setattr(user, k, v)
290 290
291 291 self.sa.add(user)
292 292 except:
293 293 log.error(traceback.format_exc())
294 294 raise
295 295
296 296 def delete(self, user):
297 297 user = self.__get_user(user)
298 298
299 299 try:
300 300 if user.username == 'default':
301 301 raise DefaultUserException(
302 302 _("You can't remove this user since it's"
303 303 " crucial for entire application")
304 304 )
305 305 if user.repositories:
306 306 raise UserOwnsReposException(
307 307 _('user "%s" still owns %s repositories and cannot be '
308 308 'removed. Switch owners or remove those repositories')
309 309 % (user.username, user.repositories)
310 310 )
311 311 self.sa.delete(user)
312 312 except:
313 313 log.error(traceback.format_exc())
314 314 raise
315 315
316 316 def reset_password_link(self, data):
317 317 from rhodecode.lib.celerylib import tasks, run_task
318 318 run_task(tasks.send_password_link, data['email'])
319 319
320 320 def reset_password(self, data):
321 321 from rhodecode.lib.celerylib import tasks, run_task
322 322 run_task(tasks.reset_user_password, data['email'])
323 323
324 324 def fill_data(self, auth_user, user_id=None, api_key=None):
325 325 """
326 326 Fetches auth_user by user_id,or api_key if present.
327 327 Fills auth_user attributes with those taken from database.
328 328 Additionally set's is_authenitated if lookup fails
329 329 present in database
330 330
331 331 :param auth_user: instance of user to set attributes
332 332 :param user_id: user id to fetch by
333 333 :param api_key: api key to fetch by
334 334 """
335 335 if user_id is None and api_key is None:
336 336 raise Exception('You need to pass user_id or api_key')
337 337
338 338 try:
339 339 if api_key:
340 340 dbuser = self.get_by_api_key(api_key)
341 341 else:
342 342 dbuser = self.get(user_id)
343 343
344 344 if dbuser is not None and dbuser.active:
345 345 log.debug('filling %s data' % dbuser)
346 346 for k, v in dbuser.get_dict().items():
347 347 setattr(auth_user, k, v)
348 348 else:
349 349 return False
350 350
351 351 except:
352 352 log.error(traceback.format_exc())
353 353 auth_user.is_authenticated = False
354 354 return False
355 355
356 356 return True
357 357
358 358 def fill_perms(self, user):
359 359 """
360 360 Fills user permission attribute with permissions taken from database
361 361 works for permissions given for repositories, and for permissions that
362 362 are granted to groups
363 363
364 364 :param user: user instance to fill his perms
365 365 """
366 366 RK = 'repositories'
367 367 GK = 'repositories_groups'
368 368 GLOBAL = 'global'
369 369 user.permissions[RK] = {}
370 370 user.permissions[GK] = {}
371 371 user.permissions[GLOBAL] = set()
372 372
373 373 #======================================================================
374 374 # fetch default permissions
375 375 #======================================================================
376 376 default_user = User.get_by_username('default', cache=True)
377 377 default_user_id = default_user.user_id
378 378
379 379 default_repo_perms = Permission.get_default_perms(default_user_id)
380 380 default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
381 381
382 382 if user.is_admin:
383 383 #==================================================================
384 384 # admin user have all default rights for repositories
385 385 # and groups set to admin
386 386 #==================================================================
387 387 user.permissions[GLOBAL].add('hg.admin')
388 388
389 389 # repositories
390 390 for perm in default_repo_perms:
391 391 r_k = perm.UserRepoToPerm.repository.repo_name
392 392 p = 'repository.admin'
393 393 user.permissions[RK][r_k] = p
394 394
395 395 # repositories groups
396 396 for perm in default_repo_groups_perms:
397 397 rg_k = perm.UserRepoGroupToPerm.group.group_name
398 398 p = 'group.admin'
399 399 user.permissions[GK][rg_k] = p
400 400
401 401 else:
402 402 #==================================================================
403 403 # set default permissions first for repositories and groups
404 404 #==================================================================
405 405 uid = user.user_id
406 406
407 407 # default global permissions
408 408 default_global_perms = self.sa.query(UserToPerm)\
409 409 .filter(UserToPerm.user_id == default_user_id)
410 410
411 411 for perm in default_global_perms:
412 412 user.permissions[GLOBAL].add(perm.permission.permission_name)
413 413
414 414 # defaults for repositories, taken from default user
415 415 for perm in default_repo_perms:
416 416 r_k = perm.UserRepoToPerm.repository.repo_name
417 417 if perm.Repository.private and not (perm.Repository.user_id == uid):
418 418 # disable defaults for private repos,
419 419 p = 'repository.none'
420 420 elif perm.Repository.user_id == uid:
421 421 # set admin if owner
422 422 p = 'repository.admin'
423 423 else:
424 424 p = perm.Permission.permission_name
425 425
426 426 user.permissions[RK][r_k] = p
427 427
428 428 # defaults for repositories groups taken from default user permission
429 429 # on given group
430 430 for perm in default_repo_groups_perms:
431 431 rg_k = perm.UserRepoGroupToPerm.group.group_name
432 432 p = perm.Permission.permission_name
433 433 user.permissions[GK][rg_k] = p
434 434
435 435 #==================================================================
436 436 # overwrite defaults with user permissions if any found
437 437 #==================================================================
438 438
439 439 # user global permissions
440 440 user_perms = self.sa.query(UserToPerm)\
441 441 .options(joinedload(UserToPerm.permission))\
442 442 .filter(UserToPerm.user_id == uid).all()
443 443
444 444 for perm in user_perms:
445 445 user.permissions[GLOBAL].add(perm.permission.permission_name)
446 446
447 447 # user explicit permissions for repositories
448 448 user_repo_perms = \
449 449 self.sa.query(UserRepoToPerm, Permission, Repository)\
450 450 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
451 451 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
452 452 .filter(UserRepoToPerm.user_id == uid)\
453 453 .all()
454 454
455 455 for perm in user_repo_perms:
456 456 # set admin if owner
457 457 r_k = perm.UserRepoToPerm.repository.repo_name
458 458 if perm.Repository.user_id == uid:
459 459 p = 'repository.admin'
460 460 else:
461 461 p = perm.Permission.permission_name
462 462 user.permissions[RK][r_k] = p
463 463
464 464 #==================================================================
465 465 # check if user is part of user groups for this repository and
466 466 # fill in (or replace with higher) permissions
467 467 #==================================================================
468 468
469 469 # users group global
470 470 user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
471 471 .options(joinedload(UsersGroupToPerm.permission))\
472 472 .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
473 473 UsersGroupMember.users_group_id))\
474 474 .filter(UsersGroupMember.user_id == uid).all()
475 475
476 476 for perm in user_perms_from_users_groups:
477 477 user.permissions[GLOBAL].add(perm.permission.permission_name)
478 478
479 479 # users group for repositories permissions
480 480 user_repo_perms_from_users_groups = \
481 481 self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
482 482 .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
483 483 .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
484 484 .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
485 485 .filter(UsersGroupMember.user_id == uid)\
486 486 .all()
487 487
488 488 for perm in user_repo_perms_from_users_groups:
489 489 r_k = perm.UsersGroupRepoToPerm.repository.repo_name
490 490 p = perm.Permission.permission_name
491 491 cur_perm = user.permissions[RK][r_k]
492 492 # overwrite permission only if it's greater than permission
493 493 # given from other sources
494 494 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
495 495 user.permissions[RK][r_k] = p
496 496
497 497 #==================================================================
498 498 # get access for this user for repos group and override defaults
499 499 #==================================================================
500 500
501 501 # user explicit permissions for repository
502 502 user_repo_groups_perms = \
503 503 self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
504 504 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
505 505 .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
506 506 .filter(UserRepoGroupToPerm.user_id == uid)\
507 507 .all()
508 508
509 509 for perm in user_repo_groups_perms:
510 510 rg_k = perm.UserRepoGroupToPerm.group.group_name
511 511 p = perm.Permission.permission_name
512 512 cur_perm = user.permissions[GK][rg_k]
513 513 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
514 514 user.permissions[GK][rg_k] = p
515 515
516 516 #==================================================================
517 517 # check if user is part of user groups for this repo group and
518 518 # fill in (or replace with higher) permissions
519 519 #==================================================================
520 520
521 521 # users group for repositories permissions
522 522 user_repo_group_perms_from_users_groups = \
523 523 self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
524 524 .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
525 525 .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
526 526 .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
527 527 .filter(UsersGroupMember.user_id == uid)\
528 528 .all()
529
529
530 530 for perm in user_repo_group_perms_from_users_groups:
531 531 g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
532 532 print perm, g_k
533 533 p = perm.Permission.permission_name
534 534 cur_perm = user.permissions[GK][g_k]
535 535 # overwrite permission only if it's greater than permission
536 536 # given from other sources
537 537 if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
538 538 user.permissions[GK][g_k] = p
539 539
540 540 return user
541 541
542 542 def has_perm(self, user, perm):
543 543 if not isinstance(perm, Permission):
544 544 raise Exception('perm needs to be an instance of Permission class '
545 545 'got %s instead' % type(perm))
546 546
547 547 user = self.__get_user(user)
548 548
549 549 return UserToPerm.query().filter(UserToPerm.user == user)\
550 550 .filter(UserToPerm.permission == perm).scalar() is not None
551 551
552 552 def grant_perm(self, user, perm):
553 553 """
554 554 Grant user global permissions
555 555
556 556 :param user:
557 557 :param perm:
558 558 """
559 559 user = self.__get_user(user)
560 560 perm = self.__get_perm(perm)
561 561 # if this permission is already granted skip it
562 562 _perm = UserToPerm.query()\
563 563 .filter(UserToPerm.user == user)\
564 564 .filter(UserToPerm.permission == perm)\
565 565 .scalar()
566 566 if _perm:
567 567 return
568 568 new = UserToPerm()
569 569 new.user = user
570 570 new.permission = perm
571 571 self.sa.add(new)
572 572
573 573 def revoke_perm(self, user, perm):
574 574 """
575 575 Revoke users global permissions
576 576
577 577 :param user:
578 578 :param perm:
579 579 """
580 580 user = self.__get_user(user)
581 581 perm = self.__get_perm(perm)
582 582
583 583 obj = UserToPerm.query()\
584 584 .filter(UserToPerm.user == user)\
585 585 .filter(UserToPerm.permission == perm)\
586 586 .scalar()
587 587 if obj:
588 588 self.sa.delete(obj)
@@ -1,128 +1,128 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repo_info.repo_to_perm:
12 12 %if r2p.user.username =='default' and c.repo_info.private:
13 13 <tr>
14 14 <td colspan="4">
15 15 <span class="private_repo_msg">
16 16 ${_('private repository')}
17 17 </span>
18 18 </td>
19 19 <td class="private_repo_msg"><img style="vertical-align:bottom" src="${h.url('/images/icons/user.png')}"/>${r2p.user.username}</td>
20 20 </tr>
21 21 %else:
22 22 <tr id="id${id(r2p.user.username)}">
23 23 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.none')}</td>
24 24 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.read')}</td>
25 25 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.write')}</td>
26 26 <td>${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')}</td>
27 27 <td style="white-space: nowrap;">
28 28 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username}
29 29 </td>
30 30 <td>
31 31 %if r2p.user.username !='default':
32 32 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
33 33 ${_('revoke')}
34 34 </span>
35 35 %endif
36 36 </td>
37 37 </tr>
38 38 %endif
39 39 %endfor
40 40
41 41 ## USERS GROUPS
42 42 %for g2p in c.repo_info.users_group_to_perm:
43 43 <tr id="id${id(g2p.users_group.users_group_name)}">
44 44 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.none')}</td>
45 45 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.read')}</td>
46 46 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')}</td>
47 47 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')}</td>
48 48 <td style="white-space: nowrap;">
49 49 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
50 50 </td>
51 51 <td>
52 52 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
53 53 ${_('revoke')}
54 54 </span>
55 55 </td>
56 56 </tr>
57 57 %endfor
58 58 <tr id="add_perm_input">
59 59 <td>${h.radio('perm_new_member','repository.none')}</td>
60 60 <td>${h.radio('perm_new_member','repository.read')}</td>
61 61 <td>${h.radio('perm_new_member','repository.write')}</td>
62 62 <td>${h.radio('perm_new_member','repository.admin')}</td>
63 63 <td class='ac'>
64 64 <div class="perm_ac" id="perm_ac">
65 65 ${h.text('perm_new_member_name',class_='yui-ac-input')}
66 66 ${h.hidden('perm_new_member_type')}
67 67 <div id="perm_container"></div>
68 68 </div>
69 69 </td>
70 70 <td></td>
71 71 </tr>
72 72 <tr>
73 73 <td colspan="6">
74 74 <span id="add_perm" class="add_icon" style="cursor: pointer;">
75 75 ${_('Add another member')}
76 76 </span>
77 77 </td>
78 78 </tr>
79 79 </table>
80 80 <script type="text/javascript">
81 81 function ajaxActionUser(user_id, field_id) {
82 82 var sUrl = "${h.url('delete_repo_user',repo_name=c.repo_name)}";
83 83 var callback = {
84 84 success: function (o) {
85 85 var tr = YUD.get(String(field_id));
86 86 tr.parentNode.removeChild(tr);
87 87 },
88 88 failure: function (o) {
89 89 alert("${_('Failed to remove user')}");
90 90 },
91 91 };
92 92 var postData = '_method=delete&user_id=' + user_id;
93 93 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
94 94 };
95 95
96 96 function ajaxActionUsersGroup(users_group_id,field_id){
97 97 var sUrl = "${h.url('delete_repo_users_group',repo_name=c.repo_name)}";
98 98 var callback = {
99 99 success:function(o){
100 100 var tr = YUD.get(String(field_id));
101 101 tr.parentNode.removeChild(tr);
102 102 },
103 103 failure:function(o){
104 104 alert("${_('Failed to remove users group')}");
105 105 },
106 106 };
107 107 var postData = '_method=delete&users_group_id='+users_group_id;
108 108 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
109 109 };
110 110
111 111 YUE.onDOMReady(function () {
112 112 if (!YUD.hasClass('perm_new_member_name', 'error')) {
113 113 YUD.setStyle('add_perm_input', 'display', 'none');
114 114 }
115 115 YAHOO.util.Event.addListener('add_perm', 'click', function () {
116 116 YUD.setStyle('add_perm_input', 'display', '');
117 117 YUD.setStyle('add_perm', 'opacity', '0.6');
118 118 YUD.setStyle('add_perm', 'cursor', 'default');
119 119 });
120 120 MembersAutoComplete(
121 121 ${c.users_array|n},
122 122 ${c.users_groups_array|n},
123 123 "${_('Group')}",
124 124 "${_('members')}"
125 );
125 );
126 126 });
127 127
128 128 </script>
@@ -1,117 +1,117 b''
1 1 <table id="permissions_manage" class="noborder">
2 2 <tr>
3 3 <td>${_('none')}</td>
4 4 <td>${_('read')}</td>
5 5 <td>${_('write')}</td>
6 6 <td>${_('admin')}</td>
7 7 <td>${_('member')}</td>
8 8 <td></td>
9 9 </tr>
10 10 ## USERS
11 11 %for r2p in c.repos_group.repo_group_to_perm:
12 12 <tr id="id${id(r2p.user.username)}">
13 13 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.none')}</td>
14 14 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.read')}</td>
15 15 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.write')}</td>
16 16 <td>${h.radio('u_perm_%s' % r2p.user.username,'group.admin')}</td>
17 17 <td style="white-space: nowrap;">
18 18 <img class="perm-gravatar" src="${h.gravatar_url(r2p.user.email,14)}"/>${r2p.user.username}
19 19 </td>
20 20 <td>
21 21 %if r2p.user.username !='default':
22 22 <span class="delete_icon action_button" onclick="ajaxActionUser(${r2p.user.user_id},'${'id%s'%id(r2p.user.username)}')">
23 23 ${_('revoke')}
24 24 </span>
25 25 %endif
26 26 </td>
27 27 </tr>
28 28 %endfor
29 29
30 30 ## USERS GROUPS
31 31 %for g2p in c.repos_group.users_group_to_perm:
32 32 <tr id="id${id(g2p.users_group.users_group_name)}">
33 33 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.none')}</td>
34 34 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.read')}</td>
35 35 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.write')}</td>
36 36 <td>${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'group.admin')}</td>
37 37 <td style="white-space: nowrap;">
38 38 <img class="perm-gravatar" src="${h.url('/images/icons/group.png')}"/>${g2p.users_group.users_group_name}
39 39 </td>
40 40 <td>
41 41 <span class="delete_icon action_button" onclick="ajaxActionUsersGroup(${g2p.users_group.users_group_id},'${'id%s'%id(g2p.users_group.users_group_name)}')">
42 42 ${_('revoke')}
43 43 </span>
44 44 </td>
45 45 </tr>
46 46 %endfor
47 47 <tr id="add_perm_input">
48 48 <td>${h.radio('perm_new_member','group.none')}</td>
49 49 <td>${h.radio('perm_new_member','group.read')}</td>
50 50 <td>${h.radio('perm_new_member','group.write')}</td>
51 51 <td>${h.radio('perm_new_member','group.admin')}</td>
52 52 <td class='ac'>
53 53 <div class="perm_ac" id="perm_ac">
54 54 ${h.text('perm_new_member_name',class_='yui-ac-input')}
55 55 ${h.hidden('perm_new_member_type')}
56 56 <div id="perm_container"></div>
57 57 </div>
58 58 </td>
59 59 <td></td>
60 60 </tr>
61 61 <tr>
62 62 <td colspan="6">
63 63 <span id="add_perm" class="add_icon" style="cursor: pointer;">
64 64 ${_('Add another member')}
65 65 </span>
66 66 </td>
67 67 </tr>
68 68 </table>
69 69 <script type="text/javascript">
70 70 function ajaxActionUser(user_id, field_id) {
71 71 var sUrl = "${h.url('delete_repos_group_user_perm',group_name=c.repos_group.group_name)}";
72 72 var callback = {
73 73 success: function (o) {
74 74 var tr = YUD.get(String(field_id));
75 75 tr.parentNode.removeChild(tr);
76 76 },
77 77 failure: function (o) {
78 78 alert("${_('Failed to remove user')}");
79 79 },
80 80 };
81 81 var postData = '_method=delete&user_id=' + user_id;
82 82 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
83 83 };
84 84
85 85 function ajaxActionUsersGroup(users_group_id,field_id){
86 86 var sUrl = "${h.url('delete_repos_group_users_group_perm',group_name=c.repos_group.group_name)}";
87 87 var callback = {
88 88 success:function(o){
89 89 var tr = YUD.get(String(field_id));
90 90 tr.parentNode.removeChild(tr);
91 91 },
92 92 failure:function(o){
93 93 alert("${_('Failed to remove users group')}");
94 94 },
95 95 };
96 96 var postData = '_method=delete&users_group_id='+users_group_id;
97 97 var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
98 98 };
99 99
100 100 YUE.onDOMReady(function () {
101 101 if (!YUD.hasClass('perm_new_member_name', 'error')) {
102 102 YUD.setStyle('add_perm_input', 'display', 'none');
103 103 }
104 104 YAHOO.util.Event.addListener('add_perm', 'click', function () {
105 105 YUD.setStyle('add_perm_input', 'display', '');
106 106 YUD.setStyle('add_perm', 'opacity', '0.6');
107 107 YUD.setStyle('add_perm', 'cursor', 'default');
108 108 });
109 109 MembersAutoComplete(
110 110 ${c.users_array|n},
111 111 ${c.users_groups_array|n},
112 112 "${_('Group')}",
113 113 "${_('members')}"
114 );
114 );
115 115 });
116 116
117 117 </script>
@@ -1,201 +1,201 b''
1 1 <%page args="parent" />
2 2 <div class="box">
3 3 <!-- box / title -->
4 4 <div class="title">
5 5 <h5>
6 6 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/> ${parent.breadcrumbs()} <span id="repo_count">0</span> ${_('repositories')}
7 7 </h5>
8 8 %if c.rhodecode_user.username != 'default':
9 9 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 10 <ul class="links">
11 11 <li>
12 12 %if c.group:
13 13 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository',parent_group=c.group.group_id))}</span>
14 14 %else:
15 15 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
16 %endif
16 %endif
17 17 </li>
18 18 </ul>
19 19 %endif
20 20 %endif
21 21 </div>
22 22 <!-- end box / title -->
23 23 <div class="table">
24 24 % if c.groups:
25 25 <div id='groups_list_wrap' class="yui-skin-sam">
26 26 <table id="groups_list">
27 27 <thead>
28 28 <tr>
29 29 <th class="left"><a href="#">${_('Group name')}</a></th>
30 30 <th class="left"><a href="#">${_('Description')}</a></th>
31 31 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
32 32 </tr>
33 33 </thead>
34 34
35 35 ## REPO GROUPS
36 36 % for gr in c.groups:
37 37 <tr>
38 38 <td>
39 39 <div style="white-space: nowrap">
40 40 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
41 41 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
42 42 </div>
43 43 </td>
44 44 <td>${gr.group_description}</td>
45 45 ## this is commented out since for multi nested repos can be HEAVY!
46 46 ## in number of executed queries during traversing uncomment at will
47 47 ##<td><b>${gr.repositories_recursive_count}</b></td>
48 48 </tr>
49 49 % endfor
50 50
51 51 </table>
52 52 </div>
53 53 <div style="height: 20px"></div>
54 54 % endif
55 55 <div id="welcome" style="display:none;text-align:center">
56 56 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
57 57 </div>
58 58 <div id='repos_list_wrap' class="yui-skin-sam">
59 59 <%cnt=0%>
60 60 <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
61 61
62 62 <table id="repos_list">
63 63 <thead>
64 64 <tr>
65 65 <th class="left"></th>
66 66 <th class="left">${_('Name')}</th>
67 67 <th class="left">${_('Description')}</th>
68 68 <th class="left">${_('Last change')}</th>
69 69 <th class="left">${_('Tip')}</th>
70 70 <th class="left">${_('Owner')}</th>
71 71 <th class="left">${_('RSS')}</th>
72 72 <th class="left">${_('Atom')}</th>
73 73 </tr>
74 74 </thead>
75 75 <tbody>
76 76 %for cnt,repo in enumerate(c.repos_list):
77 77 <tr class="parity${(cnt+1)%2}">
78 78 ##QUICK MENU
79 79 <td class="quick_repo_menu">
80 80 ${dt.quick_menu(repo['name'])}
81 81 </td>
82 82 ##REPO NAME AND ICONS
83 83 <td class="reponame">
84 84 ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'),pageargs.get('short_repo_names'))}
85 85 </td>
86 86 ##DESCRIPTION
87 87 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
88 88 ${h.truncate(repo['description'],60)}</span>
89 89 </td>
90 90 ##LAST CHANGE DATE
91 91 <td>
92 92 <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
93 93 </td>
94 94 ##LAST REVISION
95 95 <td>
96 96 ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
97 97 </td>
98 98 ##
99 99 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
100 100 <td>
101 101 %if c.rhodecode_user.username != 'default':
102 102 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
103 103 %else:
104 104 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
105 105 %endif:
106 106 </td>
107 107 <td>
108 108 %if c.rhodecode_user.username != 'default':
109 109 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
110 110 %else:
111 111 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
112 112 %endif:
113 113 </td>
114 114 </tr>
115 115 %endfor
116 116 </tbody>
117 117 </table>
118 118 </div>
119 119 </div>
120 120 </div>
121 121 <script>
122 122 YUD.get('repo_count').innerHTML = ${cnt};
123 123 var func = function(node){
124 124 return node.parentNode.parentNode.parentNode.parentNode;
125 125 }
126 126
127 127
128 128 // groups table sorting
129 129 var myColumnDefs = [
130 130 {key:"name",label:"${_('Group Name')}",sortable:true,
131 131 sortOptions: { sortFunction: groupNameSort }},
132 132 {key:"desc",label:"${_('Description')}",sortable:true},
133 133 ];
134 134
135 135 var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
136 136
137 137 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
138 138 myDataSource.responseSchema = {
139 139 fields: [
140 140 {key:"name"},
141 141 {key:"desc"},
142 142 ]
143 143 };
144 144
145 145 var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
146 146 {
147 147 sortedBy:{key:"name",dir:"asc"},
148 148 MSG_SORTASC:"${_('Click to sort ascending')}",
149 149 MSG_SORTDESC:"${_('Click to sort descending')}"
150 150 }
151 151 );
152 152
153 153 // main table sorting
154 154 var myColumnDefs = [
155 155 {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
156 156 {key:"name",label:"${_('Name')}",sortable:true,
157 157 sortOptions: { sortFunction: nameSort }},
158 158 {key:"desc",label:"${_('Description')}",sortable:true},
159 159 {key:"last_change",label:"${_('Last Change')}",sortable:true,
160 160 sortOptions: { sortFunction: ageSort }},
161 161 {key:"tip",label:"${_('Tip')}",sortable:true,
162 162 sortOptions: { sortFunction: revisionSort }},
163 163 {key:"owner",label:"${_('Owner')}",sortable:true},
164 164 {key:"rss",label:"",sortable:false},
165 165 {key:"atom",label:"",sortable:false},
166 166 ];
167 167
168 168 var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
169 169
170 170 myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
171 171
172 172 myDataSource.responseSchema = {
173 173 fields: [
174 174 {key:"menu"},
175 175 {key:"name"},
176 176 {key:"desc"},
177 177 {key:"last_change"},
178 178 {key:"tip"},
179 179 {key:"owner"},
180 180 {key:"rss"},
181 181 {key:"atom"},
182 182 ]
183 183 };
184 184
185 185 var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
186 186 {
187 187 sortedBy:{key:"name",dir:"asc"},
188 188 MSG_SORTASC:"${_('Click to sort ascending')}",
189 189 MSG_SORTDESC:"${_('Click to sort descending')}",
190 190 MSG_EMPTY:"${_('No records found.')}",
191 191 MSG_ERROR:"${_('Data error.')}",
192 192 MSG_LOADING:"${_('Loading...')}",
193 193 }
194 194 );
195 195 myDataTable.subscribe('postRenderEvent',function(oArgs) {
196 196 tooltip_activate();
197 197 quick_repo_menu();
198 198 q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
199 199 });
200 200
201 201 </script>
@@ -1,148 +1,148 b''
1 1 from rhodecode.tests import *
2 2 from rhodecode.model.db import ChangesetComment, Notification, User, \
3 3 UserNotification
4 4
5 5
6 6 class TestChangeSetCommentsController(TestController):
7 7
8 8 def setUp(self):
9 9 for x in ChangesetComment.query().all():
10 10 self.Session.delete(x)
11 11 self.Session.commit()
12 12
13 13 for x in Notification.query().all():
14 14 self.Session.delete(x)
15 15 self.Session.commit()
16 16
17 17 def tearDown(self):
18 18 for x in ChangesetComment.query().all():
19 19 self.Session.delete(x)
20 20 self.Session.commit()
21 21
22 22 for x in Notification.query().all():
23 23 self.Session.delete(x)
24 24 self.Session.commit()
25 25
26 26 def test_create(self):
27 27 self.log_user()
28 28 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
29 29 text = u'CommentOnRevision'
30 30
31 31 params = {'text': text}
32 32 response = self.app.post(url(controller='changeset', action='comment',
33 33 repo_name=HG_REPO, revision=rev),
34 34 params=params)
35 35 # Test response...
36 36 self.assertEqual(response.status, '302 Found')
37 37 response.follow()
38 38
39 39 response = self.app.get(url(controller='changeset', action='index',
40 40 repo_name=HG_REPO, revision=rev))
41 41 # test DB
42 42 self.assertEqual(ChangesetComment.query().count(), 1)
43 43 self.assertTrue('''<div class="comments-number">%s '''
44 44 '''comment(s) (0 inline)</div>''' % 1 in response.body)
45 45
46 46 self.assertEqual(Notification.query().count(), 1)
47 47 self.assertEqual(ChangesetComment.query().count(), 1)
48 48
49 49 notification = Notification.query().all()[0]
50 50
51 51 ID = ChangesetComment.query().first().comment_id
52 self.assertEqual(notification.type_,
52 self.assertEqual(notification.type_,
53 53 Notification.TYPE_CHANGESET_COMMENT)
54 54 sbj = (u'/vcs_test_hg/changeset/'
55 55 '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
56 56 print "%s vs %s" % (sbj, notification.subject)
57 57 self.assertTrue(sbj in notification.subject)
58 58
59 59 def test_create_inline(self):
60 60 self.log_user()
61 61 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
62 62 text = u'CommentOnRevision'
63 63 f_path = 'vcs/web/simplevcs/views/repository.py'
64 64 line = 'n1'
65 65
66 66 params = {'text': text, 'f_path': f_path, 'line': line}
67 67 response = self.app.post(url(controller='changeset', action='comment',
68 68 repo_name=HG_REPO, revision=rev),
69 69 params=params)
70 70 # Test response...
71 71 self.assertEqual(response.status, '302 Found')
72 72 response.follow()
73 73
74 74 response = self.app.get(url(controller='changeset', action='index',
75 75 repo_name=HG_REPO, revision=rev))
76 76 #test DB
77 77 self.assertEqual(ChangesetComment.query().count(), 1)
78 78 self.assertTrue('''<div class="comments-number">0 comment(s)'''
79 79 ''' (%s inline)</div>''' % 1 in response.body)
80 80 self.assertTrue('''<div class="inline-comment-placeholder-line"'''
81 81 ''' line="n1" target_id="vcswebsimplevcsviews'''
82 82 '''repositorypy">''' in response.body)
83 83
84 84 self.assertEqual(Notification.query().count(), 1)
85 85 self.assertEqual(ChangesetComment.query().count(), 1)
86 86
87 87 notification = Notification.query().all()[0]
88 88 ID = ChangesetComment.query().first().comment_id
89 self.assertEqual(notification.type_,
89 self.assertEqual(notification.type_,
90 90 Notification.TYPE_CHANGESET_COMMENT)
91 91 sbj = (u'/vcs_test_hg/changeset/'
92 92 '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
93 93 print "%s vs %s" % (sbj, notification.subject)
94 94 self.assertTrue(sbj in notification.subject)
95 95
96 96 def test_create_with_mention(self):
97 97 self.log_user()
98 98
99 99 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
100 100 text = u'@test_regular check CommentOnRevision'
101 101
102 102 params = {'text':text}
103 103 response = self.app.post(url(controller='changeset', action='comment',
104 104 repo_name=HG_REPO, revision=rev),
105 105 params=params)
106 106 # Test response...
107 107 self.assertEqual(response.status, '302 Found')
108 108 response.follow()
109 109
110 110 response = self.app.get(url(controller='changeset', action='index',
111 111 repo_name=HG_REPO, revision=rev))
112 112 # test DB
113 113 self.assertEqual(ChangesetComment.query().count(), 1)
114 114 self.assertTrue('''<div class="comments-number">%s '''
115 115 '''comment(s) (0 inline)</div>''' % 1 in response.body)
116 116
117 117 self.assertEqual(Notification.query().count(), 2)
118 118 users = [x.user.username for x in UserNotification.query().all()]
119 119
120 120 # test_regular get's notification by @mention
121 121 self.assertEqual(sorted(users), [u'test_admin', u'test_regular'])
122 122
123 123 def test_delete(self):
124 124 self.log_user()
125 125 rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
126 126 text = u'CommentOnRevision'
127 127
128 128 params = {'text': text}
129 129 response = self.app.post(url(controller='changeset', action='comment',
130 130 repo_name=HG_REPO, revision=rev),
131 131 params=params)
132 132
133 133 comments = ChangesetComment.query().all()
134 134 self.assertEqual(len(comments), 1)
135 135 comment_id = comments[0].comment_id
136 136
137 137 self.app.delete(url(controller='changeset',
138 138 action='delete_comment',
139 139 repo_name=HG_REPO,
140 140 comment_id=comment_id))
141 141
142 142 comments = ChangesetComment.query().all()
143 143 self.assertEqual(len(comments), 0)
144 144
145 145 response = self.app.get(url(controller='changeset', action='index',
146 146 repo_name=HG_REPO, revision=rev))
147 147 self.assertTrue('''<div class="comments-number">0 comment(s)'''
148 148 ''' (0 inline)</div>''' in response.body)
General Comments 0
You need to be logged in to leave comments. Login now