##// END OF EJS Templates
improved extraction of user from changeset when sending notification....
marcink -
r3185:a665d8cd beta
parent child Browse files
Show More
@@ -1,241 +1,244 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, safe_unicode
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, \
36 36 Notification, PullRequest
37 37 from rhodecode.model.notification import NotificationModel
38 38
39 39 log = logging.getLogger(__name__)
40 40
41 41
42 42 class ChangesetCommentsModel(BaseModel):
43 43
44 44 cls = ChangesetComment
45 45
46 46 def __get_changeset_comment(self, changeset_comment):
47 47 return self._get_instance(ChangesetComment, changeset_comment)
48 48
49 49 def __get_pull_request(self, pull_request):
50 50 return self._get_instance(PullRequest, pull_request)
51 51
52 52 def _extract_mentions(self, s):
53 53 user_objects = []
54 54 for username in extract_mentioned_users(s):
55 55 user_obj = User.get_by_username(username, case_insensitive=True)
56 56 if user_obj:
57 57 user_objects.append(user_obj)
58 58 return user_objects
59 59
60 60 def create(self, text, repo, user, revision=None, pull_request=None,
61 61 f_path=None, line_no=None, status_change=None):
62 62 """
63 63 Creates new comment for changeset or pull request.
64 64 IF status_change is not none this comment is associated with a
65 65 status change of changeset or changesets associated with pull request
66 66
67 67 :param text:
68 68 :param repo:
69 69 :param user:
70 70 :param revision:
71 71 :param pull_request:
72 72 :param f_path:
73 73 :param line_no:
74 74 :param status_change:
75 75 """
76 76 if not text:
77 77 return
78 78
79 79 repo = self._get_repo(repo)
80 80 user = self._get_user(user)
81 81 comment = ChangesetComment()
82 82 comment.repo = repo
83 83 comment.author = user
84 84 comment.text = text
85 85 comment.f_path = f_path
86 86 comment.line_no = line_no
87 87
88 88 if revision:
89 89 cs = repo.scm_instance.get_changeset(revision)
90 90 desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
91 author_email = cs.author_email
92 91 comment.revision = revision
93 92 elif pull_request:
94 93 pull_request = self.__get_pull_request(pull_request)
95 94 comment.pull_request = pull_request
96 95 desc = pull_request.pull_request_id
97 96 else:
98 97 raise Exception('Please specify revision or pull_request_id')
99 98
100 99 self.sa.add(comment)
101 100 self.sa.flush()
102 101
103 102 # make notification
104 103 line = ''
105 104 body = text
106 105
107 106 #changeset
108 107 if revision:
109 108 if line_no:
110 109 line = _('on line %s') % line_no
111 110 subj = safe_unicode(
112 111 h.link_to('Re commit: %(desc)s %(line)s' % \
113 112 {'desc': desc, 'line': line},
114 113 h.url('changeset_home', repo_name=repo.repo_name,
115 114 revision=revision,
116 115 anchor='comment-%s' % comment.comment_id,
117 116 qualified=True,
118 117 )
119 118 )
120 119 )
121 120 notification_type = Notification.TYPE_CHANGESET_COMMENT
122 121 # get the current participants of this changeset
123 122 recipients = ChangesetComment.get_users(revision=revision)
124 123 # add changeset author if it's in rhodecode system
125 recipients += [User.get_by_email(author_email)]
124 cs_author = User.get_from_cs_author(cs.author)
125 if not cs_author:
126 #use repo owner if we cannot extract the author correctly
127 cs_author = repo.user
128 recipients += [cs_author]
126 129 email_kwargs = {
127 130 'status_change': status_change,
128 131 }
129 132 #pull request
130 133 elif pull_request:
131 134 _url = h.url('pullrequest_show',
132 135 repo_name=pull_request.other_repo.repo_name,
133 136 pull_request_id=pull_request.pull_request_id,
134 137 anchor='comment-%s' % comment.comment_id,
135 138 qualified=True,
136 139 )
137 140 subj = safe_unicode(
138 141 h.link_to('Re pull request: %(desc)s %(line)s' % \
139 142 {'desc': desc, 'line': line}, _url)
140 143 )
141 144
142 145 notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
143 146 # get the current participants of this pull request
144 147 recipients = ChangesetComment.get_users(pull_request_id=
145 148 pull_request.pull_request_id)
146 149 # add pull request author
147 150 recipients += [pull_request.author]
148 151
149 152 # add the reviewers to notification
150 153 recipients += [x.user for x in pull_request.reviewers]
151 154
152 155 #set some variables for email notification
153 156 email_kwargs = {
154 157 'pr_id': pull_request.pull_request_id,
155 158 'status_change': status_change,
156 159 'pr_comment_url': _url,
157 160 'pr_comment_user': h.person(user.email),
158 161 'pr_target_repo': h.url('summary_home',
159 162 repo_name=pull_request.other_repo.repo_name,
160 163 qualified=True)
161 164 }
162 165 # create notification objects, and emails
163 166 NotificationModel().create(
164 167 created_by=user, subject=subj, body=body,
165 168 recipients=recipients, type_=notification_type,
166 169 email_kwargs=email_kwargs
167 170 )
168 171
169 172 mention_recipients = set(self._extract_mentions(body))\
170 173 .difference(recipients)
171 174 if mention_recipients:
172 175 email_kwargs.update({'pr_mention': True})
173 176 subj = _('[Mention]') + ' ' + subj
174 177 NotificationModel().create(
175 178 created_by=user, subject=subj, body=body,
176 179 recipients=mention_recipients,
177 180 type_=notification_type,
178 181 email_kwargs=email_kwargs
179 182 )
180 183
181 184 return comment
182 185
183 186 def delete(self, comment):
184 187 """
185 188 Deletes given comment
186 189
187 190 :param comment_id:
188 191 """
189 192 comment = self.__get_changeset_comment(comment)
190 193 self.sa.delete(comment)
191 194
192 195 return comment
193 196
194 197 def get_comments(self, repo_id, revision=None, pull_request=None):
195 198 """
196 199 Get's main comments based on revision or pull_request_id
197 200
198 201 :param repo_id:
199 202 :type repo_id:
200 203 :param revision:
201 204 :type revision:
202 205 :param pull_request:
203 206 :type pull_request:
204 207 """
205 208
206 209 q = ChangesetComment.query()\
207 210 .filter(ChangesetComment.repo_id == repo_id)\
208 211 .filter(ChangesetComment.line_no == None)\
209 212 .filter(ChangesetComment.f_path == None)
210 213 if revision:
211 214 q = q.filter(ChangesetComment.revision == revision)
212 215 elif pull_request:
213 216 pull_request = self.__get_pull_request(pull_request)
214 217 q = q.filter(ChangesetComment.pull_request == pull_request)
215 218 else:
216 219 raise Exception('Please specify revision or pull_request')
217 220 q = q.order_by(ChangesetComment.created_on)
218 221 return q.all()
219 222
220 223 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
221 224 q = self.sa.query(ChangesetComment)\
222 225 .filter(ChangesetComment.repo_id == repo_id)\
223 226 .filter(ChangesetComment.line_no != None)\
224 227 .filter(ChangesetComment.f_path != None)\
225 228 .order_by(ChangesetComment.comment_id.asc())\
226 229
227 230 if revision:
228 231 q = q.filter(ChangesetComment.revision == revision)
229 232 elif pull_request:
230 233 pull_request = self.__get_pull_request(pull_request)
231 234 q = q.filter(ChangesetComment.pull_request == pull_request)
232 235 else:
233 236 raise Exception('Please specify revision or pull_request_id')
234 237
235 238 comments = q.all()
236 239
237 240 paths = defaultdict(lambda: defaultdict(list))
238 241
239 242 for co in comments:
240 243 paths[co.f_path][co.line_no].append(co)
241 244 return paths.items()
@@ -1,1948 +1,1968 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 import hashlib
31 31 import time
32 32 from collections import defaultdict
33 33
34 34 from sqlalchemy import *
35 35 from sqlalchemy.ext.hybrid import hybrid_property
36 36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 37 from sqlalchemy.exc import DatabaseError
38 38 from beaker.cache import cache_region, region_invalidate
39 39 from webob.exc import HTTPNotFound
40 40
41 41 from pylons.i18n.translation import lazy_ugettext as _
42 42
43 43 from rhodecode.lib.vcs import get_backend
44 44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 45 from rhodecode.lib.vcs.exceptions import VCSError
46 46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 47
48 48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 49 safe_unicode, remove_suffix, remove_prefix
50 50 from rhodecode.lib.compat import json
51 51 from rhodecode.lib.caching_query import FromCache
52 52
53 53 from rhodecode.model.meta import Base, Session
54 54
55 55 URL_SEP = '/'
56 56 log = logging.getLogger(__name__)
57 57
58 58 #==============================================================================
59 59 # BASE CLASSES
60 60 #==============================================================================
61 61
62 62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 63
64 64
65 65 class BaseModel(object):
66 66 """
67 67 Base Model for all classess
68 68 """
69 69
70 70 @classmethod
71 71 def _get_keys(cls):
72 72 """return column names for this model """
73 73 return class_mapper(cls).c.keys()
74 74
75 75 def get_dict(self):
76 76 """
77 77 return dict with keys and values corresponding
78 78 to this model data """
79 79
80 80 d = {}
81 81 for k in self._get_keys():
82 82 d[k] = getattr(self, k)
83 83
84 84 # also use __json__() if present to get additional fields
85 85 _json_attr = getattr(self, '__json__', None)
86 86 if _json_attr:
87 87 # update with attributes from __json__
88 88 if callable(_json_attr):
89 89 _json_attr = _json_attr()
90 90 for k, val in _json_attr.iteritems():
91 91 d[k] = val
92 92 return d
93 93
94 94 def get_appstruct(self):
95 95 """return list with keys and values tupples corresponding
96 96 to this model data """
97 97
98 98 l = []
99 99 for k in self._get_keys():
100 100 l.append((k, getattr(self, k),))
101 101 return l
102 102
103 103 def populate_obj(self, populate_dict):
104 104 """populate model with data from given populate_dict"""
105 105
106 106 for k in self._get_keys():
107 107 if k in populate_dict:
108 108 setattr(self, k, populate_dict[k])
109 109
110 110 @classmethod
111 111 def query(cls):
112 112 return Session().query(cls)
113 113
114 114 @classmethod
115 115 def get(cls, id_):
116 116 if id_:
117 117 return cls.query().get(id_)
118 118
119 119 @classmethod
120 120 def get_or_404(cls, id_):
121 121 try:
122 122 id_ = int(id_)
123 123 except (TypeError, ValueError):
124 124 raise HTTPNotFound
125 125
126 126 res = cls.query().get(id_)
127 127 if not res:
128 128 raise HTTPNotFound
129 129 return res
130 130
131 131 @classmethod
132 132 def getAll(cls):
133 133 return cls.query().all()
134 134
135 135 @classmethod
136 136 def delete(cls, id_):
137 137 obj = cls.query().get(id_)
138 138 Session().delete(obj)
139 139
140 140 def __repr__(self):
141 141 if hasattr(self, '__unicode__'):
142 142 # python repr needs to return str
143 143 return safe_str(self.__unicode__())
144 144 return '<DB:%s>' % (self.__class__.__name__)
145 145
146 146
147 147 class RhodeCodeSetting(Base, BaseModel):
148 148 __tablename__ = 'rhodecode_settings'
149 149 __table_args__ = (
150 150 UniqueConstraint('app_settings_name'),
151 151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 152 'mysql_charset': 'utf8'}
153 153 )
154 154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 157
158 158 def __init__(self, k='', v=''):
159 159 self.app_settings_name = k
160 160 self.app_settings_value = v
161 161
162 162 @validates('_app_settings_value')
163 163 def validate_settings_value(self, key, val):
164 164 assert type(val) == unicode
165 165 return val
166 166
167 167 @hybrid_property
168 168 def app_settings_value(self):
169 169 v = self._app_settings_value
170 170 if self.app_settings_name in ["ldap_active",
171 171 "default_repo_enable_statistics",
172 172 "default_repo_enable_locking",
173 173 "default_repo_private",
174 174 "default_repo_enable_downloads"]:
175 175 v = str2bool(v)
176 176 return v
177 177
178 178 @app_settings_value.setter
179 179 def app_settings_value(self, val):
180 180 """
181 181 Setter that will always make sure we use unicode in app_settings_value
182 182
183 183 :param val:
184 184 """
185 185 self._app_settings_value = safe_unicode(val)
186 186
187 187 def __unicode__(self):
188 188 return u"<%s('%s:%s')>" % (
189 189 self.__class__.__name__,
190 190 self.app_settings_name, self.app_settings_value
191 191 )
192 192
193 193 @classmethod
194 194 def get_by_name(cls, key):
195 195 return cls.query()\
196 196 .filter(cls.app_settings_name == key).scalar()
197 197
198 198 @classmethod
199 199 def get_by_name_or_create(cls, key):
200 200 res = cls.get_by_name(key)
201 201 if not res:
202 202 res = cls(key)
203 203 return res
204 204
205 205 @classmethod
206 206 def get_app_settings(cls, cache=False):
207 207
208 208 ret = cls.query()
209 209
210 210 if cache:
211 211 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 212
213 213 if not ret:
214 214 raise Exception('Could not get application settings !')
215 215 settings = {}
216 216 for each in ret:
217 217 settings['rhodecode_' + each.app_settings_name] = \
218 218 each.app_settings_value
219 219
220 220 return settings
221 221
222 222 @classmethod
223 223 def get_ldap_settings(cls, cache=False):
224 224 ret = cls.query()\
225 225 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 226 fd = {}
227 227 for row in ret:
228 228 fd.update({row.app_settings_name: row.app_settings_value})
229 229
230 230 return fd
231 231
232 232 @classmethod
233 233 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 234 ret = cls.query()\
235 235 .filter(cls.app_settings_name.startswith('default_')).all()
236 236 fd = {}
237 237 for row in ret:
238 238 key = row.app_settings_name
239 239 if strip_prefix:
240 240 key = remove_prefix(key, prefix='default_')
241 241 fd.update({key: row.app_settings_value})
242 242
243 243 return fd
244 244
245 245
246 246 class RhodeCodeUi(Base, BaseModel):
247 247 __tablename__ = 'rhodecode_ui'
248 248 __table_args__ = (
249 249 UniqueConstraint('ui_key'),
250 250 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 251 'mysql_charset': 'utf8'}
252 252 )
253 253
254 254 HOOK_UPDATE = 'changegroup.update'
255 255 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 256 HOOK_PUSH = 'changegroup.push_logger'
257 257 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 258 HOOK_PULL = 'outgoing.pull_logger'
259 259 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 260
261 261 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 262 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 263 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 266
267 267 @classmethod
268 268 def get_by_key(cls, key):
269 269 return cls.query().filter(cls.ui_key == key).scalar()
270 270
271 271 @classmethod
272 272 def get_builtin_hooks(cls):
273 273 q = cls.query()
274 274 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 275 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 276 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 277 return q.all()
278 278
279 279 @classmethod
280 280 def get_custom_hooks(cls):
281 281 q = cls.query()
282 282 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 283 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 284 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 285 q = q.filter(cls.ui_section == 'hooks')
286 286 return q.all()
287 287
288 288 @classmethod
289 289 def get_repos_location(cls):
290 290 return cls.get_by_key('/').ui_value
291 291
292 292 @classmethod
293 293 def create_or_update_hook(cls, key, val):
294 294 new_ui = cls.get_by_key(key) or cls()
295 295 new_ui.ui_section = 'hooks'
296 296 new_ui.ui_active = True
297 297 new_ui.ui_key = key
298 298 new_ui.ui_value = val
299 299
300 300 Session().add(new_ui)
301 301
302 302 def __repr__(self):
303 303 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 304 self.ui_value)
305 305
306 306
307 307 class User(Base, BaseModel):
308 308 __tablename__ = 'users'
309 309 __table_args__ = (
310 310 UniqueConstraint('username'), UniqueConstraint('email'),
311 311 Index('u_username_idx', 'username'),
312 312 Index('u_email_idx', 'email'),
313 313 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 314 'mysql_charset': 'utf8'}
315 315 )
316 316 DEFAULT_USER = 'default'
317 317 DEFAULT_PERMISSIONS = [
318 318 'hg.register.manual_activate', 'hg.create.repository',
319 319 'hg.fork.repository', 'repository.read', 'group.read'
320 320 ]
321 321 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 322 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 323 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 324 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 325 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 326 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 327 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 328 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 329 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 330 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 331 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 332 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 333
334 334 user_log = relationship('UserLog')
335 335 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 336
337 337 repositories = relationship('Repository')
338 338 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 339 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 340
341 341 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 342 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 343
344 344 group_member = relationship('UsersGroupMember', cascade='all')
345 345
346 346 notifications = relationship('UserNotification', cascade='all')
347 347 # notifications assigned to this user
348 348 user_created_notifications = relationship('Notification', cascade='all')
349 349 # comments created by this user
350 350 user_comments = relationship('ChangesetComment', cascade='all')
351 351 #extra emails for this user
352 352 user_emails = relationship('UserEmailMap', cascade='all')
353 353
354 354 @hybrid_property
355 355 def email(self):
356 356 return self._email
357 357
358 358 @email.setter
359 359 def email(self, val):
360 360 self._email = val.lower() if val else None
361 361
362 362 @property
363 363 def firstname(self):
364 364 # alias for future
365 365 return self.name
366 366
367 367 @property
368 368 def emails(self):
369 369 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 370 return [self.email] + [x.email for x in other]
371 371
372 372 @property
373 373 def ip_addresses(self):
374 374 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 375 return [x.ip_addr for x in ret]
376 376
377 377 @property
378 378 def username_and_name(self):
379 379 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 380
381 381 @property
382 382 def full_name(self):
383 383 return '%s %s' % (self.firstname, self.lastname)
384 384
385 385 @property
386 386 def full_name_or_username(self):
387 387 return ('%s %s' % (self.firstname, self.lastname)
388 388 if (self.firstname and self.lastname) else self.username)
389 389
390 390 @property
391 391 def full_contact(self):
392 392 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 393
394 394 @property
395 395 def short_contact(self):
396 396 return '%s %s' % (self.firstname, self.lastname)
397 397
398 398 @property
399 399 def is_admin(self):
400 400 return self.admin
401 401
402 402 def __unicode__(self):
403 403 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
404 404 self.user_id, self.username)
405 405
406 406 @classmethod
407 407 def get_by_username(cls, username, case_insensitive=False, cache=False):
408 408 if case_insensitive:
409 409 q = cls.query().filter(cls.username.ilike(username))
410 410 else:
411 411 q = cls.query().filter(cls.username == username)
412 412
413 413 if cache:
414 414 q = q.options(FromCache(
415 415 "sql_cache_short",
416 416 "get_user_%s" % _hash_key(username)
417 417 )
418 418 )
419 419 return q.scalar()
420 420
421 421 @classmethod
422 422 def get_by_api_key(cls, api_key, cache=False):
423 423 q = cls.query().filter(cls.api_key == api_key)
424 424
425 425 if cache:
426 426 q = q.options(FromCache("sql_cache_short",
427 427 "get_api_key_%s" % api_key))
428 428 return q.scalar()
429 429
430 430 @classmethod
431 431 def get_by_email(cls, email, case_insensitive=False, cache=False):
432 432 if case_insensitive:
433 433 q = cls.query().filter(cls.email.ilike(email))
434 434 else:
435 435 q = cls.query().filter(cls.email == email)
436 436
437 437 if cache:
438 438 q = q.options(FromCache("sql_cache_short",
439 439 "get_email_key_%s" % email))
440 440
441 441 ret = q.scalar()
442 442 if ret is None:
443 443 q = UserEmailMap.query()
444 444 # try fetching in alternate email map
445 445 if case_insensitive:
446 446 q = q.filter(UserEmailMap.email.ilike(email))
447 447 else:
448 448 q = q.filter(UserEmailMap.email == email)
449 449 q = q.options(joinedload(UserEmailMap.user))
450 450 if cache:
451 451 q = q.options(FromCache("sql_cache_short",
452 452 "get_email_map_key_%s" % email))
453 453 ret = getattr(q.scalar(), 'user', None)
454 454
455 455 return ret
456 456
457 @classmethod
458 def get_from_cs_author(cls, author):
459 """
460 Tries to get User objects out of commit author string
461
462 :param author:
463 """
464 from rhodecode.lib.helpers import email, author_name
465 # Valid email in the attribute passed, see if they're in the system
466 _email = email(author)
467 if _email:
468 user = cls.get_by_email(_email, case_insensitive=True)
469 if user:
470 return user
471 # Maybe we can match by username?
472 _author = author_name(author)
473 user = cls.get_by_username(_author, case_insensitive=True)
474 if user:
475 return user
476
457 477 def update_lastlogin(self):
458 478 """Update user lastlogin"""
459 479 self.last_login = datetime.datetime.now()
460 480 Session().add(self)
461 481 log.debug('updated user %s lastlogin' % self.username)
462 482
463 483 def get_api_data(self):
464 484 """
465 485 Common function for generating user related data for API
466 486 """
467 487 user = self
468 488 data = dict(
469 489 user_id=user.user_id,
470 490 username=user.username,
471 491 firstname=user.name,
472 492 lastname=user.lastname,
473 493 email=user.email,
474 494 emails=user.emails,
475 495 api_key=user.api_key,
476 496 active=user.active,
477 497 admin=user.admin,
478 498 ldap_dn=user.ldap_dn,
479 499 last_login=user.last_login,
480 500 ip_addresses=user.ip_addresses
481 501 )
482 502 return data
483 503
484 504 def __json__(self):
485 505 data = dict(
486 506 full_name=self.full_name,
487 507 full_name_or_username=self.full_name_or_username,
488 508 short_contact=self.short_contact,
489 509 full_contact=self.full_contact
490 510 )
491 511 data.update(self.get_api_data())
492 512 return data
493 513
494 514
495 515 class UserEmailMap(Base, BaseModel):
496 516 __tablename__ = 'user_email_map'
497 517 __table_args__ = (
498 518 Index('uem_email_idx', 'email'),
499 519 UniqueConstraint('email'),
500 520 {'extend_existing': True, 'mysql_engine': 'InnoDB',
501 521 'mysql_charset': 'utf8'}
502 522 )
503 523 __mapper_args__ = {}
504 524
505 525 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
506 526 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
507 527 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
508 528 user = relationship('User', lazy='joined')
509 529
510 530 @validates('_email')
511 531 def validate_email(self, key, email):
512 532 # check if this email is not main one
513 533 main_email = Session().query(User).filter(User.email == email).scalar()
514 534 if main_email is not None:
515 535 raise AttributeError('email %s is present is user table' % email)
516 536 return email
517 537
518 538 @hybrid_property
519 539 def email(self):
520 540 return self._email
521 541
522 542 @email.setter
523 543 def email(self, val):
524 544 self._email = val.lower() if val else None
525 545
526 546
527 547 class UserIpMap(Base, BaseModel):
528 548 __tablename__ = 'user_ip_map'
529 549 __table_args__ = (
530 550 UniqueConstraint('user_id', 'ip_addr'),
531 551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
532 552 'mysql_charset': 'utf8'}
533 553 )
534 554 __mapper_args__ = {}
535 555
536 556 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
537 557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
538 558 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
539 559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
540 560 user = relationship('User', lazy='joined')
541 561
542 562 @classmethod
543 563 def _get_ip_range(cls, ip_addr):
544 564 from rhodecode.lib import ipaddr
545 565 net = ipaddr.IPv4Network(ip_addr)
546 566 return [str(net.network), str(net.broadcast)]
547 567
548 568 def __json__(self):
549 569 return dict(
550 570 ip_addr=self.ip_addr,
551 571 ip_range=self._get_ip_range(self.ip_addr)
552 572 )
553 573
554 574
555 575 class UserLog(Base, BaseModel):
556 576 __tablename__ = 'user_logs'
557 577 __table_args__ = (
558 578 {'extend_existing': True, 'mysql_engine': 'InnoDB',
559 579 'mysql_charset': 'utf8'},
560 580 )
561 581 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
562 582 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
563 583 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
564 584 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
565 585 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
566 586 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
567 587 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
568 588 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
569 589
570 590 @property
571 591 def action_as_day(self):
572 592 return datetime.date(*self.action_date.timetuple()[:3])
573 593
574 594 user = relationship('User')
575 595 repository = relationship('Repository', cascade='')
576 596
577 597
578 598 class UsersGroup(Base, BaseModel):
579 599 __tablename__ = 'users_groups'
580 600 __table_args__ = (
581 601 {'extend_existing': True, 'mysql_engine': 'InnoDB',
582 602 'mysql_charset': 'utf8'},
583 603 )
584 604
585 605 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
586 606 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
587 607 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
588 608 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
589 609
590 610 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
591 611 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
592 612 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
593 613
594 614 def __unicode__(self):
595 615 return u'<userGroup(%s)>' % (self.users_group_name)
596 616
597 617 @classmethod
598 618 def get_by_group_name(cls, group_name, cache=False,
599 619 case_insensitive=False):
600 620 if case_insensitive:
601 621 q = cls.query().filter(cls.users_group_name.ilike(group_name))
602 622 else:
603 623 q = cls.query().filter(cls.users_group_name == group_name)
604 624 if cache:
605 625 q = q.options(FromCache(
606 626 "sql_cache_short",
607 627 "get_user_%s" % _hash_key(group_name)
608 628 )
609 629 )
610 630 return q.scalar()
611 631
612 632 @classmethod
613 633 def get(cls, users_group_id, cache=False):
614 634 users_group = cls.query()
615 635 if cache:
616 636 users_group = users_group.options(FromCache("sql_cache_short",
617 637 "get_users_group_%s" % users_group_id))
618 638 return users_group.get(users_group_id)
619 639
620 640 def get_api_data(self):
621 641 users_group = self
622 642
623 643 data = dict(
624 644 users_group_id=users_group.users_group_id,
625 645 group_name=users_group.users_group_name,
626 646 active=users_group.users_group_active,
627 647 )
628 648
629 649 return data
630 650
631 651
632 652 class UsersGroupMember(Base, BaseModel):
633 653 __tablename__ = 'users_groups_members'
634 654 __table_args__ = (
635 655 {'extend_existing': True, 'mysql_engine': 'InnoDB',
636 656 'mysql_charset': 'utf8'},
637 657 )
638 658
639 659 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
640 660 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
641 661 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
642 662
643 663 user = relationship('User', lazy='joined')
644 664 users_group = relationship('UsersGroup')
645 665
646 666 def __init__(self, gr_id='', u_id=''):
647 667 self.users_group_id = gr_id
648 668 self.user_id = u_id
649 669
650 670
651 671 class Repository(Base, BaseModel):
652 672 __tablename__ = 'repositories'
653 673 __table_args__ = (
654 674 UniqueConstraint('repo_name'),
655 675 Index('r_repo_name_idx', 'repo_name'),
656 676 {'extend_existing': True, 'mysql_engine': 'InnoDB',
657 677 'mysql_charset': 'utf8'},
658 678 )
659 679
660 680 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
661 681 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
662 682 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
663 683 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
664 684 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
665 685 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
666 686 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
667 687 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
668 688 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
669 689 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
670 690 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
671 691 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
672 692 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
673 693 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
674 694 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
675 695
676 696 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
677 697 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
678 698
679 699 user = relationship('User')
680 700 fork = relationship('Repository', remote_side=repo_id)
681 701 group = relationship('RepoGroup')
682 702 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
683 703 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
684 704 stats = relationship('Statistics', cascade='all', uselist=False)
685 705
686 706 followers = relationship('UserFollowing',
687 707 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
688 708 cascade='all')
689 709
690 710 logs = relationship('UserLog')
691 711 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
692 712
693 713 pull_requests_org = relationship('PullRequest',
694 714 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
695 715 cascade="all, delete, delete-orphan")
696 716
697 717 pull_requests_other = relationship('PullRequest',
698 718 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
699 719 cascade="all, delete, delete-orphan")
700 720
701 721 def __unicode__(self):
702 722 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
703 723 self.repo_name)
704 724
705 725 @hybrid_property
706 726 def locked(self):
707 727 # always should return [user_id, timelocked]
708 728 if self._locked:
709 729 _lock_info = self._locked.split(':')
710 730 return int(_lock_info[0]), _lock_info[1]
711 731 return [None, None]
712 732
713 733 @locked.setter
714 734 def locked(self, val):
715 735 if val and isinstance(val, (list, tuple)):
716 736 self._locked = ':'.join(map(str, val))
717 737 else:
718 738 self._locked = None
719 739
720 740 @hybrid_property
721 741 def changeset_cache(self):
722 742 from rhodecode.lib.vcs.backends.base import EmptyChangeset
723 743 dummy = EmptyChangeset().__json__()
724 744 if not self._changeset_cache:
725 745 return dummy
726 746 try:
727 747 return json.loads(self._changeset_cache)
728 748 except TypeError:
729 749 return dummy
730 750
731 751 @changeset_cache.setter
732 752 def changeset_cache(self, val):
733 753 try:
734 754 self._changeset_cache = json.dumps(val)
735 755 except:
736 756 log.error(traceback.format_exc())
737 757
738 758 @classmethod
739 759 def url_sep(cls):
740 760 return URL_SEP
741 761
742 762 @classmethod
743 763 def normalize_repo_name(cls, repo_name):
744 764 """
745 765 Normalizes os specific repo_name to the format internally stored inside
746 766 dabatabase using URL_SEP
747 767
748 768 :param cls:
749 769 :param repo_name:
750 770 """
751 771 return cls.url_sep().join(repo_name.split(os.sep))
752 772
753 773 @classmethod
754 774 def get_by_repo_name(cls, repo_name):
755 775 q = Session().query(cls).filter(cls.repo_name == repo_name)
756 776 q = q.options(joinedload(Repository.fork))\
757 777 .options(joinedload(Repository.user))\
758 778 .options(joinedload(Repository.group))
759 779 return q.scalar()
760 780
761 781 @classmethod
762 782 def get_by_full_path(cls, repo_full_path):
763 783 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
764 784 repo_name = cls.normalize_repo_name(repo_name)
765 785 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
766 786
767 787 @classmethod
768 788 def get_repo_forks(cls, repo_id):
769 789 return cls.query().filter(Repository.fork_id == repo_id)
770 790
771 791 @classmethod
772 792 def base_path(cls):
773 793 """
774 794 Returns base path when all repos are stored
775 795
776 796 :param cls:
777 797 """
778 798 q = Session().query(RhodeCodeUi)\
779 799 .filter(RhodeCodeUi.ui_key == cls.url_sep())
780 800 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
781 801 return q.one().ui_value
782 802
783 803 @property
784 804 def forks(self):
785 805 """
786 806 Return forks of this repo
787 807 """
788 808 return Repository.get_repo_forks(self.repo_id)
789 809
790 810 @property
791 811 def parent(self):
792 812 """
793 813 Returns fork parent
794 814 """
795 815 return self.fork
796 816
797 817 @property
798 818 def just_name(self):
799 819 return self.repo_name.split(Repository.url_sep())[-1]
800 820
801 821 @property
802 822 def groups_with_parents(self):
803 823 groups = []
804 824 if self.group is None:
805 825 return groups
806 826
807 827 cur_gr = self.group
808 828 groups.insert(0, cur_gr)
809 829 while 1:
810 830 gr = getattr(cur_gr, 'parent_group', None)
811 831 cur_gr = cur_gr.parent_group
812 832 if gr is None:
813 833 break
814 834 groups.insert(0, gr)
815 835
816 836 return groups
817 837
818 838 @property
819 839 def groups_and_repo(self):
820 840 return self.groups_with_parents, self.just_name
821 841
822 842 @LazyProperty
823 843 def repo_path(self):
824 844 """
825 845 Returns base full path for that repository means where it actually
826 846 exists on a filesystem
827 847 """
828 848 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
829 849 Repository.url_sep())
830 850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
831 851 return q.one().ui_value
832 852
833 853 @property
834 854 def repo_full_path(self):
835 855 p = [self.repo_path]
836 856 # we need to split the name by / since this is how we store the
837 857 # names in the database, but that eventually needs to be converted
838 858 # into a valid system path
839 859 p += self.repo_name.split(Repository.url_sep())
840 860 return os.path.join(*p)
841 861
842 862 @property
843 863 def cache_keys(self):
844 864 """
845 865 Returns associated cache keys for that repo
846 866 """
847 867 return CacheInvalidation.query()\
848 868 .filter(CacheInvalidation.cache_args == self.repo_name)\
849 869 .order_by(CacheInvalidation.cache_key)\
850 870 .all()
851 871
852 872 def get_new_name(self, repo_name):
853 873 """
854 874 returns new full repository name based on assigned group and new new
855 875
856 876 :param group_name:
857 877 """
858 878 path_prefix = self.group.full_path_splitted if self.group else []
859 879 return Repository.url_sep().join(path_prefix + [repo_name])
860 880
861 881 @property
862 882 def _ui(self):
863 883 """
864 884 Creates an db based ui object for this repository
865 885 """
866 886 from rhodecode.lib.utils import make_ui
867 887 return make_ui('db', clear_session=False)
868 888
869 889 @classmethod
870 890 def inject_ui(cls, repo, extras={}):
871 891 from rhodecode.lib.vcs.backends.hg import MercurialRepository
872 892 from rhodecode.lib.vcs.backends.git import GitRepository
873 893 required = (MercurialRepository, GitRepository)
874 894 if not isinstance(repo, required):
875 895 raise Exception('repo must be instance of %s' % required)
876 896
877 897 # inject ui extra param to log this action via push logger
878 898 for k, v in extras.items():
879 899 repo._repo.ui.setconfig('rhodecode_extras', k, v)
880 900
881 901 @classmethod
882 902 def is_valid(cls, repo_name):
883 903 """
884 904 returns True if given repo name is a valid filesystem repository
885 905
886 906 :param cls:
887 907 :param repo_name:
888 908 """
889 909 from rhodecode.lib.utils import is_valid_repo
890 910
891 911 return is_valid_repo(repo_name, cls.base_path())
892 912
893 913 def get_api_data(self):
894 914 """
895 915 Common function for generating repo api data
896 916
897 917 """
898 918 repo = self
899 919 data = dict(
900 920 repo_id=repo.repo_id,
901 921 repo_name=repo.repo_name,
902 922 repo_type=repo.repo_type,
903 923 clone_uri=repo.clone_uri,
904 924 private=repo.private,
905 925 created_on=repo.created_on,
906 926 description=repo.description,
907 927 landing_rev=repo.landing_rev,
908 928 owner=repo.user.username,
909 929 fork_of=repo.fork.repo_name if repo.fork else None,
910 930 enable_statistics=repo.enable_statistics,
911 931 enable_locking=repo.enable_locking,
912 932 enable_downloads=repo.enable_downloads,
913 933 last_changeset=repo.changeset_cache
914 934 )
915 935
916 936 return data
917 937
918 938 @classmethod
919 939 def lock(cls, repo, user_id):
920 940 repo.locked = [user_id, time.time()]
921 941 Session().add(repo)
922 942 Session().commit()
923 943
924 944 @classmethod
925 945 def unlock(cls, repo):
926 946 repo.locked = None
927 947 Session().add(repo)
928 948 Session().commit()
929 949
930 950 @property
931 951 def last_db_change(self):
932 952 return self.updated_on
933 953
934 954 def clone_url(self, **override):
935 955 from pylons import url
936 956 from urlparse import urlparse
937 957 import urllib
938 958 parsed_url = urlparse(url('home', qualified=True))
939 959 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
940 960 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
941 961 args = {
942 962 'user': '',
943 963 'pass': '',
944 964 'scheme': parsed_url.scheme,
945 965 'netloc': parsed_url.netloc,
946 966 'prefix': decoded_path,
947 967 'path': self.repo_name
948 968 }
949 969
950 970 args.update(override)
951 971 return default_clone_uri % args
952 972
953 973 #==========================================================================
954 974 # SCM PROPERTIES
955 975 #==========================================================================
956 976
957 977 def get_changeset(self, rev=None):
958 978 return get_changeset_safe(self.scm_instance, rev)
959 979
960 980 def get_landing_changeset(self):
961 981 """
962 982 Returns landing changeset, or if that doesn't exist returns the tip
963 983 """
964 984 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
965 985 return cs
966 986
967 987 def update_changeset_cache(self, cs_cache=None):
968 988 """
969 989 Update cache of last changeset for repository, keys should be::
970 990
971 991 short_id
972 992 raw_id
973 993 revision
974 994 message
975 995 date
976 996 author
977 997
978 998 :param cs_cache:
979 999 """
980 1000 from rhodecode.lib.vcs.backends.base import BaseChangeset
981 1001 if cs_cache is None:
982 1002 cs_cache = self.get_changeset()
983 1003 if isinstance(cs_cache, BaseChangeset):
984 1004 cs_cache = cs_cache.__json__()
985 1005
986 1006 if cs_cache != self.changeset_cache:
987 1007 last_change = cs_cache.get('date') or self.last_change
988 1008 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
989 1009 self.updated_on = last_change
990 1010 self.changeset_cache = cs_cache
991 1011 Session().add(self)
992 1012 Session().commit()
993 1013
994 1014 @property
995 1015 def tip(self):
996 1016 return self.get_changeset('tip')
997 1017
998 1018 @property
999 1019 def author(self):
1000 1020 return self.tip.author
1001 1021
1002 1022 @property
1003 1023 def last_change(self):
1004 1024 return self.scm_instance.last_change
1005 1025
1006 1026 def get_comments(self, revisions=None):
1007 1027 """
1008 1028 Returns comments for this repository grouped by revisions
1009 1029
1010 1030 :param revisions: filter query by revisions only
1011 1031 """
1012 1032 cmts = ChangesetComment.query()\
1013 1033 .filter(ChangesetComment.repo == self)
1014 1034 if revisions:
1015 1035 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1016 1036 grouped = defaultdict(list)
1017 1037 for cmt in cmts.all():
1018 1038 grouped[cmt.revision].append(cmt)
1019 1039 return grouped
1020 1040
1021 1041 def statuses(self, revisions=None):
1022 1042 """
1023 1043 Returns statuses for this repository
1024 1044
1025 1045 :param revisions: list of revisions to get statuses for
1026 1046 :type revisions: list
1027 1047 """
1028 1048
1029 1049 statuses = ChangesetStatus.query()\
1030 1050 .filter(ChangesetStatus.repo == self)\
1031 1051 .filter(ChangesetStatus.version == 0)
1032 1052 if revisions:
1033 1053 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1034 1054 grouped = {}
1035 1055
1036 1056 #maybe we have open new pullrequest without a status ?
1037 1057 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1038 1058 status_lbl = ChangesetStatus.get_status_lbl(stat)
1039 1059 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1040 1060 for rev in pr.revisions:
1041 1061 pr_id = pr.pull_request_id
1042 1062 pr_repo = pr.other_repo.repo_name
1043 1063 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1044 1064
1045 1065 for stat in statuses.all():
1046 1066 pr_id = pr_repo = None
1047 1067 if stat.pull_request:
1048 1068 pr_id = stat.pull_request.pull_request_id
1049 1069 pr_repo = stat.pull_request.other_repo.repo_name
1050 1070 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1051 1071 pr_id, pr_repo]
1052 1072 return grouped
1053 1073
1054 1074 #==========================================================================
1055 1075 # SCM CACHE INSTANCE
1056 1076 #==========================================================================
1057 1077
1058 1078 @property
1059 1079 def invalidate(self):
1060 1080 return CacheInvalidation.invalidate(self.repo_name)
1061 1081
1062 1082 def set_invalidate(self):
1063 1083 """
1064 1084 set a cache for invalidation for this instance
1065 1085 """
1066 1086 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1067 1087
1068 1088 @LazyProperty
1069 1089 def scm_instance(self):
1070 1090 import rhodecode
1071 1091 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1072 1092 if full_cache:
1073 1093 return self.scm_instance_cached()
1074 1094 return self.__get_instance()
1075 1095
1076 1096 def scm_instance_cached(self, cache_map=None):
1077 1097 @cache_region('long_term')
1078 1098 def _c(repo_name):
1079 1099 return self.__get_instance()
1080 1100 rn = self.repo_name
1081 1101 log.debug('Getting cached instance of repo')
1082 1102
1083 1103 if cache_map:
1084 1104 # get using prefilled cache_map
1085 1105 invalidate_repo = cache_map[self.repo_name]
1086 1106 if invalidate_repo:
1087 1107 invalidate_repo = (None if invalidate_repo.cache_active
1088 1108 else invalidate_repo)
1089 1109 else:
1090 1110 # get from invalidate
1091 1111 invalidate_repo = self.invalidate
1092 1112
1093 1113 if invalidate_repo is not None:
1094 1114 region_invalidate(_c, None, rn)
1095 1115 # update our cache
1096 1116 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1097 1117 return _c(rn)
1098 1118
1099 1119 def __get_instance(self):
1100 1120 repo_full_path = self.repo_full_path
1101 1121 try:
1102 1122 alias = get_scm(repo_full_path)[0]
1103 1123 log.debug('Creating instance of %s repository' % alias)
1104 1124 backend = get_backend(alias)
1105 1125 except VCSError:
1106 1126 log.error(traceback.format_exc())
1107 1127 log.error('Perhaps this repository is in db and not in '
1108 1128 'filesystem run rescan repositories with '
1109 1129 '"destroy old data " option from admin panel')
1110 1130 return
1111 1131
1112 1132 if alias == 'hg':
1113 1133
1114 1134 repo = backend(safe_str(repo_full_path), create=False,
1115 1135 baseui=self._ui)
1116 1136 # skip hidden web repository
1117 1137 if repo._get_hidden():
1118 1138 return
1119 1139 else:
1120 1140 repo = backend(repo_full_path, create=False)
1121 1141
1122 1142 return repo
1123 1143
1124 1144
1125 1145 class RepoGroup(Base, BaseModel):
1126 1146 __tablename__ = 'groups'
1127 1147 __table_args__ = (
1128 1148 UniqueConstraint('group_name', 'group_parent_id'),
1129 1149 CheckConstraint('group_id != group_parent_id'),
1130 1150 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1131 1151 'mysql_charset': 'utf8'},
1132 1152 )
1133 1153 __mapper_args__ = {'order_by': 'group_name'}
1134 1154
1135 1155 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1136 1156 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1137 1157 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1138 1158 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1139 1159 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1140 1160
1141 1161 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1142 1162 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1143 1163
1144 1164 parent_group = relationship('RepoGroup', remote_side=group_id)
1145 1165
1146 1166 def __init__(self, group_name='', parent_group=None):
1147 1167 self.group_name = group_name
1148 1168 self.parent_group = parent_group
1149 1169
1150 1170 def __unicode__(self):
1151 1171 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1152 1172 self.group_name)
1153 1173
1154 1174 @classmethod
1155 1175 def groups_choices(cls, check_perms=False):
1156 1176 from webhelpers.html import literal as _literal
1157 1177 from rhodecode.model.scm import ScmModel
1158 1178 groups = cls.query().all()
1159 1179 if check_perms:
1160 1180 #filter group user have access to, it's done
1161 1181 #magically inside ScmModel based on current user
1162 1182 groups = ScmModel().get_repos_groups(groups)
1163 1183 repo_groups = [('', '')]
1164 1184 sep = ' &raquo; '
1165 1185 _name = lambda k: _literal(sep.join(k))
1166 1186
1167 1187 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1168 1188 for x in groups])
1169 1189
1170 1190 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1171 1191 return repo_groups
1172 1192
1173 1193 @classmethod
1174 1194 def url_sep(cls):
1175 1195 return URL_SEP
1176 1196
1177 1197 @classmethod
1178 1198 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1179 1199 if case_insensitive:
1180 1200 gr = cls.query()\
1181 1201 .filter(cls.group_name.ilike(group_name))
1182 1202 else:
1183 1203 gr = cls.query()\
1184 1204 .filter(cls.group_name == group_name)
1185 1205 if cache:
1186 1206 gr = gr.options(FromCache(
1187 1207 "sql_cache_short",
1188 1208 "get_group_%s" % _hash_key(group_name)
1189 1209 )
1190 1210 )
1191 1211 return gr.scalar()
1192 1212
1193 1213 @property
1194 1214 def parents(self):
1195 1215 parents_recursion_limit = 5
1196 1216 groups = []
1197 1217 if self.parent_group is None:
1198 1218 return groups
1199 1219 cur_gr = self.parent_group
1200 1220 groups.insert(0, cur_gr)
1201 1221 cnt = 0
1202 1222 while 1:
1203 1223 cnt += 1
1204 1224 gr = getattr(cur_gr, 'parent_group', None)
1205 1225 cur_gr = cur_gr.parent_group
1206 1226 if gr is None:
1207 1227 break
1208 1228 if cnt == parents_recursion_limit:
1209 1229 # this will prevent accidental infinit loops
1210 1230 log.error('group nested more than %s' %
1211 1231 parents_recursion_limit)
1212 1232 break
1213 1233
1214 1234 groups.insert(0, gr)
1215 1235 return groups
1216 1236
1217 1237 @property
1218 1238 def children(self):
1219 1239 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1220 1240
1221 1241 @property
1222 1242 def name(self):
1223 1243 return self.group_name.split(RepoGroup.url_sep())[-1]
1224 1244
1225 1245 @property
1226 1246 def full_path(self):
1227 1247 return self.group_name
1228 1248
1229 1249 @property
1230 1250 def full_path_splitted(self):
1231 1251 return self.group_name.split(RepoGroup.url_sep())
1232 1252
1233 1253 @property
1234 1254 def repositories(self):
1235 1255 return Repository.query()\
1236 1256 .filter(Repository.group == self)\
1237 1257 .order_by(Repository.repo_name)
1238 1258
1239 1259 @property
1240 1260 def repositories_recursive_count(self):
1241 1261 cnt = self.repositories.count()
1242 1262
1243 1263 def children_count(group):
1244 1264 cnt = 0
1245 1265 for child in group.children:
1246 1266 cnt += child.repositories.count()
1247 1267 cnt += children_count(child)
1248 1268 return cnt
1249 1269
1250 1270 return cnt + children_count(self)
1251 1271
1252 1272 def recursive_groups_and_repos(self):
1253 1273 """
1254 1274 Recursive return all groups, with repositories in those groups
1255 1275 """
1256 1276 all_ = []
1257 1277
1258 1278 def _get_members(root_gr):
1259 1279 for r in root_gr.repositories:
1260 1280 all_.append(r)
1261 1281 childs = root_gr.children.all()
1262 1282 if childs:
1263 1283 for gr in childs:
1264 1284 all_.append(gr)
1265 1285 _get_members(gr)
1266 1286
1267 1287 _get_members(self)
1268 1288 return [self] + all_
1269 1289
1270 1290 def get_new_name(self, group_name):
1271 1291 """
1272 1292 returns new full group name based on parent and new name
1273 1293
1274 1294 :param group_name:
1275 1295 """
1276 1296 path_prefix = (self.parent_group.full_path_splitted if
1277 1297 self.parent_group else [])
1278 1298 return RepoGroup.url_sep().join(path_prefix + [group_name])
1279 1299
1280 1300
1281 1301 class Permission(Base, BaseModel):
1282 1302 __tablename__ = 'permissions'
1283 1303 __table_args__ = (
1284 1304 Index('p_perm_name_idx', 'permission_name'),
1285 1305 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1286 1306 'mysql_charset': 'utf8'},
1287 1307 )
1288 1308 PERMS = [
1289 1309 ('repository.none', _('Repository no access')),
1290 1310 ('repository.read', _('Repository read access')),
1291 1311 ('repository.write', _('Repository write access')),
1292 1312 ('repository.admin', _('Repository admin access')),
1293 1313
1294 1314 ('group.none', _('Repositories Group no access')),
1295 1315 ('group.read', _('Repositories Group read access')),
1296 1316 ('group.write', _('Repositories Group write access')),
1297 1317 ('group.admin', _('Repositories Group admin access')),
1298 1318
1299 1319 ('hg.admin', _('RhodeCode Administrator')),
1300 1320 ('hg.create.none', _('Repository creation disabled')),
1301 1321 ('hg.create.repository', _('Repository creation enabled')),
1302 1322 ('hg.fork.none', _('Repository forking disabled')),
1303 1323 ('hg.fork.repository', _('Repository forking enabled')),
1304 1324 ('hg.register.none', _('Register disabled')),
1305 1325 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1306 1326 'with manual activation')),
1307 1327
1308 1328 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1309 1329 'with auto activation')),
1310 1330 ]
1311 1331
1312 1332 # defines which permissions are more important higher the more important
1313 1333 PERM_WEIGHTS = {
1314 1334 'repository.none': 0,
1315 1335 'repository.read': 1,
1316 1336 'repository.write': 3,
1317 1337 'repository.admin': 4,
1318 1338
1319 1339 'group.none': 0,
1320 1340 'group.read': 1,
1321 1341 'group.write': 3,
1322 1342 'group.admin': 4,
1323 1343
1324 1344 'hg.fork.none': 0,
1325 1345 'hg.fork.repository': 1,
1326 1346 'hg.create.none': 0,
1327 1347 'hg.create.repository':1
1328 1348 }
1329 1349
1330 1350 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1331 1351 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1332 1352 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1333 1353
1334 1354 def __unicode__(self):
1335 1355 return u"<%s('%s:%s')>" % (
1336 1356 self.__class__.__name__, self.permission_id, self.permission_name
1337 1357 )
1338 1358
1339 1359 @classmethod
1340 1360 def get_by_key(cls, key):
1341 1361 return cls.query().filter(cls.permission_name == key).scalar()
1342 1362
1343 1363 @classmethod
1344 1364 def get_default_perms(cls, default_user_id):
1345 1365 q = Session().query(UserRepoToPerm, Repository, cls)\
1346 1366 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1347 1367 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1348 1368 .filter(UserRepoToPerm.user_id == default_user_id)
1349 1369
1350 1370 return q.all()
1351 1371
1352 1372 @classmethod
1353 1373 def get_default_group_perms(cls, default_user_id):
1354 1374 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1355 1375 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1356 1376 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1357 1377 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1358 1378
1359 1379 return q.all()
1360 1380
1361 1381
1362 1382 class UserRepoToPerm(Base, BaseModel):
1363 1383 __tablename__ = 'repo_to_perm'
1364 1384 __table_args__ = (
1365 1385 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1366 1386 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1367 1387 'mysql_charset': 'utf8'}
1368 1388 )
1369 1389 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1370 1390 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1371 1391 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1372 1392 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1373 1393
1374 1394 user = relationship('User')
1375 1395 repository = relationship('Repository')
1376 1396 permission = relationship('Permission')
1377 1397
1378 1398 @classmethod
1379 1399 def create(cls, user, repository, permission):
1380 1400 n = cls()
1381 1401 n.user = user
1382 1402 n.repository = repository
1383 1403 n.permission = permission
1384 1404 Session().add(n)
1385 1405 return n
1386 1406
1387 1407 def __unicode__(self):
1388 1408 return u'<user:%s => %s >' % (self.user, self.repository)
1389 1409
1390 1410
1391 1411 class UserToPerm(Base, BaseModel):
1392 1412 __tablename__ = 'user_to_perm'
1393 1413 __table_args__ = (
1394 1414 UniqueConstraint('user_id', 'permission_id'),
1395 1415 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1396 1416 'mysql_charset': 'utf8'}
1397 1417 )
1398 1418 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1399 1419 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1400 1420 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1401 1421
1402 1422 user = relationship('User')
1403 1423 permission = relationship('Permission', lazy='joined')
1404 1424
1405 1425
1406 1426 class UsersGroupRepoToPerm(Base, BaseModel):
1407 1427 __tablename__ = 'users_group_repo_to_perm'
1408 1428 __table_args__ = (
1409 1429 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1410 1430 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1411 1431 'mysql_charset': 'utf8'}
1412 1432 )
1413 1433 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1414 1434 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1415 1435 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1416 1436 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1417 1437
1418 1438 users_group = relationship('UsersGroup')
1419 1439 permission = relationship('Permission')
1420 1440 repository = relationship('Repository')
1421 1441
1422 1442 @classmethod
1423 1443 def create(cls, users_group, repository, permission):
1424 1444 n = cls()
1425 1445 n.users_group = users_group
1426 1446 n.repository = repository
1427 1447 n.permission = permission
1428 1448 Session().add(n)
1429 1449 return n
1430 1450
1431 1451 def __unicode__(self):
1432 1452 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1433 1453
1434 1454
1435 1455 class UsersGroupToPerm(Base, BaseModel):
1436 1456 __tablename__ = 'users_group_to_perm'
1437 1457 __table_args__ = (
1438 1458 UniqueConstraint('users_group_id', 'permission_id',),
1439 1459 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1440 1460 'mysql_charset': 'utf8'}
1441 1461 )
1442 1462 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1443 1463 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1444 1464 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1445 1465
1446 1466 users_group = relationship('UsersGroup')
1447 1467 permission = relationship('Permission')
1448 1468
1449 1469
1450 1470 class UserRepoGroupToPerm(Base, BaseModel):
1451 1471 __tablename__ = 'user_repo_group_to_perm'
1452 1472 __table_args__ = (
1453 1473 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1454 1474 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1455 1475 'mysql_charset': 'utf8'}
1456 1476 )
1457 1477
1458 1478 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1459 1479 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1460 1480 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1461 1481 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1462 1482
1463 1483 user = relationship('User')
1464 1484 group = relationship('RepoGroup')
1465 1485 permission = relationship('Permission')
1466 1486
1467 1487
1468 1488 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1469 1489 __tablename__ = 'users_group_repo_group_to_perm'
1470 1490 __table_args__ = (
1471 1491 UniqueConstraint('users_group_id', 'group_id'),
1472 1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1473 1493 'mysql_charset': 'utf8'}
1474 1494 )
1475 1495
1476 1496 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)
1477 1497 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1478 1498 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1479 1499 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1480 1500
1481 1501 users_group = relationship('UsersGroup')
1482 1502 permission = relationship('Permission')
1483 1503 group = relationship('RepoGroup')
1484 1504
1485 1505
1486 1506 class Statistics(Base, BaseModel):
1487 1507 __tablename__ = 'statistics'
1488 1508 __table_args__ = (
1489 1509 UniqueConstraint('repository_id'),
1490 1510 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1491 1511 'mysql_charset': 'utf8'}
1492 1512 )
1493 1513 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1494 1514 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1495 1515 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1496 1516 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1497 1517 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1498 1518 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1499 1519
1500 1520 repository = relationship('Repository', single_parent=True)
1501 1521
1502 1522
1503 1523 class UserFollowing(Base, BaseModel):
1504 1524 __tablename__ = 'user_followings'
1505 1525 __table_args__ = (
1506 1526 UniqueConstraint('user_id', 'follows_repository_id'),
1507 1527 UniqueConstraint('user_id', 'follows_user_id'),
1508 1528 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1509 1529 'mysql_charset': 'utf8'}
1510 1530 )
1511 1531
1512 1532 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1513 1533 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1514 1534 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1515 1535 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1516 1536 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1517 1537
1518 1538 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1519 1539
1520 1540 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1521 1541 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1522 1542
1523 1543 @classmethod
1524 1544 def get_repo_followers(cls, repo_id):
1525 1545 return cls.query().filter(cls.follows_repo_id == repo_id)
1526 1546
1527 1547
1528 1548 class CacheInvalidation(Base, BaseModel):
1529 1549 __tablename__ = 'cache_invalidation'
1530 1550 __table_args__ = (
1531 1551 UniqueConstraint('cache_key'),
1532 1552 Index('key_idx', 'cache_key'),
1533 1553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1534 1554 'mysql_charset': 'utf8'},
1535 1555 )
1536 1556 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1537 1557 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1538 1558 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1539 1559 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1540 1560
1541 1561 def __init__(self, cache_key, cache_args=''):
1542 1562 self.cache_key = cache_key
1543 1563 self.cache_args = cache_args
1544 1564 self.cache_active = False
1545 1565
1546 1566 def __unicode__(self):
1547 1567 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1548 1568 self.cache_id, self.cache_key)
1549 1569
1550 1570 @property
1551 1571 def prefix(self):
1552 1572 _split = self.cache_key.split(self.cache_args, 1)
1553 1573 if _split and len(_split) == 2:
1554 1574 return _split[0]
1555 1575 return ''
1556 1576
1557 1577 @classmethod
1558 1578 def clear_cache(cls):
1559 1579 cls.query().delete()
1560 1580
1561 1581 @classmethod
1562 1582 def _get_key(cls, key):
1563 1583 """
1564 1584 Wrapper for generating a key, together with a prefix
1565 1585
1566 1586 :param key:
1567 1587 """
1568 1588 import rhodecode
1569 1589 prefix = ''
1570 1590 org_key = key
1571 1591 iid = rhodecode.CONFIG.get('instance_id')
1572 1592 if iid:
1573 1593 prefix = iid
1574 1594
1575 1595 return "%s%s" % (prefix, key), prefix, org_key
1576 1596
1577 1597 @classmethod
1578 1598 def get_by_key(cls, key):
1579 1599 return cls.query().filter(cls.cache_key == key).scalar()
1580 1600
1581 1601 @classmethod
1582 1602 def get_by_repo_name(cls, repo_name):
1583 1603 return cls.query().filter(cls.cache_args == repo_name).all()
1584 1604
1585 1605 @classmethod
1586 1606 def _get_or_create_key(cls, key, repo_name, commit=True):
1587 1607 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1588 1608 if not inv_obj:
1589 1609 try:
1590 1610 inv_obj = CacheInvalidation(key, repo_name)
1591 1611 Session().add(inv_obj)
1592 1612 if commit:
1593 1613 Session().commit()
1594 1614 except Exception:
1595 1615 log.error(traceback.format_exc())
1596 1616 Session().rollback()
1597 1617 return inv_obj
1598 1618
1599 1619 @classmethod
1600 1620 def invalidate(cls, key):
1601 1621 """
1602 1622 Returns Invalidation object if this given key should be invalidated
1603 1623 None otherwise. `cache_active = False` means that this cache
1604 1624 state is not valid and needs to be invalidated
1605 1625
1606 1626 :param key:
1607 1627 """
1608 1628 repo_name = key
1609 1629 repo_name = remove_suffix(repo_name, '_README')
1610 1630 repo_name = remove_suffix(repo_name, '_RSS')
1611 1631 repo_name = remove_suffix(repo_name, '_ATOM')
1612 1632
1613 1633 # adds instance prefix
1614 1634 key, _prefix, _org_key = cls._get_key(key)
1615 1635 inv = cls._get_or_create_key(key, repo_name)
1616 1636
1617 1637 if inv and inv.cache_active is False:
1618 1638 return inv
1619 1639
1620 1640 @classmethod
1621 1641 def set_invalidate(cls, key=None, repo_name=None):
1622 1642 """
1623 1643 Mark this Cache key for invalidation, either by key or whole
1624 1644 cache sets based on repo_name
1625 1645
1626 1646 :param key:
1627 1647 """
1628 1648 if key:
1629 1649 key, _prefix, _org_key = cls._get_key(key)
1630 1650 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1631 1651 elif repo_name:
1632 1652 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1633 1653
1634 1654 log.debug('marking %s key[s] for invalidation based on key=%s,repo_name=%s'
1635 1655 % (len(inv_objs), key, repo_name))
1636 1656 try:
1637 1657 for inv_obj in inv_objs:
1638 1658 inv_obj.cache_active = False
1639 1659 Session().add(inv_obj)
1640 1660 Session().commit()
1641 1661 except Exception:
1642 1662 log.error(traceback.format_exc())
1643 1663 Session().rollback()
1644 1664
1645 1665 @classmethod
1646 1666 def set_valid(cls, key):
1647 1667 """
1648 1668 Mark this cache key as active and currently cached
1649 1669
1650 1670 :param key:
1651 1671 """
1652 1672 inv_obj = cls.get_by_key(key)
1653 1673 inv_obj.cache_active = True
1654 1674 Session().add(inv_obj)
1655 1675 Session().commit()
1656 1676
1657 1677 @classmethod
1658 1678 def get_cache_map(cls):
1659 1679
1660 1680 class cachemapdict(dict):
1661 1681
1662 1682 def __init__(self, *args, **kwargs):
1663 1683 fixkey = kwargs.get('fixkey')
1664 1684 if fixkey:
1665 1685 del kwargs['fixkey']
1666 1686 self.fixkey = fixkey
1667 1687 super(cachemapdict, self).__init__(*args, **kwargs)
1668 1688
1669 1689 def __getattr__(self, name):
1670 1690 key = name
1671 1691 if self.fixkey:
1672 1692 key, _prefix, _org_key = cls._get_key(key)
1673 1693 if key in self.__dict__:
1674 1694 return self.__dict__[key]
1675 1695 else:
1676 1696 return self[key]
1677 1697
1678 1698 def __getitem__(self, key):
1679 1699 if self.fixkey:
1680 1700 key, _prefix, _org_key = cls._get_key(key)
1681 1701 try:
1682 1702 return super(cachemapdict, self).__getitem__(key)
1683 1703 except KeyError:
1684 1704 return
1685 1705
1686 1706 cache_map = cachemapdict(fixkey=True)
1687 1707 for obj in cls.query().all():
1688 1708 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1689 1709 return cache_map
1690 1710
1691 1711
1692 1712 class ChangesetComment(Base, BaseModel):
1693 1713 __tablename__ = 'changeset_comments'
1694 1714 __table_args__ = (
1695 1715 Index('cc_revision_idx', 'revision'),
1696 1716 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1697 1717 'mysql_charset': 'utf8'},
1698 1718 )
1699 1719 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1700 1720 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1701 1721 revision = Column('revision', String(40), nullable=True)
1702 1722 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1703 1723 line_no = Column('line_no', Unicode(10), nullable=True)
1704 1724 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1705 1725 f_path = Column('f_path', Unicode(1000), nullable=True)
1706 1726 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1707 1727 text = Column('text', UnicodeText(25000), nullable=False)
1708 1728 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1709 1729 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1710 1730
1711 1731 author = relationship('User', lazy='joined')
1712 1732 repo = relationship('Repository')
1713 1733 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1714 1734 pull_request = relationship('PullRequest', lazy='joined')
1715 1735
1716 1736 @classmethod
1717 1737 def get_users(cls, revision=None, pull_request_id=None):
1718 1738 """
1719 1739 Returns user associated with this ChangesetComment. ie those
1720 1740 who actually commented
1721 1741
1722 1742 :param cls:
1723 1743 :param revision:
1724 1744 """
1725 1745 q = Session().query(User)\
1726 1746 .join(ChangesetComment.author)
1727 1747 if revision:
1728 1748 q = q.filter(cls.revision == revision)
1729 1749 elif pull_request_id:
1730 1750 q = q.filter(cls.pull_request_id == pull_request_id)
1731 1751 return q.all()
1732 1752
1733 1753
1734 1754 class ChangesetStatus(Base, BaseModel):
1735 1755 __tablename__ = 'changeset_statuses'
1736 1756 __table_args__ = (
1737 1757 Index('cs_revision_idx', 'revision'),
1738 1758 Index('cs_version_idx', 'version'),
1739 1759 UniqueConstraint('repo_id', 'revision', 'version'),
1740 1760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1741 1761 'mysql_charset': 'utf8'}
1742 1762 )
1743 1763 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1744 1764 STATUS_APPROVED = 'approved'
1745 1765 STATUS_REJECTED = 'rejected'
1746 1766 STATUS_UNDER_REVIEW = 'under_review'
1747 1767
1748 1768 STATUSES = [
1749 1769 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1750 1770 (STATUS_APPROVED, _("Approved")),
1751 1771 (STATUS_REJECTED, _("Rejected")),
1752 1772 (STATUS_UNDER_REVIEW, _("Under Review")),
1753 1773 ]
1754 1774
1755 1775 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1756 1776 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1757 1777 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1758 1778 revision = Column('revision', String(40), nullable=False)
1759 1779 status = Column('status', String(128), nullable=False, default=DEFAULT)
1760 1780 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1761 1781 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1762 1782 version = Column('version', Integer(), nullable=False, default=0)
1763 1783 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1764 1784
1765 1785 author = relationship('User', lazy='joined')
1766 1786 repo = relationship('Repository')
1767 1787 comment = relationship('ChangesetComment', lazy='joined')
1768 1788 pull_request = relationship('PullRequest', lazy='joined')
1769 1789
1770 1790 def __unicode__(self):
1771 1791 return u"<%s('%s:%s')>" % (
1772 1792 self.__class__.__name__,
1773 1793 self.status, self.author
1774 1794 )
1775 1795
1776 1796 @classmethod
1777 1797 def get_status_lbl(cls, value):
1778 1798 return dict(cls.STATUSES).get(value)
1779 1799
1780 1800 @property
1781 1801 def status_lbl(self):
1782 1802 return ChangesetStatus.get_status_lbl(self.status)
1783 1803
1784 1804
1785 1805 class PullRequest(Base, BaseModel):
1786 1806 __tablename__ = 'pull_requests'
1787 1807 __table_args__ = (
1788 1808 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1789 1809 'mysql_charset': 'utf8'},
1790 1810 )
1791 1811
1792 1812 STATUS_NEW = u'new'
1793 1813 STATUS_OPEN = u'open'
1794 1814 STATUS_CLOSED = u'closed'
1795 1815
1796 1816 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1797 1817 title = Column('title', Unicode(256), nullable=True)
1798 1818 description = Column('description', UnicodeText(10240), nullable=True)
1799 1819 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1800 1820 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1801 1821 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1802 1822 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1803 1823 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1804 1824 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1805 1825 org_ref = Column('org_ref', Unicode(256), nullable=False)
1806 1826 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1807 1827 other_ref = Column('other_ref', Unicode(256), nullable=False)
1808 1828
1809 1829 @hybrid_property
1810 1830 def revisions(self):
1811 1831 return self._revisions.split(':')
1812 1832
1813 1833 @revisions.setter
1814 1834 def revisions(self, val):
1815 1835 self._revisions = ':'.join(val)
1816 1836
1817 1837 @property
1818 1838 def org_ref_parts(self):
1819 1839 return self.org_ref.split(':')
1820 1840
1821 1841 @property
1822 1842 def other_ref_parts(self):
1823 1843 return self.other_ref.split(':')
1824 1844
1825 1845 author = relationship('User', lazy='joined')
1826 1846 reviewers = relationship('PullRequestReviewers',
1827 1847 cascade="all, delete, delete-orphan")
1828 1848 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1829 1849 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1830 1850 statuses = relationship('ChangesetStatus')
1831 1851 comments = relationship('ChangesetComment',
1832 1852 cascade="all, delete, delete-orphan")
1833 1853
1834 1854 def is_closed(self):
1835 1855 return self.status == self.STATUS_CLOSED
1836 1856
1837 1857 def __json__(self):
1838 1858 return dict(
1839 1859 revisions=self.revisions
1840 1860 )
1841 1861
1842 1862
1843 1863 class PullRequestReviewers(Base, BaseModel):
1844 1864 __tablename__ = 'pull_request_reviewers'
1845 1865 __table_args__ = (
1846 1866 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1847 1867 'mysql_charset': 'utf8'},
1848 1868 )
1849 1869
1850 1870 def __init__(self, user=None, pull_request=None):
1851 1871 self.user = user
1852 1872 self.pull_request = pull_request
1853 1873
1854 1874 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1855 1875 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1856 1876 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1857 1877
1858 1878 user = relationship('User')
1859 1879 pull_request = relationship('PullRequest')
1860 1880
1861 1881
1862 1882 class Notification(Base, BaseModel):
1863 1883 __tablename__ = 'notifications'
1864 1884 __table_args__ = (
1865 1885 Index('notification_type_idx', 'type'),
1866 1886 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1867 1887 'mysql_charset': 'utf8'},
1868 1888 )
1869 1889
1870 1890 TYPE_CHANGESET_COMMENT = u'cs_comment'
1871 1891 TYPE_MESSAGE = u'message'
1872 1892 TYPE_MENTION = u'mention'
1873 1893 TYPE_REGISTRATION = u'registration'
1874 1894 TYPE_PULL_REQUEST = u'pull_request'
1875 1895 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1876 1896
1877 1897 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1878 1898 subject = Column('subject', Unicode(512), nullable=True)
1879 1899 body = Column('body', UnicodeText(50000), nullable=True)
1880 1900 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1881 1901 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1882 1902 type_ = Column('type', Unicode(256))
1883 1903
1884 1904 created_by_user = relationship('User')
1885 1905 notifications_to_users = relationship('UserNotification', lazy='joined',
1886 1906 cascade="all, delete, delete-orphan")
1887 1907
1888 1908 @property
1889 1909 def recipients(self):
1890 1910 return [x.user for x in UserNotification.query()\
1891 1911 .filter(UserNotification.notification == self)\
1892 1912 .order_by(UserNotification.user_id.asc()).all()]
1893 1913
1894 1914 @classmethod
1895 1915 def create(cls, created_by, subject, body, recipients, type_=None):
1896 1916 if type_ is None:
1897 1917 type_ = Notification.TYPE_MESSAGE
1898 1918
1899 1919 notification = cls()
1900 1920 notification.created_by_user = created_by
1901 1921 notification.subject = subject
1902 1922 notification.body = body
1903 1923 notification.type_ = type_
1904 1924 notification.created_on = datetime.datetime.now()
1905 1925
1906 1926 for u in recipients:
1907 1927 assoc = UserNotification()
1908 1928 assoc.notification = notification
1909 1929 u.notifications.append(assoc)
1910 1930 Session().add(notification)
1911 1931 return notification
1912 1932
1913 1933 @property
1914 1934 def description(self):
1915 1935 from rhodecode.model.notification import NotificationModel
1916 1936 return NotificationModel().make_description(self)
1917 1937
1918 1938
1919 1939 class UserNotification(Base, BaseModel):
1920 1940 __tablename__ = 'user_to_notification'
1921 1941 __table_args__ = (
1922 1942 UniqueConstraint('user_id', 'notification_id'),
1923 1943 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1924 1944 'mysql_charset': 'utf8'}
1925 1945 )
1926 1946 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1927 1947 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1928 1948 read = Column('read', Boolean, default=False)
1929 1949 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1930 1950
1931 1951 user = relationship('User', lazy="joined")
1932 1952 notification = relationship('Notification', lazy="joined",
1933 1953 order_by=lambda: Notification.created_on.desc(),)
1934 1954
1935 1955 def mark_as_read(self):
1936 1956 self.read = True
1937 1957 Session().add(self)
1938 1958
1939 1959
1940 1960 class DbMigrateVersion(Base, BaseModel):
1941 1961 __tablename__ = 'db_migrate_version'
1942 1962 __table_args__ = (
1943 1963 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1944 1964 'mysql_charset': 'utf8'},
1945 1965 )
1946 1966 repository_id = Column('repository_id', String(250), primary_key=True)
1947 1967 repository_path = Column('repository_path', Text)
1948 1968 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now