##// END OF EJS Templates
Extended API...
marcink -
r1500:256e729a beta
parent child Browse files
Show More
@@ -1,57 +1,111 b''
1 1 .. _api:
2 2
3 3
4 4 API
5 5 ===
6 6
7 7
8 8 Starting from RhodeCode version 1.2 a simple API was implemented.
9 There's one schema for calling all api methods. API is implemented
9 There's a single schema for calling all api methods. API is implemented
10 10 with JSON protocol both ways. An url to send API request in RhodeCode is
11 <your-server>/_admin/api
11 <your_server>/_admin/api
12 12
13 13
14 Clients need to send JSON data in such format::
14 All clients need to send JSON data in such format::
15 15
16 16 {
17 17 "api_key":"<api_key>",
18 18 "method":"<method_name>",
19 19 "args":{"<arg_key>":"<arg_val>"}
20 20 }
21 21
22 Simply provide api_key for access and permission validation
23 method is name of method to call
24 and args is an key:value list of arguments to pass to method
22 Example call for autopulling remotes repos using curl::
23 curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
24
25 Simply provide
26 - *api_key* for access and permission validation.
27 - *method* is name of method to call
28 - *args* is an key:value list of arguments to pass to method
25 29
26 30 .. note::
27 31
28 32 api_key can be found in your user account page
29 33
30 34
31 And will receive JSON formatted answer::
35 RhodeCode API will return always a JSON formatted answer::
32 36
33 37 {
34 38 "result": "<result>",
35 39 "error": null
36 40 }
37 41
38 42 All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
39 calling api **error** key from response will contain failure description
43 calling api *error* key from response will contain failure description
40 44 and result will be null.
41 45
42 46 API METHODS
43 47 +++++++++++
44 48
45 49
46 50 pull
47 51 ----
48 52
49 53 Pulls given repo from remote location. Can be used to automatically keep
50 remote repos upto date. This command can be executed only using admin users
51 api_key
54 remote repos up to date. This command can be executed only using api_key
55 belonging to user with admin rights
52 56
53 ::
57 INPUT::
58
54 59 api_key:"<api_key>"
55 60 method: "pull"
56 61 args: {"repo":<repo_name>}
57 62
63 OUTPUT::
64
65 result:"Pulled from <repo_name>"
66 error:null
67
68
69 create_user
70 -----------
71
72 Creates new user in RhodeCode. This command can be executed only using api_key
73 belonging to user with admin rights
74
75 INPUT::
76
77 api_key:"<api_key>"
78 method: "create_user"
79 args: {"username": "<username>",
80 "password": "<password>",
81 "active": "<bool>",
82 "admin": "<bool>",
83 "name": "<firstname>",
84 "lastname": "<lastname>",
85 "email": "<useremail>"}
86
87 OUTPUT::
88
89 result:{"id": <newuserid>,
90 "msg":"created new user <username>"}
91 error:null
92
93
94 create_users_group
95 ------------------
96
97 creates new users group. This command can be executed only using api_key
98 belonging to user with admin rights
99
100 INPUT::
101
102 api_key:"<api_key>"
103 method: "create_user"
104 args: {"name": "<groupname>",
105 "active":"<bool>"}
106
107 OUTPUT::
108
109 result:{"id": <newusersgroupid>,
110 "msg":"created new users group <groupname>"}
111 error:null
@@ -1,225 +1,229 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.api
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 JSON RPC controller
7 7
8 8 :created_on: Aug 20, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 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
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import inspect
29 29 import json
30 30 import logging
31 31 import types
32 32 import urllib
33 33
34 34 from paste.response import replace_header
35 35
36 36 from pylons.controllers import WSGIController
37 37 from pylons.controllers.util import Response
38 38
39 39 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \
40 40 HTTPBadRequest, HTTPError
41 41
42 42 from rhodecode.model.db import User
43 43 from rhodecode.lib.auth import AuthUser
44 44
45 45 log = logging.getLogger('JSONRPC')
46 46
47 47 class JSONRPCError(BaseException):
48 48
49 49 def __init__(self, message):
50 50 self.message = message
51 51
52 52 def __str__(self):
53 53 return str(self.message)
54 54
55 55
56 56 def jsonrpc_error(message, code=None):
57 57 """Generate a Response object with a JSON-RPC error body"""
58 58 return Response(body=json.dumps(dict(result=None,
59 59 error=message)))
60 60
61 61
62 62 class JSONRPCController(WSGIController):
63 63 """
64 64 A WSGI-speaking JSON-RPC controller class
65 65
66 66 See the specification:
67 67 <http://json-rpc.org/wiki/specification>`.
68 68
69 69 Valid controller return values should be json-serializable objects.
70 70
71 71 Sub-classes should catch their exceptions and raise JSONRPCError
72 72 if they want to pass meaningful errors to the client.
73 73
74 74 """
75 75
76 76 def _get_method_args(self):
77 77 """
78 78 Return `self._rpc_args` to dispatched controller method
79 79 chosen by __call__
80 80 """
81 81 return self._rpc_args
82 82
83 83 def __call__(self, environ, start_response):
84 84 """
85 85 Parse the request body as JSON, look up the method on the
86 86 controller and if it exists, dispatch to it.
87 87 """
88 88 if 'CONTENT_LENGTH' not in environ:
89 89 log.debug("No Content-Length")
90 90 return jsonrpc_error(message="No Content-Length in request")
91 91 else:
92 92 length = environ['CONTENT_LENGTH'] or 0
93 93 length = int(environ['CONTENT_LENGTH'])
94 94 log.debug('Content-Length: %s', length)
95 95
96 96 if length == 0:
97 97 log.debug("Content-Length is 0")
98 98 return jsonrpc_error(message="Content-Length is 0")
99 99
100 100 raw_body = environ['wsgi.input'].read(length)
101 101
102 102 try:
103 103 json_body = json.loads(urllib.unquote_plus(raw_body))
104 104 except ValueError as e:
105 105 #catch JSON errors Here
106 106 return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \
107 107 % (e, urllib.unquote_plus(raw_body)))
108 108
109 109 #check AUTH based on API KEY
110 110 try:
111 111 self._req_api_key = json_body['api_key']
112 112 self._req_method = json_body['method']
113 113 self._req_params = json_body['args']
114 114 log.debug('method: %s, params: %s',
115 115 self._req_method,
116 116 self._req_params)
117 117 except KeyError as e:
118 118 return jsonrpc_error(message='Incorrect JSON query missing %s' % e)
119 119
120 120 #check if we can find this session using api_key
121 121 try:
122 122 u = User.get_by_api_key(self._req_api_key)
123 123 auth_u = AuthUser(u.user_id, self._req_api_key)
124 124 except Exception as e:
125 125 return jsonrpc_error(message='Invalid API KEY')
126 126
127 127 self._error = None
128 128 try:
129 129 self._func = self._find_method()
130 130 except AttributeError, e:
131 131 return jsonrpc_error(message=str(e))
132 132
133 133 # now that we have a method, add self._req_params to
134 134 # self.kargs and dispatch control to WGIController
135 135 arglist = inspect.getargspec(self._func)[0][1:]
136 136
137 137 # this is little trick to inject logged in user for
138 138 # perms decorators to work they expect the controller class to have
139 # rhodecode_user set
139 # rhodecode_user attribute set
140 140 self.rhodecode_user = auth_u
141 141
142 if 'user' not in arglist:
142 # This attribute will need to be first param of a method that uses
143 # api_key, which is translated to instance of user at that name
144 USER_SESSION_ATTR = 'apiuser'
145
146 if USER_SESSION_ATTR not in arglist:
143 147 return jsonrpc_error(message='This method [%s] does not support '
144 'authentication (missing user param)' %
145 self._func.__name__)
148 'authentication (missing %s param)' %
149 (self._func.__name__, USER_SESSION_ATTR))
146 150
147 151 # get our arglist and check if we provided them as args
148 152 for arg in arglist:
149 if arg == 'user':
150 # user is something translated from api key and this is
151 # checked before
153 if arg == USER_SESSION_ATTR:
154 # USER_SESSION_ATTR is something translated from api key and
155 # this is checked before so we don't need validate it
152 156 continue
153 157
154 158 if not self._req_params or arg not in self._req_params:
155 159 return jsonrpc_error(message='Missing %s arg in JSON DATA' % arg)
156 160
157 self._rpc_args = dict(user=u)
161 self._rpc_args = {USER_SESSION_ATTR:u}
158 162 self._rpc_args.update(self._req_params)
159 163
160 164 self._rpc_args['action'] = self._req_method
161 165 self._rpc_args['environ'] = environ
162 166 self._rpc_args['start_response'] = start_response
163 167
164 168 status = []
165 169 headers = []
166 170 exc_info = []
167 171 def change_content(new_status, new_headers, new_exc_info=None):
168 172 status.append(new_status)
169 173 headers.extend(new_headers)
170 174 exc_info.append(new_exc_info)
171 175
172 176 output = WSGIController.__call__(self, environ, change_content)
173 177 output = list(output)
174 178 headers.append(('Content-Length', str(len(output[0]))))
175 179 replace_header(headers, 'Content-Type', 'application/json')
176 180 start_response(status[0], headers, exc_info[0])
177 181
178 182 return output
179 183
180 184 def _dispatch_call(self):
181 185 """
182 186 Implement dispatch interface specified by WSGIController
183 187 """
184 188 try:
185 189 raw_response = self._inspect_call(self._func)
186 print raw_response
187 190 if isinstance(raw_response, HTTPError):
188 191 self._error = str(raw_response)
189 192 except JSONRPCError as e:
190 193 self._error = str(e)
191 194 except Exception as e:
192 195 log.debug('Encountered unhandled exception: %s', repr(e))
193 196 json_exc = JSONRPCError('Internal server error')
194 197 self._error = str(json_exc)
195 198
196 199 if self._error is not None:
197 200 raw_response = None
198 201
199 202 response = dict(result=raw_response, error=self._error)
200 203
201 204 try:
202 205 return json.dumps(response)
203 206 except TypeError, e:
204 207 log.debug('Error encoding response: %s', e)
205 208 return json.dumps(dict(result=None,
206 209 error="Error encoding response"))
207 210
208 211 def _find_method(self):
209 212 """
210 213 Return method named by `self._req_method` in controller if able
211 214 """
212 215 log.debug('Trying to find JSON-RPC method: %s', self._req_method)
213 216 if self._req_method.startswith('_'):
214 217 raise AttributeError("Method not allowed")
215 218
216 219 try:
217 220 func = getattr(self, self._req_method, None)
218 221 except UnicodeEncodeError:
219 222 raise AttributeError("Problem decoding unicode in requested "
220 223 "method name.")
221 224
222 225 if isinstance(func, types.MethodType):
223 226 return func
224 227 else:
225 228 raise AttributeError("No such method: %s" % self._req_method)
229
@@ -1,40 +1,95 b''
1 import traceback
2 import logging
3
1 4 from rhodecode.controllers.api import JSONRPCController, JSONRPCError
2 5 from rhodecode.lib.auth import HasPermissionAllDecorator
3 6 from rhodecode.model.scm import ScmModel
4 7
8 from rhodecode.model.db import User, UsersGroup
9
10 log = logging.getLogger(__name__)
11
5 12
6 13 class ApiController(JSONRPCController):
7 14 """
8 15 API Controller
9 16
10 17
11 18 Each method needs to have USER as argument this is then based on given
12 19 API_KEY propagated as instance of user object
13 20
14 21 Preferably this should be first argument also
15 22
16 23
17 24 Each function should also **raise** JSONRPCError for any
18 25 errors that happens
19 26
20 27 """
21 28
22 29 @HasPermissionAllDecorator('hg.admin')
23 def pull(self, user, repo):
30 def pull(self, apiuser, repo):
24 31 """
25 32 Dispatch pull action on given repo
26 33
27 34
28 param user:
29 param repo:
35 :param user:
36 :param repo:
30 37 """
31 38
32 39 try:
33 40 ScmModel().pull_changes(repo, self.rhodecode_user.username)
34 41 return 'Pulled from %s' % repo
35 42 except Exception:
36 43 raise JSONRPCError('Unable to pull changes from "%s"' % repo)
37 44
38 45
46 @HasPermissionAllDecorator('hg.admin')
47 def create_user(self, apiuser, username, password, active, admin, name,
48 lastname, email):
49 """
50 Creates new user
51
52 :param apiuser:
53 :param username:
54 :param password:
55 :param active:
56 :param admin:
57 :param name:
58 :param lastname:
59 :param email:
60 """
61
62 form_data = dict(username=username,
63 password=password,
64 active=active,
65 admin=admin,
66 name=name,
67 lastname=lastname,
68 email=email)
69 try:
70 u = User.create(form_data)
71 return {'id':u.user_id,
72 'msg':'created new user %s' % name}
73 except Exception:
74 log.error(traceback.format_exc())
75 raise JSONRPCError('failed to create user %s' % name)
39 76
40 77
78 @HasPermissionAllDecorator('hg.admin')
79 def create_users_group(self, apiuser, name, active):
80 """
81 Creates an new usergroup
82
83 :param name:
84 :param active:
85 """
86 form_data = {'users_group_name':name,
87 'users_group_active':active}
88 try:
89 ug = UsersGroup.create(form_data)
90 return {'id':ug.users_group_id,
91 'msg':'created new users group %s' % name}
92 except Exception:
93 log.error(traceback.format_exc())
94 raise JSONRPCError('failed to create group %s' % name)
95 No newline at end of file
@@ -1,934 +1,955 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 from datetime import date
31 31
32 32 from sqlalchemy import *
33 33 from sqlalchemy.exc import DatabaseError
34 34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
35 35 from sqlalchemy.orm.interfaces import MapperExtension
36 36
37 37 from beaker.cache import cache_region, region_invalidate
38 38
39 39 from vcs import get_backend
40 40 from vcs.utils.helpers import get_scm
41 from vcs.exceptions import RepositoryError, VCSError
41 from vcs.exceptions import VCSError
42 42 from vcs.utils.lazy import LazyProperty
43 from vcs.nodes import FileNode
44 43
45 44 from rhodecode.lib.exceptions import UsersGroupsAssignedException
46 from rhodecode.lib import str2bool, json, safe_str, get_changeset_safe
45 from rhodecode.lib import str2bool, json, safe_str, get_changeset_safe,\
46 generate_api_key
47
47 48 from rhodecode.model.meta import Base, Session
48 49 from rhodecode.model.caching_query import FromCache
49 50
50 51 log = logging.getLogger(__name__)
51 52
52 53 #==============================================================================
53 54 # BASE CLASSES
54 55 #==============================================================================
55 56
56 57 class ModelSerializer(json.JSONEncoder):
57 58 """
58 59 Simple Serializer for JSON,
59 60
60 61 usage::
61 62
62 63 to make object customized for serialization implement a __json__
63 64 method that will return a dict for serialization into json
64 65
65 66 example::
66 67
67 68 class Task(object):
68 69
69 70 def __init__(self, name, value):
70 71 self.name = name
71 72 self.value = value
72 73
73 74 def __json__(self):
74 75 return dict(name=self.name,
75 76 value=self.value)
76 77
77 78 """
78 79
79 80 def default(self, obj):
80 81
81 82 if hasattr(obj, '__json__'):
82 83 return obj.__json__()
83 84 else:
84 85 return json.JSONEncoder.default(self, obj)
85 86
86 87 class BaseModel(object):
87 88 """Base Model for all classess
88 89
89 90 """
90 91
91 92 @classmethod
92 93 def _get_keys(cls):
93 94 """return column names for this model """
94 95 return class_mapper(cls).c.keys()
95 96
96 97 def get_dict(self):
97 98 """return dict with keys and values corresponding
98 99 to this model data """
99 100
100 101 d = {}
101 102 for k in self._get_keys():
102 103 d[k] = getattr(self, k)
103 104 return d
104 105
105 106 def get_appstruct(self):
106 107 """return list with keys and values tupples corresponding
107 108 to this model data """
108 109
109 110 l = []
110 111 for k in self._get_keys():
111 112 l.append((k, getattr(self, k),))
112 113 return l
113 114
114 115 def populate_obj(self, populate_dict):
115 116 """populate model with data from given populate_dict"""
116 117
117 118 for k in self._get_keys():
118 119 if k in populate_dict:
119 120 setattr(self, k, populate_dict[k])
120 121
121 122 @classmethod
122 123 def query(cls):
123 124 return Session.query(cls)
124 125
125 126 @classmethod
126 127 def get(cls, id_):
127 128 return Session.query(cls).get(id_)
128 129
129 130 @classmethod
130 131 def delete(cls, id_):
131 132 obj = Session.query(cls).get(id_)
132 133 Session.delete(obj)
133 134 Session.commit()
134 135
135 136
136 137 class RhodeCodeSettings(Base, BaseModel):
137 138 __tablename__ = 'rhodecode_settings'
138 139 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
139 140 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
140 141 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
141 142 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
142 143
143 144 def __init__(self, k='', v=''):
144 145 self.app_settings_name = k
145 146 self.app_settings_value = v
146 147
147 148 def __repr__(self):
148 149 return "<%s('%s:%s')>" % (self.__class__.__name__,
149 150 self.app_settings_name, self.app_settings_value)
150 151
151 152
152 153 @classmethod
153 154 def get_by_name(cls, ldap_key):
154 155 return Session.query(cls)\
155 156 .filter(cls.app_settings_name == ldap_key).scalar()
156 157
157 158 @classmethod
158 159 def get_app_settings(cls, cache=False):
159 160
160 161 ret = Session.query(cls)
161 162
162 163 if cache:
163 164 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
164 165
165 166 if not ret:
166 167 raise Exception('Could not get application settings !')
167 168 settings = {}
168 169 for each in ret:
169 170 settings['rhodecode_' + each.app_settings_name] = \
170 171 each.app_settings_value
171 172
172 173 return settings
173 174
174 175 @classmethod
175 176 def get_ldap_settings(cls, cache=False):
176 177 ret = Session.query(cls)\
177 178 .filter(cls.app_settings_name.startswith('ldap_'))\
178 179 .all()
179 180 fd = {}
180 181 for row in ret:
181 182 fd.update({row.app_settings_name:row.app_settings_value})
182 183
183 184 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
184 185
185 186 return fd
186 187
187 188
188 189 class RhodeCodeUi(Base, BaseModel):
189 190 __tablename__ = 'rhodecode_ui'
190 191 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
191 192
192 193 HOOK_UPDATE = 'changegroup.update'
193 194 HOOK_REPO_SIZE = 'changegroup.repo_size'
194 195 HOOK_PUSH = 'pretxnchangegroup.push_logger'
195 196 HOOK_PULL = 'preoutgoing.pull_logger'
196 197
197 198 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
198 199 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
199 200 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
200 201 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 202 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
202 203
203 204
204 205 @classmethod
205 206 def get_by_key(cls, key):
206 207 return Session.query(cls).filter(cls.ui_key == key)
207 208
208 209
209 210 @classmethod
210 211 def get_builtin_hooks(cls):
211 212 q = cls.query()
212 213 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
213 214 cls.HOOK_REPO_SIZE,
214 215 cls.HOOK_PUSH, cls.HOOK_PULL]))
215 216 return q.all()
216 217
217 218 @classmethod
218 219 def get_custom_hooks(cls):
219 220 q = cls.query()
220 221 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
221 222 cls.HOOK_REPO_SIZE,
222 223 cls.HOOK_PUSH, cls.HOOK_PULL]))
223 224 q = q.filter(cls.ui_section == 'hooks')
224 225 return q.all()
225 226
226 227 @classmethod
227 228 def create_or_update_hook(cls, key, val):
228 229 new_ui = cls.get_by_key(key).scalar() or cls()
229 230 new_ui.ui_section = 'hooks'
230 231 new_ui.ui_active = True
231 232 new_ui.ui_key = key
232 233 new_ui.ui_value = val
233 234
234 235 Session.add(new_ui)
235 236 Session.commit()
236 237
237 238
238 239 class User(Base, BaseModel):
239 240 __tablename__ = 'users'
240 241 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
241 242 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
242 243 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
243 244 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
244 245 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
245 246 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
246 247 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 248 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 249 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 250 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
250 251 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 252 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 253
253 254 user_log = relationship('UserLog', cascade='all')
254 255 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
255 256
256 257 repositories = relationship('Repository')
257 258 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
258 259 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
259 260
260 261 group_member = relationship('UsersGroupMember', cascade='all')
261 262
262 263 @property
263 264 def full_contact(self):
264 265 return '%s %s <%s>' % (self.name, self.lastname, self.email)
265 266
266 267 @property
267 268 def short_contact(self):
268 269 return '%s %s' % (self.name, self.lastname)
269 270
270 271 @property
271 272 def is_admin(self):
272 273 return self.admin
273 274
274 275 def __repr__(self):
275 276 try:
276 277 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
277 278 self.user_id, self.username)
278 279 except:
279 280 return self.__class__.__name__
280 281
281 282 @classmethod
282 283 def by_username(cls, username, case_insensitive=False):
283 284 if case_insensitive:
284 285 return Session.query(cls).filter(cls.username.like(username)).one()
285 286 else:
286 287 return Session.query(cls).filter(cls.username == username).one()
287 288
288 289 @classmethod
289 290 def get_by_api_key(cls, api_key):
290 291 return Session.query(cls).filter(cls.api_key == api_key).one()
291 292
292 293
293 294 def update_lastlogin(self):
294 295 """Update user lastlogin"""
295 296
296 297 self.last_login = datetime.datetime.now()
297 298 Session.add(self)
298 299 Session.commit()
299 300 log.debug('updated user %s lastlogin', self.username)
300 301
302 @classmethod
303 def create(cls, form_data):
304 from rhodecode.lib.auth import get_crypt_password
305
306 try:
307 new_user = cls()
308 for k, v in form_data.items():
309 if k == 'password':
310 v = get_crypt_password(v)
311 setattr(new_user, k, v)
312
313 new_user.api_key = generate_api_key(form_data['username'])
314 Session.add(new_user)
315 Session.commit()
316 return new_user
317 except:
318 log.error(traceback.format_exc())
319 Session.rollback()
320 raise
301 321
302 322 class UserLog(Base, BaseModel):
303 323 __tablename__ = 'user_logs'
304 324 __table_args__ = {'extend_existing':True}
305 325 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
306 326 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
307 327 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
308 328 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
309 329 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
310 330 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 331 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
312 332
313 333 @property
314 334 def action_as_day(self):
315 335 return date(*self.action_date.timetuple()[:3])
316 336
317 337 user = relationship('User')
318 338 repository = relationship('Repository')
319 339
320 340
321 341 class UsersGroup(Base, BaseModel):
322 342 __tablename__ = 'users_groups'
323 343 __table_args__ = {'extend_existing':True}
324 344
325 345 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
326 346 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
327 347 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
328 348
329 349 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
330 350
331 351 def __repr__(self):
332 352 return '<userGroup(%s)>' % (self.users_group_name)
333 353
334 354 @classmethod
335 355 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
336 356 if case_insensitive:
337 357 gr = Session.query(cls)\
338 358 .filter(cls.users_group_name.ilike(group_name))
339 359 else:
340 360 gr = Session.query(UsersGroup)\
341 361 .filter(UsersGroup.users_group_name == group_name)
342 362 if cache:
343 363 gr = gr.options(FromCache("sql_cache_short",
344 364 "get_user_%s" % group_name))
345 365 return gr.scalar()
346 366
347 367
348 368 @classmethod
349 369 def get(cls, users_group_id, cache=False):
350 370 users_group = Session.query(cls)
351 371 if cache:
352 372 users_group = users_group.options(FromCache("sql_cache_short",
353 373 "get_users_group_%s" % users_group_id))
354 374 return users_group.get(users_group_id)
355 375
356 376 @classmethod
357 377 def create(cls, form_data):
358 378 try:
359 379 new_users_group = cls()
360 380 for k, v in form_data.items():
361 381 setattr(new_users_group, k, v)
362 382
363 383 Session.add(new_users_group)
364 384 Session.commit()
385 return new_users_group
365 386 except:
366 387 log.error(traceback.format_exc())
367 388 Session.rollback()
368 389 raise
369 390
370 391 @classmethod
371 392 def update(cls, users_group_id, form_data):
372 393
373 394 try:
374 395 users_group = cls.get(users_group_id, cache=False)
375 396
376 397 for k, v in form_data.items():
377 398 if k == 'users_group_members':
378 399 users_group.members = []
379 400 Session.flush()
380 401 members_list = []
381 402 if v:
382 403 for u_id in set(v):
383 404 members_list.append(UsersGroupMember(
384 405 users_group_id,
385 406 u_id))
386 407 setattr(users_group, 'members', members_list)
387 408 setattr(users_group, k, v)
388 409
389 410 Session.add(users_group)
390 411 Session.commit()
391 412 except:
392 413 log.error(traceback.format_exc())
393 414 Session.rollback()
394 415 raise
395 416
396 417 @classmethod
397 418 def delete(cls, users_group_id):
398 419 try:
399 420
400 421 # check if this group is not assigned to repo
401 422 assigned_groups = UsersGroupRepoToPerm.query()\
402 423 .filter(UsersGroupRepoToPerm.users_group_id ==
403 424 users_group_id).all()
404 425
405 426 if assigned_groups:
406 427 raise UsersGroupsAssignedException('Group assigned to %s' %
407 428 assigned_groups)
408 429
409 430 users_group = cls.get(users_group_id, cache=False)
410 431 Session.delete(users_group)
411 432 Session.commit()
412 433 except:
413 434 log.error(traceback.format_exc())
414 435 Session.rollback()
415 436 raise
416 437
417 438
418 439 class UsersGroupMember(Base, BaseModel):
419 440 __tablename__ = 'users_groups_members'
420 441 __table_args__ = {'extend_existing':True}
421 442
422 443 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
423 444 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
424 445 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
425 446
426 447 user = relationship('User', lazy='joined')
427 448 users_group = relationship('UsersGroup')
428 449
429 450 def __init__(self, gr_id='', u_id=''):
430 451 self.users_group_id = gr_id
431 452 self.user_id = u_id
432 453
433 454 class Repository(Base, BaseModel):
434 455 __tablename__ = 'repositories'
435 456 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
436 457
437 458 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
438 459 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
439 460 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
440 461 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
441 462 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
442 463 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
443 464 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
444 465 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
445 466 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
446 467 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
447 468
448 469 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
449 470 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
450 471
451 472
452 473 user = relationship('User')
453 474 fork = relationship('Repository', remote_side=repo_id)
454 475 group = relationship('Group')
455 476 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
456 477 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
457 478 stats = relationship('Statistics', cascade='all', uselist=False)
458 479
459 480 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
460 481
461 482 logs = relationship('UserLog', cascade='all')
462 483
463 484 def __repr__(self):
464 485 return "<%s('%s:%s')>" % (self.__class__.__name__,
465 486 self.repo_id, self.repo_name)
466 487
467 488 @classmethod
468 489 def by_repo_name(cls, repo_name):
469 490 q = Session.query(cls).filter(cls.repo_name == repo_name)
470 491
471 492 q = q.options(joinedload(Repository.fork))\
472 493 .options(joinedload(Repository.user))\
473 494 .options(joinedload(Repository.group))\
474 495
475 496 return q.one()
476 497
477 498 @classmethod
478 499 def get_repo_forks(cls, repo_id):
479 500 return Session.query(cls).filter(Repository.fork_id == repo_id)
480 501
481 502 @property
482 503 def just_name(self):
483 504 return self.repo_name.split(os.sep)[-1]
484 505
485 506 @property
486 507 def groups_with_parents(self):
487 508 groups = []
488 509 if self.group is None:
489 510 return groups
490 511
491 512 cur_gr = self.group
492 513 groups.insert(0, cur_gr)
493 514 while 1:
494 515 gr = getattr(cur_gr, 'parent_group', None)
495 516 cur_gr = cur_gr.parent_group
496 517 if gr is None:
497 518 break
498 519 groups.insert(0, gr)
499 520
500 521 return groups
501 522
502 523 @property
503 524 def groups_and_repo(self):
504 525 return self.groups_with_parents, self.just_name
505 526
506 527 @LazyProperty
507 528 def repo_path(self):
508 529 """
509 530 Returns base full path for that repository means where it actually
510 531 exists on a filesystem
511 532 """
512 533 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
513 534 q.options(FromCache("sql_cache_short", "repository_repo_path"))
514 535 return q.one().ui_value
515 536
516 537 @property
517 538 def repo_full_path(self):
518 539 p = [self.repo_path]
519 540 # we need to split the name by / since this is how we store the
520 541 # names in the database, but that eventually needs to be converted
521 542 # into a valid system path
522 543 p += self.repo_name.split('/')
523 544 return os.path.join(*p)
524 545
525 546 @property
526 547 def _ui(self):
527 548 """
528 549 Creates an db based ui object for this repository
529 550 """
530 551 from mercurial import ui
531 552 from mercurial import config
532 553 baseui = ui.ui()
533 554
534 555 #clean the baseui object
535 556 baseui._ocfg = config.config()
536 557 baseui._ucfg = config.config()
537 558 baseui._tcfg = config.config()
538 559
539 560
540 561 ret = Session.query(RhodeCodeUi)\
541 562 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
542 563
543 564 hg_ui = ret
544 565 for ui_ in hg_ui:
545 566 if ui_.ui_active:
546 567 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
547 568 ui_.ui_key, ui_.ui_value)
548 569 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
549 570
550 571 return baseui
551 572
552 573 #==========================================================================
553 574 # SCM PROPERTIES
554 575 #==========================================================================
555 576
556 577 def get_changeset(self, rev):
557 578 return get_changeset_safe(self.scm_instance, rev)
558 579
559 580 @property
560 581 def tip(self):
561 582 return self.get_changeset('tip')
562 583
563 584 @property
564 585 def author(self):
565 586 return self.tip.author
566 587
567 588 @property
568 589 def last_change(self):
569 590 return self.scm_instance.last_change
570 591
571 592 #==========================================================================
572 593 # SCM CACHE INSTANCE
573 594 #==========================================================================
574 595
575 596 @property
576 597 def invalidate(self):
577 598 """
578 599 Returns Invalidation object if this repo should be invalidated
579 600 None otherwise. `cache_active = False` means that this cache
580 601 state is not valid and needs to be invalidated
581 602 """
582 603 return Session.query(CacheInvalidation)\
583 604 .filter(CacheInvalidation.cache_key == self.repo_name)\
584 605 .filter(CacheInvalidation.cache_active == False)\
585 606 .scalar()
586 607
587 608 def set_invalidate(self):
588 609 """
589 610 set a cache for invalidation for this instance
590 611 """
591 612 inv = Session.query(CacheInvalidation)\
592 613 .filter(CacheInvalidation.cache_key == self.repo_name)\
593 614 .scalar()
594 615
595 616 if inv is None:
596 617 inv = CacheInvalidation(self.repo_name)
597 618 inv.cache_active = True
598 619 Session.add(inv)
599 620 Session.commit()
600 621
601 622 @LazyProperty
602 623 def scm_instance(self):
603 624 return self.__get_instance()
604 625
605 626 @property
606 627 def scm_instance_cached(self):
607 628 @cache_region('long_term')
608 629 def _c(repo_name):
609 630 return self.__get_instance()
610 631
611 632 # TODO: remove this trick when beaker 1.6 is released
612 633 # and have fixed this issue with not supporting unicode keys
613 634 rn = safe_str(self.repo_name)
614 635
615 636 inv = self.invalidate
616 637 if inv is not None:
617 638 region_invalidate(_c, None, rn)
618 639 # update our cache
619 640 inv.cache_active = True
620 641 Session.add(inv)
621 642 Session.commit()
622 643
623 644 return _c(rn)
624 645
625 646 def __get_instance(self):
626 647
627 648 repo_full_path = self.repo_full_path
628 649
629 650 try:
630 651 alias = get_scm(repo_full_path)[0]
631 652 log.debug('Creating instance of %s repository', alias)
632 653 backend = get_backend(alias)
633 654 except VCSError:
634 655 log.error(traceback.format_exc())
635 656 log.error('Perhaps this repository is in db and not in '
636 657 'filesystem run rescan repositories with '
637 658 '"destroy old data " option from admin panel')
638 659 return
639 660
640 661 if alias == 'hg':
641 662
642 663 repo = backend(safe_str(repo_full_path), create=False,
643 664 baseui=self._ui)
644 665 #skip hidden web repository
645 666 if repo._get_hidden():
646 667 return
647 668 else:
648 669 repo = backend(repo_full_path, create=False)
649 670
650 671 return repo
651 672
652 673
653 674 class Group(Base, BaseModel):
654 675 __tablename__ = 'groups'
655 676 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
656 677 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
657 678 __mapper_args__ = {'order_by':'group_name'}
658 679
659 680 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
660 681 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
661 682 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
662 683 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
663 684
664 685 parent_group = relationship('Group', remote_side=group_id)
665 686
666 687
667 688 def __init__(self, group_name='', parent_group=None):
668 689 self.group_name = group_name
669 690 self.parent_group = parent_group
670 691
671 692 def __repr__(self):
672 693 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
673 694 self.group_name)
674 695
675 696 @classmethod
676 697 def url_sep(cls):
677 698 return '/'
678 699
679 700 @property
680 701 def parents(self):
681 702 parents_recursion_limit = 5
682 703 groups = []
683 704 if self.parent_group is None:
684 705 return groups
685 706 cur_gr = self.parent_group
686 707 groups.insert(0, cur_gr)
687 708 cnt = 0
688 709 while 1:
689 710 cnt += 1
690 711 gr = getattr(cur_gr, 'parent_group', None)
691 712 cur_gr = cur_gr.parent_group
692 713 if gr is None:
693 714 break
694 715 if cnt == parents_recursion_limit:
695 716 # this will prevent accidental infinit loops
696 717 log.error('group nested more than %s' %
697 718 parents_recursion_limit)
698 719 break
699 720
700 721 groups.insert(0, gr)
701 722 return groups
702 723
703 724 @property
704 725 def children(self):
705 726 return Session.query(Group).filter(Group.parent_group == self)
706 727
707 728 @property
708 729 def full_path(self):
709 730 return Group.url_sep().join([g.group_name for g in self.parents] +
710 731 [self.group_name])
711 732
712 733 @property
713 734 def repositories(self):
714 735 return Session.query(Repository).filter(Repository.group == self)
715 736
716 737 @property
717 738 def repositories_recursive_count(self):
718 739 cnt = self.repositories.count()
719 740
720 741 def children_count(group):
721 742 cnt = 0
722 743 for child in group.children:
723 744 cnt += child.repositories.count()
724 745 cnt += children_count(child)
725 746 return cnt
726 747
727 748 return cnt + children_count(self)
728 749
729 750 class Permission(Base, BaseModel):
730 751 __tablename__ = 'permissions'
731 752 __table_args__ = {'extend_existing':True}
732 753 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
733 754 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
734 755 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
735 756
736 757 def __repr__(self):
737 758 return "<%s('%s:%s')>" % (self.__class__.__name__,
738 759 self.permission_id, self.permission_name)
739 760
740 761 @classmethod
741 762 def get_by_key(cls, key):
742 763 return Session.query(cls).filter(cls.permission_name == key).scalar()
743 764
744 765 class RepoToPerm(Base, BaseModel):
745 766 __tablename__ = 'repo_to_perm'
746 767 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
747 768 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
748 769 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
749 770 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
750 771 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
751 772
752 773 user = relationship('User')
753 774 permission = relationship('Permission')
754 775 repository = relationship('Repository')
755 776
756 777 class UserToPerm(Base, BaseModel):
757 778 __tablename__ = 'user_to_perm'
758 779 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
759 780 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
760 781 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
761 782 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
762 783
763 784 user = relationship('User')
764 785 permission = relationship('Permission')
765 786
766 787 @classmethod
767 788 def has_perm(cls, user_id, perm):
768 789 if not isinstance(perm, Permission):
769 790 raise Exception('perm needs to be an instance of Permission class')
770 791
771 792 return Session.query(cls).filter(cls.user_id == user_id)\
772 793 .filter(cls.permission == perm).scalar() is not None
773 794
774 795 @classmethod
775 796 def grant_perm(cls, user_id, perm):
776 797 if not isinstance(perm, Permission):
777 798 raise Exception('perm needs to be an instance of Permission class')
778 799
779 800 new = cls()
780 801 new.user_id = user_id
781 802 new.permission = perm
782 803 try:
783 804 Session.add(new)
784 805 Session.commit()
785 806 except:
786 807 Session.rollback()
787 808
788 809
789 810 @classmethod
790 811 def revoke_perm(cls, user_id, perm):
791 812 if not isinstance(perm, Permission):
792 813 raise Exception('perm needs to be an instance of Permission class')
793 814
794 815 try:
795 816 Session.query(cls).filter(cls.user_id == user_id)\
796 817 .filter(cls.permission == perm).delete()
797 818 Session.commit()
798 819 except:
799 820 Session.rollback()
800 821
801 822 class UsersGroupRepoToPerm(Base, BaseModel):
802 823 __tablename__ = 'users_group_repo_to_perm'
803 824 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
804 825 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
805 826 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
806 827 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
807 828 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
808 829
809 830 users_group = relationship('UsersGroup')
810 831 permission = relationship('Permission')
811 832 repository = relationship('Repository')
812 833
813 834 def __repr__(self):
814 835 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
815 836
816 837 class UsersGroupToPerm(Base, BaseModel):
817 838 __tablename__ = 'users_group_to_perm'
818 839 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
819 840 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
820 841 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
821 842
822 843 users_group = relationship('UsersGroup')
823 844 permission = relationship('Permission')
824 845
825 846
826 847 @classmethod
827 848 def has_perm(cls, users_group_id, perm):
828 849 if not isinstance(perm, Permission):
829 850 raise Exception('perm needs to be an instance of Permission class')
830 851
831 852 return Session.query(cls).filter(cls.users_group_id ==
832 853 users_group_id)\
833 854 .filter(cls.permission == perm)\
834 855 .scalar() is not None
835 856
836 857 @classmethod
837 858 def grant_perm(cls, users_group_id, perm):
838 859 if not isinstance(perm, Permission):
839 860 raise Exception('perm needs to be an instance of Permission class')
840 861
841 862 new = cls()
842 863 new.users_group_id = users_group_id
843 864 new.permission = perm
844 865 try:
845 866 Session.add(new)
846 867 Session.commit()
847 868 except:
848 869 Session.rollback()
849 870
850 871
851 872 @classmethod
852 873 def revoke_perm(cls, users_group_id, perm):
853 874 if not isinstance(perm, Permission):
854 875 raise Exception('perm needs to be an instance of Permission class')
855 876
856 877 try:
857 878 Session.query(cls).filter(cls.users_group_id == users_group_id)\
858 879 .filter(cls.permission == perm).delete()
859 880 Session.commit()
860 881 except:
861 882 Session.rollback()
862 883
863 884
864 885 class GroupToPerm(Base, BaseModel):
865 886 __tablename__ = 'group_to_perm'
866 887 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
867 888
868 889 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
869 890 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
870 891 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
871 892 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
872 893
873 894 user = relationship('User')
874 895 permission = relationship('Permission')
875 896 group = relationship('Group')
876 897
877 898 class Statistics(Base, BaseModel):
878 899 __tablename__ = 'statistics'
879 900 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
880 901 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
881 902 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
882 903 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
883 904 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
884 905 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
885 906 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
886 907
887 908 repository = relationship('Repository', single_parent=True)
888 909
889 910 class UserFollowing(Base, BaseModel):
890 911 __tablename__ = 'user_followings'
891 912 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
892 913 UniqueConstraint('user_id', 'follows_user_id')
893 914 , {'extend_existing':True})
894 915
895 916 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
896 917 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
897 918 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
898 919 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
899 920 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
900 921
901 922 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
902 923
903 924 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
904 925 follows_repository = relationship('Repository', order_by='Repository.repo_name')
905 926
906 927
907 928 @classmethod
908 929 def get_repo_followers(cls, repo_id):
909 930 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
910 931
911 932 class CacheInvalidation(Base, BaseModel):
912 933 __tablename__ = 'cache_invalidation'
913 934 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
914 935 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
915 936 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
916 937 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
917 938 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
918 939
919 940
920 941 def __init__(self, cache_key, cache_args=''):
921 942 self.cache_key = cache_key
922 943 self.cache_args = cache_args
923 944 self.cache_active = False
924 945
925 946 def __repr__(self):
926 947 return "<%s('%s:%s')>" % (self.__class__.__name__,
927 948 self.cache_id, self.cache_key)
928 949
929 950 class DbMigrateVersion(Base, BaseModel):
930 951 __tablename__ = 'db_migrate_version'
931 952 __table_args__ = {'extend_existing':True}
932 953 repository_id = Column('repository_id', String(250), primary_key=True)
933 954 repository_path = Column('repository_path', Text)
934 955 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now