##// END OF EJS Templates
fixed caching query on repos path...
marcink -
r1727:8e9f5109 beta
parent child Browse files
Show More
@@ -1,68 +1,69 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.home
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Home controller for Rhodecode
7 7
8 8 :created_on: Feb 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 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
28 28 from pylons import tmpl_context as c, request
29 29 from paste.httpexceptions import HTTPBadRequest
30 30
31 31 from rhodecode.lib.auth import LoginRequired
32 32 from rhodecode.lib.base import BaseController, render
33 33 from rhodecode.model.db import RepoGroup, Repository
34 34
35 35 log = logging.getLogger(__name__)
36 36
37 37
38 38 class HomeController(BaseController):
39 39
40 40 @LoginRequired()
41 41 def __before__(self):
42 42 super(HomeController, self).__before__()
43 43
44 44 def index(self):
45 45
46 46 c.repos_list = self.scm_model.get_repos()
47 47
48 c.groups = RepoGroup.query().filter(RepoGroup.group_parent_id == None).all()
48 c.groups = RepoGroup.query()\
49 .filter(RepoGroup.group_parent_id == None).all()
49 50
50 51 return render('/index.html')
51 52
52 53 def repo_switcher(self):
53 54 if request.is_xhr:
54 55 all_repos = Repository.query().order_by(Repository.repo_name).all()
55 56 c.repos_list = self.scm_model.get_repos(all_repos,
56 57 sort_key='name_sort')
57 58 return render('/repo_switcher_list.html')
58 59 else:
59 60 return HTTPBadRequest()
60 61
61 62 def branch_tag_switcher(self, repo_name):
62 63 if request.is_xhr:
63 64 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
64 65 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
65 66 return render('/switch_to_list.html')
66 67 else:
67 68 return HTTPBadRequest()
68 69
@@ -1,289 +1,299 b''
1 1 """caching_query.py
2 2
3 3 Represent persistence structures which allow the usage of
4 4 Beaker caching with SQLAlchemy.
5 5
6 6 The three new concepts introduced here are:
7 7
8 8 * CachingQuery - a Query subclass that caches and
9 9 retrieves results in/from Beaker.
10 10 * FromCache - a query option that establishes caching
11 11 parameters on a Query
12 12 * RelationshipCache - a variant of FromCache which is specific
13 13 to a query invoked during a lazy load.
14 14 * _params_from_query - extracts value parameters from
15 15 a Query.
16 16
17 17 The rest of what's here are standard SQLAlchemy and
18 18 Beaker constructs.
19 19
20 20 """
21 21 import beaker
22 22 from beaker.exceptions import BeakerException
23 23
24 24 from sqlalchemy.orm.interfaces import MapperOption
25 25 from sqlalchemy.orm.query import Query
26 26 from sqlalchemy.sql import visitors
27 27
28 28
29 29 class CachingQuery(Query):
30 30 """A Query subclass which optionally loads full results from a Beaker
31 31 cache region.
32 32
33 33 The CachingQuery stores additional state that allows it to consult
34 34 a Beaker cache before accessing the database:
35 35
36 36 * A "region", which is a cache region argument passed to a
37 37 Beaker CacheManager, specifies a particular cache configuration
38 38 (including backend implementation, expiration times, etc.)
39 39 * A "namespace", which is a qualifying name that identifies a
40 40 group of keys within the cache. A query that filters on a name
41 41 might use the name "by_name", a query that filters on a date range
42 42 to a joined table might use the name "related_date_range".
43 43
44 44 When the above state is present, a Beaker cache is retrieved.
45 45
46 46 The "namespace" name is first concatenated with
47 47 a string composed of the individual entities and columns the Query
48 48 requests, i.e. such as ``Query(User.id, User.name)``.
49 49
50 50 The Beaker cache is then loaded from the cache manager based
51 51 on the region and composed namespace. The key within the cache
52 52 itself is then constructed against the bind parameters specified
53 53 by this query, which are usually literals defined in the
54 54 WHERE clause.
55 55
56 56 The FromCache and RelationshipCache mapper options below represent
57 57 the "public" method of configuring this state upon the CachingQuery.
58 58
59 59 """
60 60
61 61 def __init__(self, manager, *args, **kw):
62 62 self.cache_manager = manager
63 63 Query.__init__(self, *args, **kw)
64 64
65 65 def __iter__(self):
66 66 """override __iter__ to pull results from Beaker
67 67 if particular attributes have been configured.
68 68
69 69 Note that this approach does *not* detach the loaded objects from
70 70 the current session. If the cache backend is an in-process cache
71 71 (like "memory") and lives beyond the scope of the current session's
72 72 transaction, those objects may be expired. The method here can be
73 73 modified to first expunge() each loaded item from the current
74 74 session before returning the list of items, so that the items
75 75 in the cache are not the same ones in the current Session.
76 76
77 77 """
78 78 if hasattr(self, '_cache_parameters'):
79 79 return self.get_value(createfunc=lambda:
80 80 list(Query.__iter__(self)))
81 81 else:
82 82 return Query.__iter__(self)
83 83
84 84 def invalidate(self):
85 85 """Invalidate the value represented by this Query."""
86 86
87 87 cache, cache_key = _get_cache_parameters(self)
88 88 cache.remove(cache_key)
89 89
90 90 def get_value(self, merge=True, createfunc=None):
91 91 """Return the value from the cache for this query.
92 92
93 93 Raise KeyError if no value present and no
94 94 createfunc specified.
95 95
96 96 """
97 97 cache, cache_key = _get_cache_parameters(self)
98 98 ret = cache.get_value(cache_key, createfunc=createfunc)
99 99 if merge:
100 100 ret = self.merge_result(ret, load=False)
101 101 return ret
102 102
103 103 def set_value(self, value):
104 104 """Set the value in the cache for this query."""
105 105
106 106 cache, cache_key = _get_cache_parameters(self)
107 107 cache.put(cache_key, value)
108 108
109 109
110 110 def query_callable(manager, query_cls=CachingQuery):
111 111 def query(*arg, **kw):
112 112 return query_cls(manager, *arg, **kw)
113 113 return query
114 114
115 115
116 116 def get_cache_region(name, region):
117 117 if region not in beaker.cache.cache_regions:
118 118 raise BeakerException('Cache region `%s` not configured '
119 119 'Check if proper cache settings are in the .ini files' % region)
120 120 kw = beaker.cache.cache_regions[region]
121 121 return beaker.cache.Cache._get_cache(name, kw)
122 122
123 123
124 124 def _get_cache_parameters(query):
125 125 """For a query with cache_region and cache_namespace configured,
126 126 return the correspoinding Cache instance and cache key, based
127 127 on this query's current criterion and parameter values.
128 128
129 129 """
130 130 if not hasattr(query, '_cache_parameters'):
131 131 raise ValueError("This Query does not have caching "
132 132 "parameters configured.")
133 133
134 134 region, namespace, cache_key = query._cache_parameters
135 135
136 136 namespace = _namespace_from_query(namespace, query)
137 137
138 138 if cache_key is None:
139 139 # cache key - the value arguments from this query's parameters.
140 args = _params_from_query(query)
141 cache_key = " ".join([str(x) for x in args])
140 args = [str(x) for x in _params_from_query(query)]
141 args.extend(filter(lambda k:k not in ['None', None, u'None'],
142 [str(query._limit), str(query._offset)]))
143 cache_key = " ".join(args)
144
145 if cache_key is None:
146 raise Exception('Cache key cannot be None')
142 147
143 148 # get cache
144 149 #cache = query.cache_manager.get_cache_region(namespace, region)
145 150 cache = get_cache_region(namespace, region)
146 151 # optional - hash the cache_key too for consistent length
147 152 # import uuid
148 153 # cache_key= str(uuid.uuid5(uuid.NAMESPACE_DNS, cache_key))
149 154
150 155 return cache, cache_key
151 156
152 157
153 158 def _namespace_from_query(namespace, query):
154 159 # cache namespace - the token handed in by the
155 160 # option + class we're querying against
156 161 namespace = " ".join([namespace] + [str(x) for x in query._entities])
157 162
158 163 # memcached wants this
159 164 namespace = namespace.replace(' ', '_')
160 165
161 166 return namespace
162 167
163 168
164 169 def _set_cache_parameters(query, region, namespace, cache_key):
165 170
166 171 if hasattr(query, '_cache_parameters'):
167 172 region, namespace, cache_key = query._cache_parameters
168 173 raise ValueError("This query is already configured "
169 174 "for region %r namespace %r" %
170 175 (region, namespace)
171 176 )
172 177 query._cache_parameters = region, namespace, cache_key
173 178
174 179
175 180 class FromCache(MapperOption):
176 181 """Specifies that a Query should load results from a cache."""
177 182
178 183 propagate_to_loaders = False
179 184
180 185 def __init__(self, region, namespace, cache_key=None):
181 186 """Construct a new FromCache.
182 187
183 188 :param region: the cache region. Should be a
184 189 region configured in the Beaker CacheManager.
185 190
186 191 :param namespace: the cache namespace. Should
187 192 be a name uniquely describing the target Query's
188 193 lexical structure.
189 194
190 195 :param cache_key: optional. A string cache key
191 196 that will serve as the key to the query. Use this
192 197 if your query has a huge amount of parameters (such
193 198 as when using in_()) which correspond more simply to
194 199 some other identifier.
195 200
196 201 """
197 202 self.region = region
198 203 self.namespace = namespace
199 204 self.cache_key = cache_key
200 205
201 206 def process_query(self, query):
202 207 """Process a Query during normal loading operation."""
203 208
204 209 _set_cache_parameters(query, self.region, self.namespace,
205 210 self.cache_key)
206 211
207 212
208 213 class RelationshipCache(MapperOption):
209 214 """Specifies that a Query as called within a "lazy load"
210 215 should load results from a cache."""
211 216
212 217 propagate_to_loaders = True
213 218
214 219 def __init__(self, region, namespace, attribute):
215 220 """Construct a new RelationshipCache.
216 221
217 222 :param region: the cache region. Should be a
218 223 region configured in the Beaker CacheManager.
219 224
220 225 :param namespace: the cache namespace. Should
221 226 be a name uniquely describing the target Query's
222 227 lexical structure.
223 228
224 229 :param attribute: A Class.attribute which
225 230 indicates a particular class relationship() whose
226 231 lazy loader should be pulled from the cache.
227 232
228 233 """
229 234 self.region = region
230 235 self.namespace = namespace
231 236 self._relationship_options = {
232 237 (attribute.property.parent.class_, attribute.property.key): self
233 238 }
234 239
235 240 def process_query_conditionally(self, query):
236 241 """Process a Query that is used within a lazy loader.
237 242
238 243 (the process_query_conditionally() method is a SQLAlchemy
239 244 hook invoked only within lazyload.)
240 245
241 246 """
242 247 if query._current_path:
243 248 mapper, key = query._current_path[-2:]
244 249
245 250 for cls in mapper.class_.__mro__:
246 251 if (cls, key) in self._relationship_options:
247 252 relationship_option = \
248 253 self._relationship_options[(cls, key)]
249 254 _set_cache_parameters(
250 255 query,
251 256 relationship_option.region,
252 257 relationship_option.namespace,
253 258 None)
254 259
255 260 def and_(self, option):
256 261 """Chain another RelationshipCache option to this one.
257 262
258 263 While many RelationshipCache objects can be specified on a single
259 264 Query separately, chaining them together allows for a more efficient
260 265 lookup during load.
261 266
262 267 """
263 268 self._relationship_options.update(option._relationship_options)
264 269 return self
265 270
266 271
267 272 def _params_from_query(query):
268 273 """Pull the bind parameter values from a query.
269 274
270 275 This takes into account any scalar attribute bindparam set up.
271 276
272 277 E.g. params_from_query(query.filter(Cls.foo==5).filter(Cls.bar==7)))
273 278 would return [5, 7].
274 279
275 280 """
276 281 v = []
277 282 def visit_bindparam(bind):
278 value = query._params.get(bind.key, bind.value)
279 283
280 # lazyloader may dig a callable in here, intended
281 # to late-evaluate params after autoflush is called.
282 # convert to a scalar value.
283 if callable(value):
284 value = value()
284 if bind.key in query._params:
285 value = query._params[bind.key]
286 elif bind.callable:
287 # lazyloader may dig a callable in here, intended
288 # to late-evaluate params after autoflush is called.
289 # convert to a scalar value.
290 value = bind.callable()
291 else:
292 value = bind.value
285 293
286 294 v.append(value)
287 295 if query._criterion is not None:
288 296 visitors.traverse(query._criterion, {}, {'bindparam':visit_bindparam})
297 for f in query._from_obj:
298 visitors.traverse(f, {}, {'bindparam':visit_bindparam})
289 299 return v
@@ -1,1206 +1,1206 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) 2009-2011 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
31 31 from sqlalchemy import *
32 32 from sqlalchemy.ext.hybrid import hybrid_property
33 33 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 34 from beaker.cache import cache_region, region_invalidate
35 35
36 36 from vcs import get_backend
37 37 from vcs.utils.helpers import get_scm
38 38 from vcs.exceptions import VCSError
39 39 from vcs.utils.lazy import LazyProperty
40 40
41 41 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, safe_unicode
42 42 from rhodecode.lib.exceptions import UsersGroupsAssignedException
43 43 from rhodecode.lib.compat import json
44 44 from rhodecode.lib.caching_query import FromCache
45 45
46 46 from rhodecode.model.meta import Base, Session
47 47
48 48 log = logging.getLogger(__name__)
49 49
50 50 #==============================================================================
51 51 # BASE CLASSES
52 52 #==============================================================================
53 53
54 54 class ModelSerializer(json.JSONEncoder):
55 55 """
56 56 Simple Serializer for JSON,
57 57
58 58 usage::
59 59
60 60 to make object customized for serialization implement a __json__
61 61 method that will return a dict for serialization into json
62 62
63 63 example::
64 64
65 65 class Task(object):
66 66
67 67 def __init__(self, name, value):
68 68 self.name = name
69 69 self.value = value
70 70
71 71 def __json__(self):
72 72 return dict(name=self.name,
73 73 value=self.value)
74 74
75 75 """
76 76
77 77 def default(self, obj):
78 78
79 79 if hasattr(obj, '__json__'):
80 80 return obj.__json__()
81 81 else:
82 82 return json.JSONEncoder.default(self, obj)
83 83
84 84 class BaseModel(object):
85 85 """Base Model for all classess
86 86
87 87 """
88 88
89 89 @classmethod
90 90 def _get_keys(cls):
91 91 """return column names for this model """
92 92 return class_mapper(cls).c.keys()
93 93
94 94 def get_dict(self):
95 95 """return dict with keys and values corresponding
96 96 to this model data """
97 97
98 98 d = {}
99 99 for k in self._get_keys():
100 100 d[k] = getattr(self, k)
101 101 return d
102 102
103 103 def get_appstruct(self):
104 104 """return list with keys and values tupples corresponding
105 105 to this model data """
106 106
107 107 l = []
108 108 for k in self._get_keys():
109 109 l.append((k, getattr(self, k),))
110 110 return l
111 111
112 112 def populate_obj(self, populate_dict):
113 113 """populate model with data from given populate_dict"""
114 114
115 115 for k in self._get_keys():
116 116 if k in populate_dict:
117 117 setattr(self, k, populate_dict[k])
118 118
119 119 @classmethod
120 120 def query(cls):
121 121 return Session().query(cls)
122 122
123 123 @classmethod
124 124 def get(cls, id_):
125 125 if id_:
126 126 return cls.query().get(id_)
127 127
128 128 @classmethod
129 129 def getAll(cls):
130 130 return cls.query().all()
131 131
132 132 @classmethod
133 133 def delete(cls, id_):
134 134 obj = cls.query().get(id_)
135 135 Session().delete(obj)
136 136
137 137
138 138 class RhodeCodeSetting(Base, BaseModel):
139 139 __tablename__ = 'rhodecode_settings'
140 140 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
141 141 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 142 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 143 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144 144
145 145 def __init__(self, k='', v=''):
146 146 self.app_settings_name = k
147 147 self.app_settings_value = v
148 148
149 149
150 150 @validates('_app_settings_value')
151 151 def validate_settings_value(self, key, val):
152 152 assert type(val) == unicode
153 153 return val
154 154
155 155 @hybrid_property
156 156 def app_settings_value(self):
157 157 v = self._app_settings_value
158 158 if v == 'ldap_active':
159 159 v = str2bool(v)
160 160 return v
161 161
162 162 @app_settings_value.setter
163 163 def app_settings_value(self, val):
164 164 """
165 165 Setter that will always make sure we use unicode in app_settings_value
166 166
167 167 :param val:
168 168 """
169 169 self._app_settings_value = safe_unicode(val)
170 170
171 171 def __repr__(self):
172 172 return "<%s('%s:%s')>" % (self.__class__.__name__,
173 173 self.app_settings_name, self.app_settings_value)
174 174
175 175
176 176 @classmethod
177 177 def get_by_name(cls, ldap_key):
178 178 return cls.query()\
179 179 .filter(cls.app_settings_name == ldap_key).scalar()
180 180
181 181 @classmethod
182 182 def get_app_settings(cls, cache=False):
183 183
184 184 ret = cls.query()
185 185
186 186 if cache:
187 187 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
188 188
189 189 if not ret:
190 190 raise Exception('Could not get application settings !')
191 191 settings = {}
192 192 for each in ret:
193 193 settings['rhodecode_' + each.app_settings_name] = \
194 194 each.app_settings_value
195 195
196 196 return settings
197 197
198 198 @classmethod
199 199 def get_ldap_settings(cls, cache=False):
200 200 ret = cls.query()\
201 201 .filter(cls.app_settings_name.startswith('ldap_')).all()
202 202 fd = {}
203 203 for row in ret:
204 204 fd.update({row.app_settings_name:row.app_settings_value})
205 205
206 206 return fd
207 207
208 208
209 209 class RhodeCodeUi(Base, BaseModel):
210 210 __tablename__ = 'rhodecode_ui'
211 211 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
212 212
213 213 HOOK_UPDATE = 'changegroup.update'
214 214 HOOK_REPO_SIZE = 'changegroup.repo_size'
215 215 HOOK_PUSH = 'pretxnchangegroup.push_logger'
216 216 HOOK_PULL = 'preoutgoing.pull_logger'
217 217
218 218 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
219 219 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
220 220 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
221 221 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
222 222 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
223 223
224 224
225 225 @classmethod
226 226 def get_by_key(cls, key):
227 227 return cls.query().filter(cls.ui_key == key)
228 228
229 229
230 230 @classmethod
231 231 def get_builtin_hooks(cls):
232 232 q = cls.query()
233 233 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
234 234 cls.HOOK_REPO_SIZE,
235 235 cls.HOOK_PUSH, cls.HOOK_PULL]))
236 236 return q.all()
237 237
238 238 @classmethod
239 239 def get_custom_hooks(cls):
240 240 q = cls.query()
241 241 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
242 242 cls.HOOK_REPO_SIZE,
243 243 cls.HOOK_PUSH, cls.HOOK_PULL]))
244 244 q = q.filter(cls.ui_section == 'hooks')
245 245 return q.all()
246 246
247 247 @classmethod
248 248 def create_or_update_hook(cls, key, val):
249 249 new_ui = cls.get_by_key(key).scalar() or cls()
250 250 new_ui.ui_section = 'hooks'
251 251 new_ui.ui_active = True
252 252 new_ui.ui_key = key
253 253 new_ui.ui_value = val
254 254
255 255 Session().add(new_ui)
256 256 Session().commit()
257 257
258 258
259 259 class User(Base, BaseModel):
260 260 __tablename__ = 'users'
261 261 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
262 262 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 263 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 264 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 265 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
266 266 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
267 267 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
268 268 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
269 269 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 270 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
271 271 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 272 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
273 273
274 274 user_log = relationship('UserLog', cascade='all')
275 275 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
276 276
277 277 repositories = relationship('Repository')
278 278 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
279 279 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
280 280
281 281 group_member = relationship('UsersGroupMember', cascade='all')
282 282
283 283 notifications = relationship('UserNotification',)
284 284
285 285 @property
286 286 def full_contact(self):
287 287 return '%s %s <%s>' % (self.name, self.lastname, self.email)
288 288
289 289 @property
290 290 def short_contact(self):
291 291 return '%s %s' % (self.name, self.lastname)
292 292
293 293 @property
294 294 def is_admin(self):
295 295 return self.admin
296 296
297 297 def __repr__(self):
298 298 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
299 299 self.user_id, self.username)
300 300
301 301
302 302 @classmethod
303 303 def get_by_username(cls, username, case_insensitive=False, cache=False):
304 304 if case_insensitive:
305 305 q = cls.query().filter(cls.username.ilike(username))
306 306 else:
307 307 q = cls.query().filter(cls.username == username)
308 308
309 309 if cache:
310 310 q = q.options(FromCache("sql_cache_short",
311 311 "get_user_%s" % username))
312 312 return q.scalar()
313 313
314 314 @classmethod
315 315 def get_by_api_key(cls, api_key, cache=False):
316 316 q = cls.query().filter(cls.api_key == api_key)
317 317
318 318 if cache:
319 319 q = q.options(FromCache("sql_cache_short",
320 320 "get_api_key_%s" % api_key))
321 321 return q.scalar()
322 322
323 323 @classmethod
324 324 def get_by_email(cls, email, cache=False):
325 325 q = cls.query().filter(cls.email == email)
326 326
327 327 if cache:
328 328 q = q.options(FromCache("sql_cache_short",
329 329 "get_api_key_%s" % email))
330 330 return q.scalar()
331 331
332 332 def update_lastlogin(self):
333 333 """Update user lastlogin"""
334 334
335 335 self.last_login = datetime.datetime.now()
336 336 Session().add(self)
337 337 Session().commit()
338 338 log.debug('updated user %s lastlogin', self.username)
339 339
340 340
341 341 class UserLog(Base, BaseModel):
342 342 __tablename__ = 'user_logs'
343 343 __table_args__ = {'extend_existing':True}
344 344 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
345 345 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
346 346 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
347 347 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
348 348 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
349 349 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
350 350 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
351 351
352 352 @property
353 353 def action_as_day(self):
354 354 return datetime.date(*self.action_date.timetuple()[:3])
355 355
356 356 user = relationship('User')
357 357 repository = relationship('Repository')
358 358
359 359
360 360 class UsersGroup(Base, BaseModel):
361 361 __tablename__ = 'users_groups'
362 362 __table_args__ = {'extend_existing':True}
363 363
364 364 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
365 365 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
366 366 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
367 367
368 368 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
369 369
370 370 def __repr__(self):
371 371 return '<userGroup(%s)>' % (self.users_group_name)
372 372
373 373 @classmethod
374 374 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
375 375 if case_insensitive:
376 376 gr = cls.query()\
377 377 .filter(cls.users_group_name.ilike(group_name))
378 378 else:
379 379 gr = cls.query()\
380 380 .filter(cls.users_group_name == group_name)
381 381 if cache:
382 382 gr = gr.options(FromCache("sql_cache_short",
383 383 "get_user_%s" % group_name))
384 384 return gr.scalar()
385 385
386 386
387 387 @classmethod
388 388 def get(cls, users_group_id, cache=False):
389 389 users_group = cls.query()
390 390 if cache:
391 391 users_group = users_group.options(FromCache("sql_cache_short",
392 392 "get_users_group_%s" % users_group_id))
393 393 return users_group.get(users_group_id)
394 394
395 395 @classmethod
396 396 def create(cls, form_data):
397 397 try:
398 398 new_users_group = cls()
399 399 for k, v in form_data.items():
400 400 setattr(new_users_group, k, v)
401 401
402 402 Session().add(new_users_group)
403 403 Session().commit()
404 404 return new_users_group
405 405 except:
406 406 log.error(traceback.format_exc())
407 407 Session().rollback()
408 408 raise
409 409
410 410 @classmethod
411 411 def update(cls, users_group_id, form_data):
412 412
413 413 try:
414 414 users_group = cls.get(users_group_id, cache=False)
415 415
416 416 for k, v in form_data.items():
417 417 if k == 'users_group_members':
418 418 users_group.members = []
419 419 Session().flush()
420 420 members_list = []
421 421 if v:
422 422 v = [v] if isinstance(v, basestring) else v
423 423 for u_id in set(v):
424 424 member = UsersGroupMember(users_group_id, u_id)
425 425 members_list.append(member)
426 426 setattr(users_group, 'members', members_list)
427 427 setattr(users_group, k, v)
428 428
429 429 Session().add(users_group)
430 430 Session().commit()
431 431 except:
432 432 log.error(traceback.format_exc())
433 433 Session().rollback()
434 434 raise
435 435
436 436 @classmethod
437 437 def delete(cls, users_group_id):
438 438 try:
439 439
440 440 # check if this group is not assigned to repo
441 441 assigned_groups = UsersGroupRepoToPerm.query()\
442 442 .filter(UsersGroupRepoToPerm.users_group_id ==
443 443 users_group_id).all()
444 444
445 445 if assigned_groups:
446 446 raise UsersGroupsAssignedException('RepoGroup assigned to %s' %
447 447 assigned_groups)
448 448
449 449 users_group = cls.get(users_group_id, cache=False)
450 450 Session().delete(users_group)
451 451 Session().commit()
452 452 except:
453 453 log.error(traceback.format_exc())
454 454 Session().rollback()
455 455 raise
456 456
457 457 class UsersGroupMember(Base, BaseModel):
458 458 __tablename__ = 'users_groups_members'
459 459 __table_args__ = {'extend_existing':True}
460 460
461 461 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
462 462 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
463 463 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
464 464
465 465 user = relationship('User', lazy='joined')
466 466 users_group = relationship('UsersGroup')
467 467
468 468 def __init__(self, gr_id='', u_id=''):
469 469 self.users_group_id = gr_id
470 470 self.user_id = u_id
471 471
472 472 @staticmethod
473 473 def add_user_to_group(group, user):
474 474 ugm = UsersGroupMember()
475 475 ugm.users_group = group
476 476 ugm.user = user
477 477 Session().add(ugm)
478 478 Session().commit()
479 479 return ugm
480 480
481 481 class Repository(Base, BaseModel):
482 482 __tablename__ = 'repositories'
483 483 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
484 484
485 485 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
486 486 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
487 487 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
488 488 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
489 489 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
490 490 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
491 491 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
492 492 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
493 493 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
494 494 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
495 495
496 496 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
497 497 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
498 498
499 499
500 500 user = relationship('User')
501 501 fork = relationship('Repository', remote_side=repo_id)
502 502 group = relationship('RepoGroup')
503 503 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
504 504 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
505 505 stats = relationship('Statistics', cascade='all', uselist=False)
506 506
507 507 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
508 508
509 509 logs = relationship('UserLog', cascade='all')
510 510
511 511 def __repr__(self):
512 512 return "<%s('%s:%s')>" % (self.__class__.__name__,
513 513 self.repo_id, self.repo_name)
514 514
515 515 @classmethod
516 516 def url_sep(cls):
517 517 return '/'
518 518
519 519 @classmethod
520 520 def get_by_repo_name(cls, repo_name):
521 521 q = Session().query(cls).filter(cls.repo_name == repo_name)
522 522 q = q.options(joinedload(Repository.fork))\
523 523 .options(joinedload(Repository.user))\
524 524 .options(joinedload(Repository.group))
525 525 return q.one()
526 526
527 527 @classmethod
528 528 def get_repo_forks(cls, repo_id):
529 529 return cls.query().filter(Repository.fork_id == repo_id)
530 530
531 531 @classmethod
532 532 def base_path(cls):
533 533 """
534 534 Returns base path when all repos are stored
535 535
536 536 :param cls:
537 537 """
538 538 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
539 539 cls.url_sep())
540 540 q.options(FromCache("sql_cache_short", "repository_repo_path"))
541 541 return q.one().ui_value
542 542
543 543 @property
544 544 def just_name(self):
545 545 return self.repo_name.split(Repository.url_sep())[-1]
546 546
547 547 @property
548 548 def groups_with_parents(self):
549 549 groups = []
550 550 if self.group is None:
551 551 return groups
552 552
553 553 cur_gr = self.group
554 554 groups.insert(0, cur_gr)
555 555 while 1:
556 556 gr = getattr(cur_gr, 'parent_group', None)
557 557 cur_gr = cur_gr.parent_group
558 558 if gr is None:
559 559 break
560 560 groups.insert(0, gr)
561 561
562 562 return groups
563 563
564 564 @property
565 565 def groups_and_repo(self):
566 566 return self.groups_with_parents, self.just_name
567 567
568 568 @LazyProperty
569 569 def repo_path(self):
570 570 """
571 571 Returns base full path for that repository means where it actually
572 572 exists on a filesystem
573 573 """
574 574 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
575 575 Repository.url_sep())
576 q.options(FromCache("sql_cache_short", "repository_repo_path"))
576 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
577 577 return q.one().ui_value
578 578
579 579 @property
580 580 def repo_full_path(self):
581 581 p = [self.repo_path]
582 582 # we need to split the name by / since this is how we store the
583 583 # names in the database, but that eventually needs to be converted
584 584 # into a valid system path
585 585 p += self.repo_name.split(Repository.url_sep())
586 586 return os.path.join(*p)
587 587
588 588 def get_new_name(self, repo_name):
589 589 """
590 590 returns new full repository name based on assigned group and new new
591 591
592 592 :param group_name:
593 593 """
594 594 path_prefix = self.group.full_path_splitted if self.group else []
595 595 return Repository.url_sep().join(path_prefix + [repo_name])
596 596
597 597 @property
598 598 def _ui(self):
599 599 """
600 600 Creates an db based ui object for this repository
601 601 """
602 602 from mercurial import ui
603 603 from mercurial import config
604 604 baseui = ui.ui()
605 605
606 606 #clean the baseui object
607 607 baseui._ocfg = config.config()
608 608 baseui._ucfg = config.config()
609 609 baseui._tcfg = config.config()
610 610
611 611
612 612 ret = RhodeCodeUi.query()\
613 613 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
614 614
615 615 hg_ui = ret
616 616 for ui_ in hg_ui:
617 617 if ui_.ui_active:
618 618 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
619 619 ui_.ui_key, ui_.ui_value)
620 620 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
621 621
622 622 return baseui
623 623
624 624 @classmethod
625 625 def is_valid(cls, repo_name):
626 626 """
627 627 returns True if given repo name is a valid filesystem repository
628 628
629 629 @param cls:
630 630 @param repo_name:
631 631 """
632 632 from rhodecode.lib.utils import is_valid_repo
633 633
634 634 return is_valid_repo(repo_name, cls.base_path())
635 635
636 636
637 637 #==========================================================================
638 638 # SCM PROPERTIES
639 639 #==========================================================================
640 640
641 641 def get_changeset(self, rev):
642 642 return get_changeset_safe(self.scm_instance, rev)
643 643
644 644 @property
645 645 def tip(self):
646 646 return self.get_changeset('tip')
647 647
648 648 @property
649 649 def author(self):
650 650 return self.tip.author
651 651
652 652 @property
653 653 def last_change(self):
654 654 return self.scm_instance.last_change
655 655
656 656 #==========================================================================
657 657 # SCM CACHE INSTANCE
658 658 #==========================================================================
659 659
660 660 @property
661 661 def invalidate(self):
662 662 return CacheInvalidation.invalidate(self.repo_name)
663 663
664 664 def set_invalidate(self):
665 665 """
666 666 set a cache for invalidation for this instance
667 667 """
668 668 CacheInvalidation.set_invalidate(self.repo_name)
669 669
670 670 @LazyProperty
671 671 def scm_instance(self):
672 672 return self.__get_instance()
673 673
674 674 @property
675 675 def scm_instance_cached(self):
676 676 @cache_region('long_term')
677 677 def _c(repo_name):
678 678 return self.__get_instance()
679 679 rn = self.repo_name
680 680
681 681 inv = self.invalidate
682 682 if inv is not None:
683 683 region_invalidate(_c, None, rn)
684 684 # update our cache
685 685 CacheInvalidation.set_valid(inv.cache_key)
686 686 return _c(rn)
687 687
688 688 def __get_instance(self):
689 689
690 690 repo_full_path = self.repo_full_path
691 691
692 692 try:
693 693 alias = get_scm(repo_full_path)[0]
694 694 log.debug('Creating instance of %s repository', alias)
695 695 backend = get_backend(alias)
696 696 except VCSError:
697 697 log.error(traceback.format_exc())
698 698 log.error('Perhaps this repository is in db and not in '
699 699 'filesystem run rescan repositories with '
700 700 '"destroy old data " option from admin panel')
701 701 return
702 702
703 703 if alias == 'hg':
704 704
705 705 repo = backend(safe_str(repo_full_path), create=False,
706 706 baseui=self._ui)
707 707 # skip hidden web repository
708 708 if repo._get_hidden():
709 709 return
710 710 else:
711 711 repo = backend(repo_full_path, create=False)
712 712
713 713 return repo
714 714
715 715
716 716 class RepoGroup(Base, BaseModel):
717 717 __tablename__ = 'groups'
718 718 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
719 719 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
720 720 __mapper_args__ = {'order_by':'group_name'}
721 721
722 722 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
723 723 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
724 724 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
725 725 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
726 726
727 727 parent_group = relationship('RepoGroup', remote_side=group_id)
728 728
729 729
730 730 def __init__(self, group_name='', parent_group=None):
731 731 self.group_name = group_name
732 732 self.parent_group = parent_group
733 733
734 734 def __repr__(self):
735 735 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
736 736 self.group_name)
737 737
738 738 @classmethod
739 739 def groups_choices(cls):
740 740 from webhelpers.html import literal as _literal
741 741 repo_groups = [('', '')]
742 742 sep = ' &raquo; '
743 743 _name = lambda k: _literal(sep.join(k))
744 744
745 745 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
746 746 for x in cls.query().all()])
747 747
748 748 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
749 749 return repo_groups
750 750
751 751 @classmethod
752 752 def url_sep(cls):
753 753 return '/'
754 754
755 755 @classmethod
756 756 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
757 757 if case_insensitive:
758 758 gr = cls.query()\
759 759 .filter(cls.group_name.ilike(group_name))
760 760 else:
761 761 gr = cls.query()\
762 762 .filter(cls.group_name == group_name)
763 763 if cache:
764 764 gr = gr.options(FromCache("sql_cache_short",
765 765 "get_group_%s" % group_name))
766 766 return gr.scalar()
767 767
768 768 @property
769 769 def parents(self):
770 770 parents_recursion_limit = 5
771 771 groups = []
772 772 if self.parent_group is None:
773 773 return groups
774 774 cur_gr = self.parent_group
775 775 groups.insert(0, cur_gr)
776 776 cnt = 0
777 777 while 1:
778 778 cnt += 1
779 779 gr = getattr(cur_gr, 'parent_group', None)
780 780 cur_gr = cur_gr.parent_group
781 781 if gr is None:
782 782 break
783 783 if cnt == parents_recursion_limit:
784 784 # this will prevent accidental infinit loops
785 785 log.error('group nested more than %s' %
786 786 parents_recursion_limit)
787 787 break
788 788
789 789 groups.insert(0, gr)
790 790 return groups
791 791
792 792 @property
793 793 def children(self):
794 794 return RepoGroup.query().filter(RepoGroup.parent_group == self)
795 795
796 796 @property
797 797 def name(self):
798 798 return self.group_name.split(RepoGroup.url_sep())[-1]
799 799
800 800 @property
801 801 def full_path(self):
802 802 return self.group_name
803 803
804 804 @property
805 805 def full_path_splitted(self):
806 806 return self.group_name.split(RepoGroup.url_sep())
807 807
808 808 @property
809 809 def repositories(self):
810 810 return Repository.query().filter(Repository.group == self)
811 811
812 812 @property
813 813 def repositories_recursive_count(self):
814 814 cnt = self.repositories.count()
815 815
816 816 def children_count(group):
817 817 cnt = 0
818 818 for child in group.children:
819 819 cnt += child.repositories.count()
820 820 cnt += children_count(child)
821 821 return cnt
822 822
823 823 return cnt + children_count(self)
824 824
825 825
826 826 def get_new_name(self, group_name):
827 827 """
828 828 returns new full group name based on parent and new name
829 829
830 830 :param group_name:
831 831 """
832 832 path_prefix = (self.parent_group.full_path_splitted if
833 833 self.parent_group else [])
834 834 return RepoGroup.url_sep().join(path_prefix + [group_name])
835 835
836 836
837 837 class Permission(Base, BaseModel):
838 838 __tablename__ = 'permissions'
839 839 __table_args__ = {'extend_existing':True}
840 840 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
841 841 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
842 842 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
843 843
844 844 def __repr__(self):
845 845 return "<%s('%s:%s')>" % (self.__class__.__name__,
846 846 self.permission_id, self.permission_name)
847 847
848 848 @classmethod
849 849 def get_by_key(cls, key):
850 850 return cls.query().filter(cls.permission_name == key).scalar()
851 851
852 852 class UserRepoToPerm(Base, BaseModel):
853 853 __tablename__ = 'repo_to_perm'
854 854 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
855 855 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
856 856 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
857 857 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
858 858 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
859 859
860 860 user = relationship('User')
861 861 permission = relationship('Permission')
862 862 repository = relationship('Repository')
863 863
864 864 class UserToPerm(Base, BaseModel):
865 865 __tablename__ = 'user_to_perm'
866 866 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
867 867 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
868 868 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
869 869 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
870 870
871 871 user = relationship('User')
872 872 permission = relationship('Permission')
873 873
874 874 @classmethod
875 875 def has_perm(cls, user_id, perm):
876 876 if not isinstance(perm, Permission):
877 877 raise Exception('perm needs to be an instance of Permission class')
878 878
879 879 return cls.query().filter(cls.user_id == user_id)\
880 880 .filter(cls.permission == perm).scalar() is not None
881 881
882 882 @classmethod
883 883 def grant_perm(cls, user_id, perm):
884 884 if not isinstance(perm, Permission):
885 885 raise Exception('perm needs to be an instance of Permission class')
886 886
887 887 new = cls()
888 888 new.user_id = user_id
889 889 new.permission = perm
890 890 try:
891 891 Session().add(new)
892 892 Session().commit()
893 893 except:
894 894 Session().rollback()
895 895
896 896
897 897 @classmethod
898 898 def revoke_perm(cls, user_id, perm):
899 899 if not isinstance(perm, Permission):
900 900 raise Exception('perm needs to be an instance of Permission class')
901 901
902 902 try:
903 903 obj = cls.query().filter(cls.user_id == user_id)\
904 904 .filter(cls.permission == perm).one()
905 905 Session().delete(obj)
906 906 Session().commit()
907 907 except:
908 908 Session().rollback()
909 909
910 910 class UsersGroupRepoToPerm(Base, BaseModel):
911 911 __tablename__ = 'users_group_repo_to_perm'
912 912 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
913 913 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
914 914 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
915 915 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
916 916 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
917 917
918 918 users_group = relationship('UsersGroup')
919 919 permission = relationship('Permission')
920 920 repository = relationship('Repository')
921 921
922 922 def __repr__(self):
923 923 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
924 924
925 925 class UsersGroupToPerm(Base, BaseModel):
926 926 __tablename__ = 'users_group_to_perm'
927 927 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
928 928 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
929 929 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
930 930
931 931 users_group = relationship('UsersGroup')
932 932 permission = relationship('Permission')
933 933
934 934
935 935 @classmethod
936 936 def has_perm(cls, users_group_id, perm):
937 937 if not isinstance(perm, Permission):
938 938 raise Exception('perm needs to be an instance of Permission class')
939 939
940 940 return cls.query().filter(cls.users_group_id ==
941 941 users_group_id)\
942 942 .filter(cls.permission == perm)\
943 943 .scalar() is not None
944 944
945 945 @classmethod
946 946 def grant_perm(cls, users_group_id, perm):
947 947 if not isinstance(perm, Permission):
948 948 raise Exception('perm needs to be an instance of Permission class')
949 949
950 950 new = cls()
951 951 new.users_group_id = users_group_id
952 952 new.permission = perm
953 953 try:
954 954 Session().add(new)
955 955 Session().commit()
956 956 except:
957 957 Session().rollback()
958 958
959 959
960 960 @classmethod
961 961 def revoke_perm(cls, users_group_id, perm):
962 962 if not isinstance(perm, Permission):
963 963 raise Exception('perm needs to be an instance of Permission class')
964 964
965 965 try:
966 966 obj = cls.query().filter(cls.users_group_id == users_group_id)\
967 967 .filter(cls.permission == perm).one()
968 968 Session().delete(obj)
969 969 Session().commit()
970 970 except:
971 971 Session().rollback()
972 972
973 973
974 974 class UserRepoGroupToPerm(Base, BaseModel):
975 975 __tablename__ = 'group_to_perm'
976 976 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
977 977
978 978 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
979 979 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
980 980 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
981 981 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
982 982
983 983 user = relationship('User')
984 984 permission = relationship('Permission')
985 985 group = relationship('RepoGroup')
986 986
987 987 class UsersGroupRepoGroupToPerm(Base, BaseModel):
988 988 __tablename__ = 'users_group_repo_group_to_perm'
989 989 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
990 990
991 991 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)
992 992 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
993 993 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
994 994 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
995 995
996 996 users_group = relationship('UsersGroup')
997 997 permission = relationship('Permission')
998 998 group = relationship('RepoGroup')
999 999
1000 1000 class Statistics(Base, BaseModel):
1001 1001 __tablename__ = 'statistics'
1002 1002 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
1003 1003 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1004 1004 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1005 1005 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1006 1006 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1007 1007 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1008 1008 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1009 1009
1010 1010 repository = relationship('Repository', single_parent=True)
1011 1011
1012 1012 class UserFollowing(Base, BaseModel):
1013 1013 __tablename__ = 'user_followings'
1014 1014 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1015 1015 UniqueConstraint('user_id', 'follows_user_id')
1016 1016 , {'extend_existing':True})
1017 1017
1018 1018 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1019 1019 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1020 1020 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1021 1021 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1022 1022 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1023 1023
1024 1024 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1025 1025
1026 1026 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1027 1027 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1028 1028
1029 1029
1030 1030 @classmethod
1031 1031 def get_repo_followers(cls, repo_id):
1032 1032 return cls.query().filter(cls.follows_repo_id == repo_id)
1033 1033
1034 1034 class CacheInvalidation(Base, BaseModel):
1035 1035 __tablename__ = 'cache_invalidation'
1036 1036 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1037 1037 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1038 1038 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1039 1039 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1040 1040 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1041 1041
1042 1042
1043 1043 def __init__(self, cache_key, cache_args=''):
1044 1044 self.cache_key = cache_key
1045 1045 self.cache_args = cache_args
1046 1046 self.cache_active = False
1047 1047
1048 1048 def __repr__(self):
1049 1049 return "<%s('%s:%s')>" % (self.__class__.__name__,
1050 1050 self.cache_id, self.cache_key)
1051 1051
1052 1052 @classmethod
1053 1053 def invalidate(cls, key):
1054 1054 """
1055 1055 Returns Invalidation object if this given key should be invalidated
1056 1056 None otherwise. `cache_active = False` means that this cache
1057 1057 state is not valid and needs to be invalidated
1058 1058
1059 1059 :param key:
1060 1060 """
1061 1061 return cls.query()\
1062 1062 .filter(CacheInvalidation.cache_key == key)\
1063 1063 .filter(CacheInvalidation.cache_active == False)\
1064 1064 .scalar()
1065 1065
1066 1066 @classmethod
1067 1067 def set_invalidate(cls, key):
1068 1068 """
1069 1069 Mark this Cache key for invalidation
1070 1070
1071 1071 :param key:
1072 1072 """
1073 1073
1074 1074 log.debug('marking %s for invalidation' % key)
1075 1075 inv_obj = Session().query(cls)\
1076 1076 .filter(cls.cache_key == key).scalar()
1077 1077 if inv_obj:
1078 1078 inv_obj.cache_active = False
1079 1079 else:
1080 1080 log.debug('cache key not found in invalidation db -> creating one')
1081 1081 inv_obj = CacheInvalidation(key)
1082 1082
1083 1083 try:
1084 1084 Session().add(inv_obj)
1085 1085 Session().commit()
1086 1086 except Exception:
1087 1087 log.error(traceback.format_exc())
1088 1088 Session().rollback()
1089 1089
1090 1090 @classmethod
1091 1091 def set_valid(cls, key):
1092 1092 """
1093 1093 Mark this cache key as active and currently cached
1094 1094
1095 1095 :param key:
1096 1096 """
1097 1097 inv_obj = CacheInvalidation.query()\
1098 1098 .filter(CacheInvalidation.cache_key == key).scalar()
1099 1099 inv_obj.cache_active = True
1100 1100 Session().add(inv_obj)
1101 1101 Session().commit()
1102 1102
1103 1103
1104 1104 class ChangesetComment(Base, BaseModel):
1105 1105 __tablename__ = 'changeset_comments'
1106 1106 __table_args__ = ({'extend_existing':True},)
1107 1107 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1108 1108 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1109 1109 revision = Column('revision', String(40), nullable=False)
1110 1110 line_no = Column('line_no', Unicode(10), nullable=True)
1111 1111 f_path = Column('f_path', Unicode(1000), nullable=True)
1112 1112 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1113 1113 text = Column('text', Unicode(25000), nullable=False)
1114 1114 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1115 1115
1116 1116 author = relationship('User', lazy='joined')
1117 1117 repo = relationship('Repository')
1118 1118
1119 1119
1120 1120 @classmethod
1121 1121 def get_users(cls, revision):
1122 1122 """
1123 1123 Returns user associated with this changesetComment. ie those
1124 1124 who actually commented
1125 1125
1126 1126 :param cls:
1127 1127 :param revision:
1128 1128 """
1129 1129 return Session().query(User)\
1130 1130 .filter(cls.revision == revision)\
1131 1131 .join(ChangesetComment.author).all()
1132 1132
1133 1133
1134 1134 class Notification(Base, BaseModel):
1135 1135 __tablename__ = 'notifications'
1136 1136 __table_args__ = ({'extend_existing':True})
1137 1137
1138 1138 TYPE_CHANGESET_COMMENT = u'cs_comment'
1139 1139 TYPE_MESSAGE = u'message'
1140 1140 TYPE_MENTION = u'mention'
1141 1141
1142 1142 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1143 1143 subject = Column('subject', Unicode(512), nullable=True)
1144 1144 body = Column('body', Unicode(50000), nullable=True)
1145 1145 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1146 1146 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1147 1147 type_ = Column('type', Unicode(256))
1148 1148
1149 1149 created_by_user = relationship('User')
1150 1150 notifications_to_users = relationship('UserNotification', lazy='joined',
1151 1151 cascade="all, delete, delete-orphan")
1152 1152
1153 1153 @property
1154 1154 def recipients(self):
1155 1155 return [x.user for x in UserNotification.query()\
1156 1156 .filter(UserNotification.notification == self).all()]
1157 1157
1158 1158 @classmethod
1159 1159 def create(cls, created_by, subject, body, recipients, type_=None):
1160 1160 if type_ is None:
1161 1161 type_ = Notification.TYPE_MESSAGE
1162 1162
1163 1163 notification = cls()
1164 1164 notification.created_by_user = created_by
1165 1165 notification.subject = subject
1166 1166 notification.body = body
1167 1167 notification.type_ = type_
1168 1168 notification.created_on = datetime.datetime.now()
1169 1169
1170 1170 for u in recipients:
1171 1171 assoc = UserNotification()
1172 1172 assoc.notification = notification
1173 1173 u.notifications.append(assoc)
1174 1174 Session().add(notification)
1175 1175 return notification
1176 1176
1177 1177 @property
1178 1178 def description(self):
1179 1179 from rhodecode.model.notification import NotificationModel
1180 1180 return NotificationModel().make_description(self)
1181 1181
1182 1182 class UserNotification(Base, BaseModel):
1183 1183 __tablename__ = 'user_to_notification'
1184 1184 __table_args__ = (UniqueConstraint('user_id', 'notification_id'),
1185 1185 {'extend_existing':True})
1186 1186 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), primary_key=True)
1187 1187 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1188 1188 read = Column('read', Boolean, default=False)
1189 1189 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1190 1190
1191 1191 user = relationship('User', lazy="joined")
1192 1192 notification = relationship('Notification', lazy="joined",
1193 1193 order_by=lambda:Notification.created_on.desc(),
1194 1194 cascade='all')
1195 1195
1196 1196 def mark_as_read(self):
1197 1197 self.read = True
1198 1198 Session().add(self)
1199 1199
1200 1200 class DbMigrateVersion(Base, BaseModel):
1201 1201 __tablename__ = 'db_migrate_version'
1202 1202 __table_args__ = {'extend_existing':True}
1203 1203 repository_id = Column('repository_id', String(250), primary_key=True)
1204 1204 repository_path = Column('repository_path', Text)
1205 1205 version = Column('version', Integer)
1206 1206
General Comments 0
You need to be logged in to leave comments. Login now