##// END OF EJS Templates
gists: define and use explicit Mercurial backend to speed up creation and fetching of backend repo
marcink -
r3536:5ee2c115 default
parent child Browse files
Show More
@@ -1,413 +1,412 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2019 RhodeCode GmbH
3 # Copyright (C) 2013-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27
27
28 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
28 from pyramid.httpexceptions import HTTPNotFound, HTTPFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31 from pyramid.response import Response
31 from pyramid.response import Response
32
32
33 from rhodecode.apps._base import BaseAppView
33 from rhodecode.apps._base import BaseAppView
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
35 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
36 from rhodecode.lib.utils2 import time_to_datetime
36 from rhodecode.lib.utils2 import time_to_datetime
37 from rhodecode.lib.ext_json import json
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
38 from rhodecode.lib.vcs.exceptions import VCSError, NodeNotChangedError
39 from rhodecode.model.gist import GistModel
39 from rhodecode.model.gist import GistModel
40 from rhodecode.model.meta import Session
40 from rhodecode.model.meta import Session
41 from rhodecode.model.db import Gist, User, or_
41 from rhodecode.model.db import Gist, User, or_
42 from rhodecode.model import validation_schema
42 from rhodecode.model import validation_schema
43 from rhodecode.model.validation_schema.schemas import gist_schema
43 from rhodecode.model.validation_schema.schemas import gist_schema
44
44
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class GistView(BaseAppView):
49 class GistView(BaseAppView):
50
50
51 def load_default_context(self):
51 def load_default_context(self):
52 _ = self.request.translate
52 _ = self.request.translate
53 c = self._get_local_tmpl_context()
53 c = self._get_local_tmpl_context()
54 c.user = c.auth_user.get_instance()
54 c.user = c.auth_user.get_instance()
55
55
56 c.lifetime_values = [
56 c.lifetime_values = [
57 (-1, _('forever')),
57 (-1, _('forever')),
58 (5, _('5 minutes')),
58 (5, _('5 minutes')),
59 (60, _('1 hour')),
59 (60, _('1 hour')),
60 (60 * 24, _('1 day')),
60 (60 * 24, _('1 day')),
61 (60 * 24 * 30, _('1 month')),
61 (60 * 24 * 30, _('1 month')),
62 ]
62 ]
63
63
64 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
64 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
65 c.acl_options = [
65 c.acl_options = [
66 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
66 (Gist.ACL_LEVEL_PRIVATE, _("Requires registered account")),
67 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
67 (Gist.ACL_LEVEL_PUBLIC, _("Can be accessed by anonymous users"))
68 ]
68 ]
69
69
70
71 return c
70 return c
72
71
73 @LoginRequired()
72 @LoginRequired()
74 @view_config(
73 @view_config(
75 route_name='gists_show', request_method='GET',
74 route_name='gists_show', request_method='GET',
76 renderer='rhodecode:templates/admin/gists/index.mako')
75 renderer='rhodecode:templates/admin/gists/index.mako')
77 def gist_show_all(self):
76 def gist_show_all(self):
78 c = self.load_default_context()
77 c = self.load_default_context()
79
78
80 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
79 not_default_user = self._rhodecode_user.username != User.DEFAULT_USER
81 c.show_private = self.request.GET.get('private') and not_default_user
80 c.show_private = self.request.GET.get('private') and not_default_user
82 c.show_public = self.request.GET.get('public') and not_default_user
81 c.show_public = self.request.GET.get('public') and not_default_user
83 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
82 c.show_all = self.request.GET.get('all') and self._rhodecode_user.admin
84
83
85 gists = _gists = Gist().query()\
84 gists = _gists = Gist().query()\
86 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
85 .filter(or_(Gist.gist_expires == -1, Gist.gist_expires >= time.time()))\
87 .order_by(Gist.created_on.desc())
86 .order_by(Gist.created_on.desc())
88
87
89 c.active = 'public'
88 c.active = 'public'
90 # MY private
89 # MY private
91 if c.show_private and not c.show_public:
90 if c.show_private and not c.show_public:
92 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
91 gists = _gists.filter(Gist.gist_type == Gist.GIST_PRIVATE)\
93 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
92 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
94 c.active = 'my_private'
93 c.active = 'my_private'
95 # MY public
94 # MY public
96 elif c.show_public and not c.show_private:
95 elif c.show_public and not c.show_private:
97 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
96 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)\
98 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
97 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
99 c.active = 'my_public'
98 c.active = 'my_public'
100 # MY public+private
99 # MY public+private
101 elif c.show_private and c.show_public:
100 elif c.show_private and c.show_public:
102 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
101 gists = _gists.filter(or_(Gist.gist_type == Gist.GIST_PUBLIC,
103 Gist.gist_type == Gist.GIST_PRIVATE))\
102 Gist.gist_type == Gist.GIST_PRIVATE))\
104 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
103 .filter(Gist.gist_owner == self._rhodecode_user.user_id)
105 c.active = 'my_all'
104 c.active = 'my_all'
106 # Show all by super-admin
105 # Show all by super-admin
107 elif c.show_all:
106 elif c.show_all:
108 c.active = 'all'
107 c.active = 'all'
109 gists = _gists
108 gists = _gists
110
109
111 # default show ALL public gists
110 # default show ALL public gists
112 if not c.show_public and not c.show_private and not c.show_all:
111 if not c.show_public and not c.show_private and not c.show_all:
113 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
112 gists = _gists.filter(Gist.gist_type == Gist.GIST_PUBLIC)
114 c.active = 'public'
113 c.active = 'public'
115
114
116 _render = self.request.get_partial_renderer(
115 _render = self.request.get_partial_renderer(
117 'rhodecode:templates/data_table/_dt_elements.mako')
116 'rhodecode:templates/data_table/_dt_elements.mako')
118
117
119 data = []
118 data = []
120
119
121 for gist in gists:
120 for gist in gists:
122 data.append({
121 data.append({
123 'created_on': _render('gist_created', gist.created_on),
122 'created_on': _render('gist_created', gist.created_on),
124 'created_on_raw': gist.created_on,
123 'created_on_raw': gist.created_on,
125 'type': _render('gist_type', gist.gist_type),
124 'type': _render('gist_type', gist.gist_type),
126 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
125 'access_id': _render('gist_access_id', gist.gist_access_id, gist.owner.full_contact),
127 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
126 'author': _render('gist_author', gist.owner.full_contact, gist.created_on, gist.gist_expires),
128 'author_raw': h.escape(gist.owner.full_contact),
127 'author_raw': h.escape(gist.owner.full_contact),
129 'expires': _render('gist_expires', gist.gist_expires),
128 'expires': _render('gist_expires', gist.gist_expires),
130 'description': _render('gist_description', gist.gist_description)
129 'description': _render('gist_description', gist.gist_description)
131 })
130 })
132 c.data = json.dumps(data)
131 c.data = json.dumps(data)
133
132
134 return self._get_template_context(c)
133 return self._get_template_context(c)
135
134
136 @LoginRequired()
135 @LoginRequired()
137 @NotAnonymous()
136 @NotAnonymous()
138 @view_config(
137 @view_config(
139 route_name='gists_new', request_method='GET',
138 route_name='gists_new', request_method='GET',
140 renderer='rhodecode:templates/admin/gists/new.mako')
139 renderer='rhodecode:templates/admin/gists/new.mako')
141 def gist_new(self):
140 def gist_new(self):
142 c = self.load_default_context()
141 c = self.load_default_context()
143 return self._get_template_context(c)
142 return self._get_template_context(c)
144
143
145 @LoginRequired()
144 @LoginRequired()
146 @NotAnonymous()
145 @NotAnonymous()
147 @CSRFRequired()
146 @CSRFRequired()
148 @view_config(
147 @view_config(
149 route_name='gists_create', request_method='POST',
148 route_name='gists_create', request_method='POST',
150 renderer='rhodecode:templates/admin/gists/new.mako')
149 renderer='rhodecode:templates/admin/gists/new.mako')
151 def gist_create(self):
150 def gist_create(self):
152 _ = self.request.translate
151 _ = self.request.translate
153 c = self.load_default_context()
152 c = self.load_default_context()
154
153
155 data = dict(self.request.POST)
154 data = dict(self.request.POST)
156 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
155 data['filename'] = data.get('filename') or Gist.DEFAULT_FILENAME
157 data['nodes'] = [{
156 data['nodes'] = [{
158 'filename': data['filename'],
157 'filename': data['filename'],
159 'content': data.get('content'),
158 'content': data.get('content'),
160 'mimetype': data.get('mimetype') # None is autodetect
159 'mimetype': data.get('mimetype') # None is autodetect
161 }]
160 }]
162
161
163 data['gist_type'] = (
162 data['gist_type'] = (
164 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
163 Gist.GIST_PUBLIC if data.get('public') else Gist.GIST_PRIVATE)
165 data['gist_acl_level'] = (
164 data['gist_acl_level'] = (
166 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
165 data.get('gist_acl_level') or Gist.ACL_LEVEL_PRIVATE)
167
166
168 schema = gist_schema.GistSchema().bind(
167 schema = gist_schema.GistSchema().bind(
169 lifetime_options=[x[0] for x in c.lifetime_values])
168 lifetime_options=[x[0] for x in c.lifetime_values])
170
169
171 try:
170 try:
172
171
173 schema_data = schema.deserialize(data)
172 schema_data = schema.deserialize(data)
174 # convert to safer format with just KEYs so we sure no duplicates
173 # convert to safer format with just KEYs so we sure no duplicates
175 schema_data['nodes'] = gist_schema.sequence_to_nodes(
174 schema_data['nodes'] = gist_schema.sequence_to_nodes(
176 schema_data['nodes'])
175 schema_data['nodes'])
177
176
178 gist = GistModel().create(
177 gist = GistModel().create(
179 gist_id=schema_data['gistid'], # custom access id not real ID
178 gist_id=schema_data['gistid'], # custom access id not real ID
180 description=schema_data['description'],
179 description=schema_data['description'],
181 owner=self._rhodecode_user.user_id,
180 owner=self._rhodecode_user.user_id,
182 gist_mapping=schema_data['nodes'],
181 gist_mapping=schema_data['nodes'],
183 gist_type=schema_data['gist_type'],
182 gist_type=schema_data['gist_type'],
184 lifetime=schema_data['lifetime'],
183 lifetime=schema_data['lifetime'],
185 gist_acl_level=schema_data['gist_acl_level']
184 gist_acl_level=schema_data['gist_acl_level']
186 )
185 )
187 Session().commit()
186 Session().commit()
188 new_gist_id = gist.gist_access_id
187 new_gist_id = gist.gist_access_id
189 except validation_schema.Invalid as errors:
188 except validation_schema.Invalid as errors:
190 defaults = data
189 defaults = data
191 errors = errors.asdict()
190 errors = errors.asdict()
192
191
193 if 'nodes.0.content' in errors:
192 if 'nodes.0.content' in errors:
194 errors['content'] = errors['nodes.0.content']
193 errors['content'] = errors['nodes.0.content']
195 del errors['nodes.0.content']
194 del errors['nodes.0.content']
196 if 'nodes.0.filename' in errors:
195 if 'nodes.0.filename' in errors:
197 errors['filename'] = errors['nodes.0.filename']
196 errors['filename'] = errors['nodes.0.filename']
198 del errors['nodes.0.filename']
197 del errors['nodes.0.filename']
199
198
200 data = render('rhodecode:templates/admin/gists/new.mako',
199 data = render('rhodecode:templates/admin/gists/new.mako',
201 self._get_template_context(c), self.request)
200 self._get_template_context(c), self.request)
202 html = formencode.htmlfill.render(
201 html = formencode.htmlfill.render(
203 data,
202 data,
204 defaults=defaults,
203 defaults=defaults,
205 errors=errors,
204 errors=errors,
206 prefix_error=False,
205 prefix_error=False,
207 encoding="UTF-8",
206 encoding="UTF-8",
208 force_defaults=False
207 force_defaults=False
209 )
208 )
210 return Response(html)
209 return Response(html)
211
210
212 except Exception:
211 except Exception:
213 log.exception("Exception while trying to create a gist")
212 log.exception("Exception while trying to create a gist")
214 h.flash(_('Error occurred during gist creation'), category='error')
213 h.flash(_('Error occurred during gist creation'), category='error')
215 raise HTTPFound(h.route_url('gists_new'))
214 raise HTTPFound(h.route_url('gists_new'))
216 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
215 raise HTTPFound(h.route_url('gist_show', gist_id=new_gist_id))
217
216
218 @LoginRequired()
217 @LoginRequired()
219 @NotAnonymous()
218 @NotAnonymous()
220 @CSRFRequired()
219 @CSRFRequired()
221 @view_config(
220 @view_config(
222 route_name='gist_delete', request_method='POST')
221 route_name='gist_delete', request_method='POST')
223 def gist_delete(self):
222 def gist_delete(self):
224 _ = self.request.translate
223 _ = self.request.translate
225 gist_id = self.request.matchdict['gist_id']
224 gist_id = self.request.matchdict['gist_id']
226
225
227 c = self.load_default_context()
226 c = self.load_default_context()
228 c.gist = Gist.get_or_404(gist_id)
227 c.gist = Gist.get_or_404(gist_id)
229
228
230 owner = c.gist.gist_owner == self._rhodecode_user.user_id
229 owner = c.gist.gist_owner == self._rhodecode_user.user_id
231 if not (h.HasPermissionAny('hg.admin')() or owner):
230 if not (h.HasPermissionAny('hg.admin')() or owner):
232 log.warning('Deletion of Gist was forbidden '
231 log.warning('Deletion of Gist was forbidden '
233 'by unauthorized user: `%s`', self._rhodecode_user)
232 'by unauthorized user: `%s`', self._rhodecode_user)
234 raise HTTPNotFound()
233 raise HTTPNotFound()
235
234
236 GistModel().delete(c.gist)
235 GistModel().delete(c.gist)
237 Session().commit()
236 Session().commit()
238 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
237 h.flash(_('Deleted gist %s') % c.gist.gist_access_id, category='success')
239
238
240 raise HTTPFound(h.route_url('gists_show'))
239 raise HTTPFound(h.route_url('gists_show'))
241
240
242 def _get_gist(self, gist_id):
241 def _get_gist(self, gist_id):
243
242
244 gist = Gist.get_or_404(gist_id)
243 gist = Gist.get_or_404(gist_id)
245
244
246 # Check if this gist is expired
245 # Check if this gist is expired
247 if gist.gist_expires != -1:
246 if gist.gist_expires != -1:
248 if time.time() > gist.gist_expires:
247 if time.time() > gist.gist_expires:
249 log.error(
248 log.error(
250 'Gist expired at %s', time_to_datetime(gist.gist_expires))
249 'Gist expired at %s', time_to_datetime(gist.gist_expires))
251 raise HTTPNotFound()
250 raise HTTPNotFound()
252
251
253 # check if this gist requires a login
252 # check if this gist requires a login
254 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
253 is_default_user = self._rhodecode_user.username == User.DEFAULT_USER
255 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
254 if gist.acl_level == Gist.ACL_LEVEL_PRIVATE and is_default_user:
256 log.error("Anonymous user %s tried to access protected gist `%s`",
255 log.error("Anonymous user %s tried to access protected gist `%s`",
257 self._rhodecode_user, gist_id)
256 self._rhodecode_user, gist_id)
258 raise HTTPNotFound()
257 raise HTTPNotFound()
259 return gist
258 return gist
260
259
261 @LoginRequired()
260 @LoginRequired()
262 @view_config(
261 @view_config(
263 route_name='gist_show', request_method='GET',
262 route_name='gist_show', request_method='GET',
264 renderer='rhodecode:templates/admin/gists/show.mako')
263 renderer='rhodecode:templates/admin/gists/show.mako')
265 @view_config(
264 @view_config(
266 route_name='gist_show_rev', request_method='GET',
265 route_name='gist_show_rev', request_method='GET',
267 renderer='rhodecode:templates/admin/gists/show.mako')
266 renderer='rhodecode:templates/admin/gists/show.mako')
268 @view_config(
267 @view_config(
269 route_name='gist_show_formatted', request_method='GET',
268 route_name='gist_show_formatted', request_method='GET',
270 renderer=None)
269 renderer=None)
271 @view_config(
270 @view_config(
272 route_name='gist_show_formatted_path', request_method='GET',
271 route_name='gist_show_formatted_path', request_method='GET',
273 renderer=None)
272 renderer=None)
274 def gist_show(self):
273 def gist_show(self):
275 gist_id = self.request.matchdict['gist_id']
274 gist_id = self.request.matchdict['gist_id']
276
275
277 # TODO(marcink): expose those via matching dict
276 # TODO(marcink): expose those via matching dict
278 revision = self.request.matchdict.get('revision', 'tip')
277 revision = self.request.matchdict.get('revision', 'tip')
279 f_path = self.request.matchdict.get('f_path', None)
278 f_path = self.request.matchdict.get('f_path', None)
280 return_format = self.request.matchdict.get('format')
279 return_format = self.request.matchdict.get('format')
281
280
282 c = self.load_default_context()
281 c = self.load_default_context()
283 c.gist = self._get_gist(gist_id)
282 c.gist = self._get_gist(gist_id)
284 c.render = not self.request.GET.get('no-render', False)
283 c.render = not self.request.GET.get('no-render', False)
285
284
286 try:
285 try:
287 c.file_last_commit, c.files = GistModel().get_gist_files(
286 c.file_last_commit, c.files = GistModel().get_gist_files(
288 gist_id, revision=revision)
287 gist_id, revision=revision)
289 except VCSError:
288 except VCSError:
290 log.exception("Exception in gist show")
289 log.exception("Exception in gist show")
291 raise HTTPNotFound()
290 raise HTTPNotFound()
292
291
293 if return_format == 'raw':
292 if return_format == 'raw':
294 content = '\n\n'.join([f.content for f in c.files
293 content = '\n\n'.join([f.content for f in c.files
295 if (f_path is None or f.path == f_path)])
294 if (f_path is None or f.path == f_path)])
296 response = Response(content)
295 response = Response(content)
297 response.content_type = 'text/plain'
296 response.content_type = 'text/plain'
298 return response
297 return response
299
298
300 return self._get_template_context(c)
299 return self._get_template_context(c)
301
300
302 @LoginRequired()
301 @LoginRequired()
303 @NotAnonymous()
302 @NotAnonymous()
304 @view_config(
303 @view_config(
305 route_name='gist_edit', request_method='GET',
304 route_name='gist_edit', request_method='GET',
306 renderer='rhodecode:templates/admin/gists/edit.mako')
305 renderer='rhodecode:templates/admin/gists/edit.mako')
307 def gist_edit(self):
306 def gist_edit(self):
308 _ = self.request.translate
307 _ = self.request.translate
309 gist_id = self.request.matchdict['gist_id']
308 gist_id = self.request.matchdict['gist_id']
310 c = self.load_default_context()
309 c = self.load_default_context()
311 c.gist = self._get_gist(gist_id)
310 c.gist = self._get_gist(gist_id)
312
311
313 owner = c.gist.gist_owner == self._rhodecode_user.user_id
312 owner = c.gist.gist_owner == self._rhodecode_user.user_id
314 if not (h.HasPermissionAny('hg.admin')() or owner):
313 if not (h.HasPermissionAny('hg.admin')() or owner):
315 raise HTTPNotFound()
314 raise HTTPNotFound()
316
315
317 try:
316 try:
318 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
317 c.file_last_commit, c.files = GistModel().get_gist_files(gist_id)
319 except VCSError:
318 except VCSError:
320 log.exception("Exception in gist edit")
319 log.exception("Exception in gist edit")
321 raise HTTPNotFound()
320 raise HTTPNotFound()
322
321
323 if c.gist.gist_expires == -1:
322 if c.gist.gist_expires == -1:
324 expiry = _('never')
323 expiry = _('never')
325 else:
324 else:
326 # this cannot use timeago, since it's used in select2 as a value
325 # this cannot use timeago, since it's used in select2 as a value
327 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
326 expiry = h.age(h.time_to_datetime(c.gist.gist_expires))
328
327
329 c.lifetime_values.append(
328 c.lifetime_values.append(
330 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
329 (0, _('%(expiry)s - current value') % {'expiry': _(expiry)})
331 )
330 )
332
331
333 return self._get_template_context(c)
332 return self._get_template_context(c)
334
333
335 @LoginRequired()
334 @LoginRequired()
336 @NotAnonymous()
335 @NotAnonymous()
337 @CSRFRequired()
336 @CSRFRequired()
338 @view_config(
337 @view_config(
339 route_name='gist_update', request_method='POST',
338 route_name='gist_update', request_method='POST',
340 renderer='rhodecode:templates/admin/gists/edit.mako')
339 renderer='rhodecode:templates/admin/gists/edit.mako')
341 def gist_update(self):
340 def gist_update(self):
342 _ = self.request.translate
341 _ = self.request.translate
343 gist_id = self.request.matchdict['gist_id']
342 gist_id = self.request.matchdict['gist_id']
344 c = self.load_default_context()
343 c = self.load_default_context()
345 c.gist = self._get_gist(gist_id)
344 c.gist = self._get_gist(gist_id)
346
345
347 owner = c.gist.gist_owner == self._rhodecode_user.user_id
346 owner = c.gist.gist_owner == self._rhodecode_user.user_id
348 if not (h.HasPermissionAny('hg.admin')() or owner):
347 if not (h.HasPermissionAny('hg.admin')() or owner):
349 raise HTTPNotFound()
348 raise HTTPNotFound()
350
349
351 data = peppercorn.parse(self.request.POST.items())
350 data = peppercorn.parse(self.request.POST.items())
352
351
353 schema = gist_schema.GistSchema()
352 schema = gist_schema.GistSchema()
354 schema = schema.bind(
353 schema = schema.bind(
355 # '0' is special value to leave lifetime untouched
354 # '0' is special value to leave lifetime untouched
356 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
355 lifetime_options=[x[0] for x in c.lifetime_values] + [0],
357 )
356 )
358
357
359 try:
358 try:
360 schema_data = schema.deserialize(data)
359 schema_data = schema.deserialize(data)
361 # convert to safer format with just KEYs so we sure no duplicates
360 # convert to safer format with just KEYs so we sure no duplicates
362 schema_data['nodes'] = gist_schema.sequence_to_nodes(
361 schema_data['nodes'] = gist_schema.sequence_to_nodes(
363 schema_data['nodes'])
362 schema_data['nodes'])
364
363
365 GistModel().update(
364 GistModel().update(
366 gist=c.gist,
365 gist=c.gist,
367 description=schema_data['description'],
366 description=schema_data['description'],
368 owner=c.gist.owner,
367 owner=c.gist.owner,
369 gist_mapping=schema_data['nodes'],
368 gist_mapping=schema_data['nodes'],
370 lifetime=schema_data['lifetime'],
369 lifetime=schema_data['lifetime'],
371 gist_acl_level=schema_data['gist_acl_level']
370 gist_acl_level=schema_data['gist_acl_level']
372 )
371 )
373
372
374 Session().commit()
373 Session().commit()
375 h.flash(_('Successfully updated gist content'), category='success')
374 h.flash(_('Successfully updated gist content'), category='success')
376 except NodeNotChangedError:
375 except NodeNotChangedError:
377 # raised if nothing was changed in repo itself. We anyway then
376 # raised if nothing was changed in repo itself. We anyway then
378 # store only DB stuff for gist
377 # store only DB stuff for gist
379 Session().commit()
378 Session().commit()
380 h.flash(_('Successfully updated gist data'), category='success')
379 h.flash(_('Successfully updated gist data'), category='success')
381 except validation_schema.Invalid as errors:
380 except validation_schema.Invalid as errors:
382 errors = h.escape(errors.asdict())
381 errors = h.escape(errors.asdict())
383 h.flash(_('Error occurred during update of gist {}: {}').format(
382 h.flash(_('Error occurred during update of gist {}: {}').format(
384 gist_id, errors), category='error')
383 gist_id, errors), category='error')
385 except Exception:
384 except Exception:
386 log.exception("Exception in gist edit")
385 log.exception("Exception in gist edit")
387 h.flash(_('Error occurred during update of gist %s') % gist_id,
386 h.flash(_('Error occurred during update of gist %s') % gist_id,
388 category='error')
387 category='error')
389
388
390 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
389 raise HTTPFound(h.route_url('gist_show', gist_id=gist_id))
391
390
392 @LoginRequired()
391 @LoginRequired()
393 @NotAnonymous()
392 @NotAnonymous()
394 @view_config(
393 @view_config(
395 route_name='gist_edit_check_revision', request_method='GET',
394 route_name='gist_edit_check_revision', request_method='GET',
396 renderer='json_ext')
395 renderer='json_ext')
397 def gist_edit_check_revision(self):
396 def gist_edit_check_revision(self):
398 _ = self.request.translate
397 _ = self.request.translate
399 gist_id = self.request.matchdict['gist_id']
398 gist_id = self.request.matchdict['gist_id']
400 c = self.load_default_context()
399 c = self.load_default_context()
401 c.gist = self._get_gist(gist_id)
400 c.gist = self._get_gist(gist_id)
402
401
403 last_rev = c.gist.scm_instance().get_commit()
402 last_rev = c.gist.scm_instance().get_commit()
404 success = True
403 success = True
405 revision = self.request.GET.get('revision')
404 revision = self.request.GET.get('revision')
406
405
407 if revision != last_rev.raw_id:
406 if revision != last_rev.raw_id:
408 log.error('Last revision %s is different then submitted %s',
407 log.error('Last revision %s is different then submitted %s',
409 revision, last_rev)
408 revision, last_rev)
410 # our gist has newer version than we
409 # our gist has newer version than we
411 success = False
410 success = False
412
411
413 return {'success': success}
412 return {'success': success}
@@ -1,4974 +1,4981 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Database Models for RhodeCode Enterprise
22 Database Models for RhodeCode Enterprise
23 """
23 """
24
24
25 import re
25 import re
26 import os
26 import os
27 import time
27 import time
28 import hashlib
28 import hashlib
29 import logging
29 import logging
30 import datetime
30 import datetime
31 import warnings
31 import warnings
32 import ipaddress
32 import ipaddress
33 import functools
33 import functools
34 import traceback
34 import traceback
35 import collections
35 import collections
36
36
37 from sqlalchemy import (
37 from sqlalchemy import (
38 or_, and_, not_, func, TypeDecorator, event,
38 or_, and_, not_, func, TypeDecorator, event,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
39 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
40 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
41 Text, Float, PickleType)
41 Text, Float, PickleType)
42 from sqlalchemy.sql.expression import true, false
42 from sqlalchemy.sql.expression import true, false
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
43 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
44 from sqlalchemy.orm import (
44 from sqlalchemy.orm import (
45 relationship, joinedload, class_mapper, validates, aliased)
45 relationship, joinedload, class_mapper, validates, aliased)
46 from sqlalchemy.ext.declarative import declared_attr
46 from sqlalchemy.ext.declarative import declared_attr
47 from sqlalchemy.ext.hybrid import hybrid_property
47 from sqlalchemy.ext.hybrid import hybrid_property
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
48 from sqlalchemy.exc import IntegrityError # pragma: no cover
49 from sqlalchemy.dialects.mysql import LONGTEXT
49 from sqlalchemy.dialects.mysql import LONGTEXT
50 from zope.cachedescriptors.property import Lazy as LazyProperty
50 from zope.cachedescriptors.property import Lazy as LazyProperty
51 from pyramid import compat
51 from pyramid import compat
52 from pyramid.threadlocal import get_current_request
52 from pyramid.threadlocal import get_current_request
53
53
54 from rhodecode.translation import _
54 from rhodecode.translation import _
55 from rhodecode.lib.vcs import get_vcs_instance
55 from rhodecode.lib.vcs import get_vcs_instance
56 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
56 from rhodecode.lib.vcs.backends.base import EmptyCommit, Reference
57 from rhodecode.lib.utils2 import (
57 from rhodecode.lib.utils2 import (
58 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
58 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
59 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
60 glob2re, StrictAttributeDict, cleaned_uri)
60 glob2re, StrictAttributeDict, cleaned_uri)
61 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
61 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
62 JsonRaw
62 JsonRaw
63 from rhodecode.lib.ext_json import json
63 from rhodecode.lib.ext_json import json
64 from rhodecode.lib.caching_query import FromCache
64 from rhodecode.lib.caching_query import FromCache
65 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
65 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
66 from rhodecode.lib.encrypt2 import Encryptor
66 from rhodecode.lib.encrypt2 import Encryptor
67 from rhodecode.model.meta import Base, Session
67 from rhodecode.model.meta import Base, Session
68
68
69 URL_SEP = '/'
69 URL_SEP = '/'
70 log = logging.getLogger(__name__)
70 log = logging.getLogger(__name__)
71
71
72 # =============================================================================
72 # =============================================================================
73 # BASE CLASSES
73 # BASE CLASSES
74 # =============================================================================
74 # =============================================================================
75
75
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
76 # this is propagated from .ini file rhodecode.encrypted_values.secret or
77 # beaker.session.secret if first is not set.
77 # beaker.session.secret if first is not set.
78 # and initialized at environment.py
78 # and initialized at environment.py
79 ENCRYPTION_KEY = None
79 ENCRYPTION_KEY = None
80
80
81 # used to sort permissions by types, '#' used here is not allowed to be in
81 # used to sort permissions by types, '#' used here is not allowed to be in
82 # usernames, and it's very early in sorted string.printable table.
82 # usernames, and it's very early in sorted string.printable table.
83 PERMISSION_TYPE_SORT = {
83 PERMISSION_TYPE_SORT = {
84 'admin': '####',
84 'admin': '####',
85 'write': '###',
85 'write': '###',
86 'read': '##',
86 'read': '##',
87 'none': '#',
87 'none': '#',
88 }
88 }
89
89
90
90
91 def display_user_sort(obj):
91 def display_user_sort(obj):
92 """
92 """
93 Sort function used to sort permissions in .permissions() function of
93 Sort function used to sort permissions in .permissions() function of
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
94 Repository, RepoGroup, UserGroup. Also it put the default user in front
95 of all other resources
95 of all other resources
96 """
96 """
97
97
98 if obj.username == User.DEFAULT_USER:
98 if obj.username == User.DEFAULT_USER:
99 return '#####'
99 return '#####'
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
100 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
101 return prefix + obj.username
101 return prefix + obj.username
102
102
103
103
104 def display_user_group_sort(obj):
104 def display_user_group_sort(obj):
105 """
105 """
106 Sort function used to sort permissions in .permissions() function of
106 Sort function used to sort permissions in .permissions() function of
107 Repository, RepoGroup, UserGroup. Also it put the default user in front
107 Repository, RepoGroup, UserGroup. Also it put the default user in front
108 of all other resources
108 of all other resources
109 """
109 """
110
110
111 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
111 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
112 return prefix + obj.users_group_name
112 return prefix + obj.users_group_name
113
113
114
114
115 def _hash_key(k):
115 def _hash_key(k):
116 return sha1_safe(k)
116 return sha1_safe(k)
117
117
118
118
119 def in_filter_generator(qry, items, limit=500):
119 def in_filter_generator(qry, items, limit=500):
120 """
120 """
121 Splits IN() into multiple with OR
121 Splits IN() into multiple with OR
122 e.g.::
122 e.g.::
123 cnt = Repository.query().filter(
123 cnt = Repository.query().filter(
124 or_(
124 or_(
125 *in_filter_generator(Repository.repo_id, range(100000))
125 *in_filter_generator(Repository.repo_id, range(100000))
126 )).count()
126 )).count()
127 """
127 """
128 if not items:
128 if not items:
129 # empty list will cause empty query which might cause security issues
129 # empty list will cause empty query which might cause security issues
130 # this can lead to hidden unpleasant results
130 # this can lead to hidden unpleasant results
131 items = [-1]
131 items = [-1]
132
132
133 parts = []
133 parts = []
134 for chunk in xrange(0, len(items), limit):
134 for chunk in xrange(0, len(items), limit):
135 parts.append(
135 parts.append(
136 qry.in_(items[chunk: chunk + limit])
136 qry.in_(items[chunk: chunk + limit])
137 )
137 )
138
138
139 return parts
139 return parts
140
140
141
141
142 base_table_args = {
142 base_table_args = {
143 'extend_existing': True,
143 'extend_existing': True,
144 'mysql_engine': 'InnoDB',
144 'mysql_engine': 'InnoDB',
145 'mysql_charset': 'utf8',
145 'mysql_charset': 'utf8',
146 'sqlite_autoincrement': True
146 'sqlite_autoincrement': True
147 }
147 }
148
148
149
149
150 class EncryptedTextValue(TypeDecorator):
150 class EncryptedTextValue(TypeDecorator):
151 """
151 """
152 Special column for encrypted long text data, use like::
152 Special column for encrypted long text data, use like::
153
153
154 value = Column("encrypted_value", EncryptedValue(), nullable=False)
154 value = Column("encrypted_value", EncryptedValue(), nullable=False)
155
155
156 This column is intelligent so if value is in unencrypted form it return
156 This column is intelligent so if value is in unencrypted form it return
157 unencrypted form, but on save it always encrypts
157 unencrypted form, but on save it always encrypts
158 """
158 """
159 impl = Text
159 impl = Text
160
160
161 def process_bind_param(self, value, dialect):
161 def process_bind_param(self, value, dialect):
162 """
162 """
163 Setter for storing value
163 Setter for storing value
164 """
164 """
165 import rhodecode
165 import rhodecode
166 if not value:
166 if not value:
167 return value
167 return value
168
168
169 # protect against double encrypting if values is already encrypted
169 # protect against double encrypting if values is already encrypted
170 if value.startswith('enc$aes$') \
170 if value.startswith('enc$aes$') \
171 or value.startswith('enc$aes_hmac$') \
171 or value.startswith('enc$aes_hmac$') \
172 or value.startswith('enc2$'):
172 or value.startswith('enc2$'):
173 raise ValueError('value needs to be in unencrypted format, '
173 raise ValueError('value needs to be in unencrypted format, '
174 'ie. not starting with enc$ or enc2$')
174 'ie. not starting with enc$ or enc2$')
175
175
176 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
176 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
177 if algo == 'aes':
177 if algo == 'aes':
178 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
178 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
179 elif algo == 'fernet':
179 elif algo == 'fernet':
180 return Encryptor(ENCRYPTION_KEY).encrypt(value)
180 return Encryptor(ENCRYPTION_KEY).encrypt(value)
181 else:
181 else:
182 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
182 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
183
183
184 def process_result_value(self, value, dialect):
184 def process_result_value(self, value, dialect):
185 """
185 """
186 Getter for retrieving value
186 Getter for retrieving value
187 """
187 """
188
188
189 import rhodecode
189 import rhodecode
190 if not value:
190 if not value:
191 return value
191 return value
192
192
193 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
193 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
194 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
194 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
195 if algo == 'aes':
195 if algo == 'aes':
196 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
196 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
197 elif algo == 'fernet':
197 elif algo == 'fernet':
198 return Encryptor(ENCRYPTION_KEY).decrypt(value)
198 return Encryptor(ENCRYPTION_KEY).decrypt(value)
199 else:
199 else:
200 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
200 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
201 return decrypted_data
201 return decrypted_data
202
202
203
203
204 class BaseModel(object):
204 class BaseModel(object):
205 """
205 """
206 Base Model for all classes
206 Base Model for all classes
207 """
207 """
208
208
209 @classmethod
209 @classmethod
210 def _get_keys(cls):
210 def _get_keys(cls):
211 """return column names for this model """
211 """return column names for this model """
212 return class_mapper(cls).c.keys()
212 return class_mapper(cls).c.keys()
213
213
214 def get_dict(self):
214 def get_dict(self):
215 """
215 """
216 return dict with keys and values corresponding
216 return dict with keys and values corresponding
217 to this model data """
217 to this model data """
218
218
219 d = {}
219 d = {}
220 for k in self._get_keys():
220 for k in self._get_keys():
221 d[k] = getattr(self, k)
221 d[k] = getattr(self, k)
222
222
223 # also use __json__() if present to get additional fields
223 # also use __json__() if present to get additional fields
224 _json_attr = getattr(self, '__json__', None)
224 _json_attr = getattr(self, '__json__', None)
225 if _json_attr:
225 if _json_attr:
226 # update with attributes from __json__
226 # update with attributes from __json__
227 if callable(_json_attr):
227 if callable(_json_attr):
228 _json_attr = _json_attr()
228 _json_attr = _json_attr()
229 for k, val in _json_attr.iteritems():
229 for k, val in _json_attr.iteritems():
230 d[k] = val
230 d[k] = val
231 return d
231 return d
232
232
233 def get_appstruct(self):
233 def get_appstruct(self):
234 """return list with keys and values tuples corresponding
234 """return list with keys and values tuples corresponding
235 to this model data """
235 to this model data """
236
236
237 lst = []
237 lst = []
238 for k in self._get_keys():
238 for k in self._get_keys():
239 lst.append((k, getattr(self, k),))
239 lst.append((k, getattr(self, k),))
240 return lst
240 return lst
241
241
242 def populate_obj(self, populate_dict):
242 def populate_obj(self, populate_dict):
243 """populate model with data from given populate_dict"""
243 """populate model with data from given populate_dict"""
244
244
245 for k in self._get_keys():
245 for k in self._get_keys():
246 if k in populate_dict:
246 if k in populate_dict:
247 setattr(self, k, populate_dict[k])
247 setattr(self, k, populate_dict[k])
248
248
249 @classmethod
249 @classmethod
250 def query(cls):
250 def query(cls):
251 return Session().query(cls)
251 return Session().query(cls)
252
252
253 @classmethod
253 @classmethod
254 def get(cls, id_):
254 def get(cls, id_):
255 if id_:
255 if id_:
256 return cls.query().get(id_)
256 return cls.query().get(id_)
257
257
258 @classmethod
258 @classmethod
259 def get_or_404(cls, id_):
259 def get_or_404(cls, id_):
260 from pyramid.httpexceptions import HTTPNotFound
260 from pyramid.httpexceptions import HTTPNotFound
261
261
262 try:
262 try:
263 id_ = int(id_)
263 id_ = int(id_)
264 except (TypeError, ValueError):
264 except (TypeError, ValueError):
265 raise HTTPNotFound()
265 raise HTTPNotFound()
266
266
267 res = cls.query().get(id_)
267 res = cls.query().get(id_)
268 if not res:
268 if not res:
269 raise HTTPNotFound()
269 raise HTTPNotFound()
270 return res
270 return res
271
271
272 @classmethod
272 @classmethod
273 def getAll(cls):
273 def getAll(cls):
274 # deprecated and left for backward compatibility
274 # deprecated and left for backward compatibility
275 return cls.get_all()
275 return cls.get_all()
276
276
277 @classmethod
277 @classmethod
278 def get_all(cls):
278 def get_all(cls):
279 return cls.query().all()
279 return cls.query().all()
280
280
281 @classmethod
281 @classmethod
282 def delete(cls, id_):
282 def delete(cls, id_):
283 obj = cls.query().get(id_)
283 obj = cls.query().get(id_)
284 Session().delete(obj)
284 Session().delete(obj)
285
285
286 @classmethod
286 @classmethod
287 def identity_cache(cls, session, attr_name, value):
287 def identity_cache(cls, session, attr_name, value):
288 exist_in_session = []
288 exist_in_session = []
289 for (item_cls, pkey), instance in session.identity_map.items():
289 for (item_cls, pkey), instance in session.identity_map.items():
290 if cls == item_cls and getattr(instance, attr_name) == value:
290 if cls == item_cls and getattr(instance, attr_name) == value:
291 exist_in_session.append(instance)
291 exist_in_session.append(instance)
292 if exist_in_session:
292 if exist_in_session:
293 if len(exist_in_session) == 1:
293 if len(exist_in_session) == 1:
294 return exist_in_session[0]
294 return exist_in_session[0]
295 log.exception(
295 log.exception(
296 'multiple objects with attr %s and '
296 'multiple objects with attr %s and '
297 'value %s found with same name: %r',
297 'value %s found with same name: %r',
298 attr_name, value, exist_in_session)
298 attr_name, value, exist_in_session)
299
299
300 def __repr__(self):
300 def __repr__(self):
301 if hasattr(self, '__unicode__'):
301 if hasattr(self, '__unicode__'):
302 # python repr needs to return str
302 # python repr needs to return str
303 try:
303 try:
304 return safe_str(self.__unicode__())
304 return safe_str(self.__unicode__())
305 except UnicodeDecodeError:
305 except UnicodeDecodeError:
306 pass
306 pass
307 return '<DB:%s>' % (self.__class__.__name__)
307 return '<DB:%s>' % (self.__class__.__name__)
308
308
309
309
310 class RhodeCodeSetting(Base, BaseModel):
310 class RhodeCodeSetting(Base, BaseModel):
311 __tablename__ = 'rhodecode_settings'
311 __tablename__ = 'rhodecode_settings'
312 __table_args__ = (
312 __table_args__ = (
313 UniqueConstraint('app_settings_name'),
313 UniqueConstraint('app_settings_name'),
314 base_table_args
314 base_table_args
315 )
315 )
316
316
317 SETTINGS_TYPES = {
317 SETTINGS_TYPES = {
318 'str': safe_str,
318 'str': safe_str,
319 'int': safe_int,
319 'int': safe_int,
320 'unicode': safe_unicode,
320 'unicode': safe_unicode,
321 'bool': str2bool,
321 'bool': str2bool,
322 'list': functools.partial(aslist, sep=',')
322 'list': functools.partial(aslist, sep=',')
323 }
323 }
324 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
324 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
325 GLOBAL_CONF_KEY = 'app_settings'
325 GLOBAL_CONF_KEY = 'app_settings'
326
326
327 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
327 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
328 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
328 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
329 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
329 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
330 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
330 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
331
331
332 def __init__(self, key='', val='', type='unicode'):
332 def __init__(self, key='', val='', type='unicode'):
333 self.app_settings_name = key
333 self.app_settings_name = key
334 self.app_settings_type = type
334 self.app_settings_type = type
335 self.app_settings_value = val
335 self.app_settings_value = val
336
336
337 @validates('_app_settings_value')
337 @validates('_app_settings_value')
338 def validate_settings_value(self, key, val):
338 def validate_settings_value(self, key, val):
339 assert type(val) == unicode
339 assert type(val) == unicode
340 return val
340 return val
341
341
342 @hybrid_property
342 @hybrid_property
343 def app_settings_value(self):
343 def app_settings_value(self):
344 v = self._app_settings_value
344 v = self._app_settings_value
345 _type = self.app_settings_type
345 _type = self.app_settings_type
346 if _type:
346 if _type:
347 _type = self.app_settings_type.split('.')[0]
347 _type = self.app_settings_type.split('.')[0]
348 # decode the encrypted value
348 # decode the encrypted value
349 if 'encrypted' in self.app_settings_type:
349 if 'encrypted' in self.app_settings_type:
350 cipher = EncryptedTextValue()
350 cipher = EncryptedTextValue()
351 v = safe_unicode(cipher.process_result_value(v, None))
351 v = safe_unicode(cipher.process_result_value(v, None))
352
352
353 converter = self.SETTINGS_TYPES.get(_type) or \
353 converter = self.SETTINGS_TYPES.get(_type) or \
354 self.SETTINGS_TYPES['unicode']
354 self.SETTINGS_TYPES['unicode']
355 return converter(v)
355 return converter(v)
356
356
357 @app_settings_value.setter
357 @app_settings_value.setter
358 def app_settings_value(self, val):
358 def app_settings_value(self, val):
359 """
359 """
360 Setter that will always make sure we use unicode in app_settings_value
360 Setter that will always make sure we use unicode in app_settings_value
361
361
362 :param val:
362 :param val:
363 """
363 """
364 val = safe_unicode(val)
364 val = safe_unicode(val)
365 # encode the encrypted value
365 # encode the encrypted value
366 if 'encrypted' in self.app_settings_type:
366 if 'encrypted' in self.app_settings_type:
367 cipher = EncryptedTextValue()
367 cipher = EncryptedTextValue()
368 val = safe_unicode(cipher.process_bind_param(val, None))
368 val = safe_unicode(cipher.process_bind_param(val, None))
369 self._app_settings_value = val
369 self._app_settings_value = val
370
370
371 @hybrid_property
371 @hybrid_property
372 def app_settings_type(self):
372 def app_settings_type(self):
373 return self._app_settings_type
373 return self._app_settings_type
374
374
375 @app_settings_type.setter
375 @app_settings_type.setter
376 def app_settings_type(self, val):
376 def app_settings_type(self, val):
377 if val.split('.')[0] not in self.SETTINGS_TYPES:
377 if val.split('.')[0] not in self.SETTINGS_TYPES:
378 raise Exception('type must be one of %s got %s'
378 raise Exception('type must be one of %s got %s'
379 % (self.SETTINGS_TYPES.keys(), val))
379 % (self.SETTINGS_TYPES.keys(), val))
380 self._app_settings_type = val
380 self._app_settings_type = val
381
381
382 @classmethod
382 @classmethod
383 def get_by_prefix(cls, prefix):
383 def get_by_prefix(cls, prefix):
384 return RhodeCodeSetting.query()\
384 return RhodeCodeSetting.query()\
385 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
385 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
386 .all()
386 .all()
387
387
388 def __unicode__(self):
388 def __unicode__(self):
389 return u"<%s('%s:%s[%s]')>" % (
389 return u"<%s('%s:%s[%s]')>" % (
390 self.__class__.__name__,
390 self.__class__.__name__,
391 self.app_settings_name, self.app_settings_value,
391 self.app_settings_name, self.app_settings_value,
392 self.app_settings_type
392 self.app_settings_type
393 )
393 )
394
394
395
395
396 class RhodeCodeUi(Base, BaseModel):
396 class RhodeCodeUi(Base, BaseModel):
397 __tablename__ = 'rhodecode_ui'
397 __tablename__ = 'rhodecode_ui'
398 __table_args__ = (
398 __table_args__ = (
399 UniqueConstraint('ui_key'),
399 UniqueConstraint('ui_key'),
400 base_table_args
400 base_table_args
401 )
401 )
402
402
403 HOOK_REPO_SIZE = 'changegroup.repo_size'
403 HOOK_REPO_SIZE = 'changegroup.repo_size'
404 # HG
404 # HG
405 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
405 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
406 HOOK_PULL = 'outgoing.pull_logger'
406 HOOK_PULL = 'outgoing.pull_logger'
407 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
407 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
408 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
408 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
409 HOOK_PUSH = 'changegroup.push_logger'
409 HOOK_PUSH = 'changegroup.push_logger'
410 HOOK_PUSH_KEY = 'pushkey.key_push'
410 HOOK_PUSH_KEY = 'pushkey.key_push'
411
411
412 # TODO: johbo: Unify way how hooks are configured for git and hg,
412 # TODO: johbo: Unify way how hooks are configured for git and hg,
413 # git part is currently hardcoded.
413 # git part is currently hardcoded.
414
414
415 # SVN PATTERNS
415 # SVN PATTERNS
416 SVN_BRANCH_ID = 'vcs_svn_branch'
416 SVN_BRANCH_ID = 'vcs_svn_branch'
417 SVN_TAG_ID = 'vcs_svn_tag'
417 SVN_TAG_ID = 'vcs_svn_tag'
418
418
419 ui_id = Column(
419 ui_id = Column(
420 "ui_id", Integer(), nullable=False, unique=True, default=None,
420 "ui_id", Integer(), nullable=False, unique=True, default=None,
421 primary_key=True)
421 primary_key=True)
422 ui_section = Column(
422 ui_section = Column(
423 "ui_section", String(255), nullable=True, unique=None, default=None)
423 "ui_section", String(255), nullable=True, unique=None, default=None)
424 ui_key = Column(
424 ui_key = Column(
425 "ui_key", String(255), nullable=True, unique=None, default=None)
425 "ui_key", String(255), nullable=True, unique=None, default=None)
426 ui_value = Column(
426 ui_value = Column(
427 "ui_value", String(255), nullable=True, unique=None, default=None)
427 "ui_value", String(255), nullable=True, unique=None, default=None)
428 ui_active = Column(
428 ui_active = Column(
429 "ui_active", Boolean(), nullable=True, unique=None, default=True)
429 "ui_active", Boolean(), nullable=True, unique=None, default=True)
430
430
431 def __repr__(self):
431 def __repr__(self):
432 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
432 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
433 self.ui_key, self.ui_value)
433 self.ui_key, self.ui_value)
434
434
435
435
436 class RepoRhodeCodeSetting(Base, BaseModel):
436 class RepoRhodeCodeSetting(Base, BaseModel):
437 __tablename__ = 'repo_rhodecode_settings'
437 __tablename__ = 'repo_rhodecode_settings'
438 __table_args__ = (
438 __table_args__ = (
439 UniqueConstraint(
439 UniqueConstraint(
440 'app_settings_name', 'repository_id',
440 'app_settings_name', 'repository_id',
441 name='uq_repo_rhodecode_setting_name_repo_id'),
441 name='uq_repo_rhodecode_setting_name_repo_id'),
442 base_table_args
442 base_table_args
443 )
443 )
444
444
445 repository_id = Column(
445 repository_id = Column(
446 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
446 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
447 nullable=False)
447 nullable=False)
448 app_settings_id = Column(
448 app_settings_id = Column(
449 "app_settings_id", Integer(), nullable=False, unique=True,
449 "app_settings_id", Integer(), nullable=False, unique=True,
450 default=None, primary_key=True)
450 default=None, primary_key=True)
451 app_settings_name = Column(
451 app_settings_name = Column(
452 "app_settings_name", String(255), nullable=True, unique=None,
452 "app_settings_name", String(255), nullable=True, unique=None,
453 default=None)
453 default=None)
454 _app_settings_value = Column(
454 _app_settings_value = Column(
455 "app_settings_value", String(4096), nullable=True, unique=None,
455 "app_settings_value", String(4096), nullable=True, unique=None,
456 default=None)
456 default=None)
457 _app_settings_type = Column(
457 _app_settings_type = Column(
458 "app_settings_type", String(255), nullable=True, unique=None,
458 "app_settings_type", String(255), nullable=True, unique=None,
459 default=None)
459 default=None)
460
460
461 repository = relationship('Repository')
461 repository = relationship('Repository')
462
462
463 def __init__(self, repository_id, key='', val='', type='unicode'):
463 def __init__(self, repository_id, key='', val='', type='unicode'):
464 self.repository_id = repository_id
464 self.repository_id = repository_id
465 self.app_settings_name = key
465 self.app_settings_name = key
466 self.app_settings_type = type
466 self.app_settings_type = type
467 self.app_settings_value = val
467 self.app_settings_value = val
468
468
469 @validates('_app_settings_value')
469 @validates('_app_settings_value')
470 def validate_settings_value(self, key, val):
470 def validate_settings_value(self, key, val):
471 assert type(val) == unicode
471 assert type(val) == unicode
472 return val
472 return val
473
473
474 @hybrid_property
474 @hybrid_property
475 def app_settings_value(self):
475 def app_settings_value(self):
476 v = self._app_settings_value
476 v = self._app_settings_value
477 type_ = self.app_settings_type
477 type_ = self.app_settings_type
478 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
478 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
479 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
479 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
480 return converter(v)
480 return converter(v)
481
481
482 @app_settings_value.setter
482 @app_settings_value.setter
483 def app_settings_value(self, val):
483 def app_settings_value(self, val):
484 """
484 """
485 Setter that will always make sure we use unicode in app_settings_value
485 Setter that will always make sure we use unicode in app_settings_value
486
486
487 :param val:
487 :param val:
488 """
488 """
489 self._app_settings_value = safe_unicode(val)
489 self._app_settings_value = safe_unicode(val)
490
490
491 @hybrid_property
491 @hybrid_property
492 def app_settings_type(self):
492 def app_settings_type(self):
493 return self._app_settings_type
493 return self._app_settings_type
494
494
495 @app_settings_type.setter
495 @app_settings_type.setter
496 def app_settings_type(self, val):
496 def app_settings_type(self, val):
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
497 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
498 if val not in SETTINGS_TYPES:
498 if val not in SETTINGS_TYPES:
499 raise Exception('type must be one of %s got %s'
499 raise Exception('type must be one of %s got %s'
500 % (SETTINGS_TYPES.keys(), val))
500 % (SETTINGS_TYPES.keys(), val))
501 self._app_settings_type = val
501 self._app_settings_type = val
502
502
503 def __unicode__(self):
503 def __unicode__(self):
504 return u"<%s('%s:%s:%s[%s]')>" % (
504 return u"<%s('%s:%s:%s[%s]')>" % (
505 self.__class__.__name__, self.repository.repo_name,
505 self.__class__.__name__, self.repository.repo_name,
506 self.app_settings_name, self.app_settings_value,
506 self.app_settings_name, self.app_settings_value,
507 self.app_settings_type
507 self.app_settings_type
508 )
508 )
509
509
510
510
511 class RepoRhodeCodeUi(Base, BaseModel):
511 class RepoRhodeCodeUi(Base, BaseModel):
512 __tablename__ = 'repo_rhodecode_ui'
512 __tablename__ = 'repo_rhodecode_ui'
513 __table_args__ = (
513 __table_args__ = (
514 UniqueConstraint(
514 UniqueConstraint(
515 'repository_id', 'ui_section', 'ui_key',
515 'repository_id', 'ui_section', 'ui_key',
516 name='uq_repo_rhodecode_ui_repository_id_section_key'),
516 name='uq_repo_rhodecode_ui_repository_id_section_key'),
517 base_table_args
517 base_table_args
518 )
518 )
519
519
520 repository_id = Column(
520 repository_id = Column(
521 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
521 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
522 nullable=False)
522 nullable=False)
523 ui_id = Column(
523 ui_id = Column(
524 "ui_id", Integer(), nullable=False, unique=True, default=None,
524 "ui_id", Integer(), nullable=False, unique=True, default=None,
525 primary_key=True)
525 primary_key=True)
526 ui_section = Column(
526 ui_section = Column(
527 "ui_section", String(255), nullable=True, unique=None, default=None)
527 "ui_section", String(255), nullable=True, unique=None, default=None)
528 ui_key = Column(
528 ui_key = Column(
529 "ui_key", String(255), nullable=True, unique=None, default=None)
529 "ui_key", String(255), nullable=True, unique=None, default=None)
530 ui_value = Column(
530 ui_value = Column(
531 "ui_value", String(255), nullable=True, unique=None, default=None)
531 "ui_value", String(255), nullable=True, unique=None, default=None)
532 ui_active = Column(
532 ui_active = Column(
533 "ui_active", Boolean(), nullable=True, unique=None, default=True)
533 "ui_active", Boolean(), nullable=True, unique=None, default=True)
534
534
535 repository = relationship('Repository')
535 repository = relationship('Repository')
536
536
537 def __repr__(self):
537 def __repr__(self):
538 return '<%s[%s:%s]%s=>%s]>' % (
538 return '<%s[%s:%s]%s=>%s]>' % (
539 self.__class__.__name__, self.repository.repo_name,
539 self.__class__.__name__, self.repository.repo_name,
540 self.ui_section, self.ui_key, self.ui_value)
540 self.ui_section, self.ui_key, self.ui_value)
541
541
542
542
543 class User(Base, BaseModel):
543 class User(Base, BaseModel):
544 __tablename__ = 'users'
544 __tablename__ = 'users'
545 __table_args__ = (
545 __table_args__ = (
546 UniqueConstraint('username'), UniqueConstraint('email'),
546 UniqueConstraint('username'), UniqueConstraint('email'),
547 Index('u_username_idx', 'username'),
547 Index('u_username_idx', 'username'),
548 Index('u_email_idx', 'email'),
548 Index('u_email_idx', 'email'),
549 base_table_args
549 base_table_args
550 )
550 )
551
551
552 DEFAULT_USER = 'default'
552 DEFAULT_USER = 'default'
553 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
553 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
554 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
554 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
555
555
556 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
556 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
557 username = Column("username", String(255), nullable=True, unique=None, default=None)
557 username = Column("username", String(255), nullable=True, unique=None, default=None)
558 password = Column("password", String(255), nullable=True, unique=None, default=None)
558 password = Column("password", String(255), nullable=True, unique=None, default=None)
559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
559 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
560 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
560 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
561 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
561 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
562 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
562 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
563 _email = Column("email", String(255), nullable=True, unique=None, default=None)
563 _email = Column("email", String(255), nullable=True, unique=None, default=None)
564 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
564 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
565 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
565 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
566
566
567 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
567 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
568 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
568 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
569 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
569 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
570 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
570 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
571 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
571 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
572 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
572 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
573
573
574 user_log = relationship('UserLog')
574 user_log = relationship('UserLog')
575 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
575 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
576
576
577 repositories = relationship('Repository')
577 repositories = relationship('Repository')
578 repository_groups = relationship('RepoGroup')
578 repository_groups = relationship('RepoGroup')
579 user_groups = relationship('UserGroup')
579 user_groups = relationship('UserGroup')
580
580
581 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
581 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
582 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
582 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
583
583
584 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
584 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
585 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
585 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
586 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
586 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all')
587
587
588 group_member = relationship('UserGroupMember', cascade='all')
588 group_member = relationship('UserGroupMember', cascade='all')
589
589
590 notifications = relationship('UserNotification', cascade='all')
590 notifications = relationship('UserNotification', cascade='all')
591 # notifications assigned to this user
591 # notifications assigned to this user
592 user_created_notifications = relationship('Notification', cascade='all')
592 user_created_notifications = relationship('Notification', cascade='all')
593 # comments created by this user
593 # comments created by this user
594 user_comments = relationship('ChangesetComment', cascade='all')
594 user_comments = relationship('ChangesetComment', cascade='all')
595 # user profile extra info
595 # user profile extra info
596 user_emails = relationship('UserEmailMap', cascade='all')
596 user_emails = relationship('UserEmailMap', cascade='all')
597 user_ip_map = relationship('UserIpMap', cascade='all')
597 user_ip_map = relationship('UserIpMap', cascade='all')
598 user_auth_tokens = relationship('UserApiKeys', cascade='all')
598 user_auth_tokens = relationship('UserApiKeys', cascade='all')
599 user_ssh_keys = relationship('UserSshKeys', cascade='all')
599 user_ssh_keys = relationship('UserSshKeys', cascade='all')
600
600
601 # gists
601 # gists
602 user_gists = relationship('Gist', cascade='all')
602 user_gists = relationship('Gist', cascade='all')
603 # user pull requests
603 # user pull requests
604 user_pull_requests = relationship('PullRequest', cascade='all')
604 user_pull_requests = relationship('PullRequest', cascade='all')
605 # external identities
605 # external identities
606 extenal_identities = relationship(
606 extenal_identities = relationship(
607 'ExternalIdentity',
607 'ExternalIdentity',
608 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
608 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
609 cascade='all')
609 cascade='all')
610 # review rules
610 # review rules
611 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
611 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
612
612
613 def __unicode__(self):
613 def __unicode__(self):
614 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
614 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
615 self.user_id, self.username)
615 self.user_id, self.username)
616
616
617 @hybrid_property
617 @hybrid_property
618 def email(self):
618 def email(self):
619 return self._email
619 return self._email
620
620
621 @email.setter
621 @email.setter
622 def email(self, val):
622 def email(self, val):
623 self._email = val.lower() if val else None
623 self._email = val.lower() if val else None
624
624
625 @hybrid_property
625 @hybrid_property
626 def first_name(self):
626 def first_name(self):
627 from rhodecode.lib import helpers as h
627 from rhodecode.lib import helpers as h
628 if self.name:
628 if self.name:
629 return h.escape(self.name)
629 return h.escape(self.name)
630 return self.name
630 return self.name
631
631
632 @hybrid_property
632 @hybrid_property
633 def last_name(self):
633 def last_name(self):
634 from rhodecode.lib import helpers as h
634 from rhodecode.lib import helpers as h
635 if self.lastname:
635 if self.lastname:
636 return h.escape(self.lastname)
636 return h.escape(self.lastname)
637 return self.lastname
637 return self.lastname
638
638
639 @hybrid_property
639 @hybrid_property
640 def api_key(self):
640 def api_key(self):
641 """
641 """
642 Fetch if exist an auth-token with role ALL connected to this user
642 Fetch if exist an auth-token with role ALL connected to this user
643 """
643 """
644 user_auth_token = UserApiKeys.query()\
644 user_auth_token = UserApiKeys.query()\
645 .filter(UserApiKeys.user_id == self.user_id)\
645 .filter(UserApiKeys.user_id == self.user_id)\
646 .filter(or_(UserApiKeys.expires == -1,
646 .filter(or_(UserApiKeys.expires == -1,
647 UserApiKeys.expires >= time.time()))\
647 UserApiKeys.expires >= time.time()))\
648 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
648 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
649 if user_auth_token:
649 if user_auth_token:
650 user_auth_token = user_auth_token.api_key
650 user_auth_token = user_auth_token.api_key
651
651
652 return user_auth_token
652 return user_auth_token
653
653
654 @api_key.setter
654 @api_key.setter
655 def api_key(self, val):
655 def api_key(self, val):
656 # don't allow to set API key this is deprecated for now
656 # don't allow to set API key this is deprecated for now
657 self._api_key = None
657 self._api_key = None
658
658
659 @property
659 @property
660 def reviewer_pull_requests(self):
660 def reviewer_pull_requests(self):
661 return PullRequestReviewers.query() \
661 return PullRequestReviewers.query() \
662 .options(joinedload(PullRequestReviewers.pull_request)) \
662 .options(joinedload(PullRequestReviewers.pull_request)) \
663 .filter(PullRequestReviewers.user_id == self.user_id) \
663 .filter(PullRequestReviewers.user_id == self.user_id) \
664 .all()
664 .all()
665
665
666 @property
666 @property
667 def firstname(self):
667 def firstname(self):
668 # alias for future
668 # alias for future
669 return self.name
669 return self.name
670
670
671 @property
671 @property
672 def emails(self):
672 def emails(self):
673 other = UserEmailMap.query()\
673 other = UserEmailMap.query()\
674 .filter(UserEmailMap.user == self) \
674 .filter(UserEmailMap.user == self) \
675 .order_by(UserEmailMap.email_id.asc()) \
675 .order_by(UserEmailMap.email_id.asc()) \
676 .all()
676 .all()
677 return [self.email] + [x.email for x in other]
677 return [self.email] + [x.email for x in other]
678
678
679 @property
679 @property
680 def auth_tokens(self):
680 def auth_tokens(self):
681 auth_tokens = self.get_auth_tokens()
681 auth_tokens = self.get_auth_tokens()
682 return [x.api_key for x in auth_tokens]
682 return [x.api_key for x in auth_tokens]
683
683
684 def get_auth_tokens(self):
684 def get_auth_tokens(self):
685 return UserApiKeys.query()\
685 return UserApiKeys.query()\
686 .filter(UserApiKeys.user == self)\
686 .filter(UserApiKeys.user == self)\
687 .order_by(UserApiKeys.user_api_key_id.asc())\
687 .order_by(UserApiKeys.user_api_key_id.asc())\
688 .all()
688 .all()
689
689
690 @LazyProperty
690 @LazyProperty
691 def feed_token(self):
691 def feed_token(self):
692 return self.get_feed_token()
692 return self.get_feed_token()
693
693
694 def get_feed_token(self, cache=True):
694 def get_feed_token(self, cache=True):
695 feed_tokens = UserApiKeys.query()\
695 feed_tokens = UserApiKeys.query()\
696 .filter(UserApiKeys.user == self)\
696 .filter(UserApiKeys.user == self)\
697 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
697 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
698 if cache:
698 if cache:
699 feed_tokens = feed_tokens.options(
699 feed_tokens = feed_tokens.options(
700 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
700 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
701
701
702 feed_tokens = feed_tokens.all()
702 feed_tokens = feed_tokens.all()
703 if feed_tokens:
703 if feed_tokens:
704 return feed_tokens[0].api_key
704 return feed_tokens[0].api_key
705 return 'NO_FEED_TOKEN_AVAILABLE'
705 return 'NO_FEED_TOKEN_AVAILABLE'
706
706
707 @classmethod
707 @classmethod
708 def get(cls, user_id, cache=False):
708 def get(cls, user_id, cache=False):
709 if not user_id:
709 if not user_id:
710 return
710 return
711
711
712 user = cls.query()
712 user = cls.query()
713 if cache:
713 if cache:
714 user = user.options(
714 user = user.options(
715 FromCache("sql_cache_short", "get_users_%s" % user_id))
715 FromCache("sql_cache_short", "get_users_%s" % user_id))
716 return user.get(user_id)
716 return user.get(user_id)
717
717
718 @classmethod
718 @classmethod
719 def extra_valid_auth_tokens(cls, user, role=None):
719 def extra_valid_auth_tokens(cls, user, role=None):
720 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
720 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
721 .filter(or_(UserApiKeys.expires == -1,
721 .filter(or_(UserApiKeys.expires == -1,
722 UserApiKeys.expires >= time.time()))
722 UserApiKeys.expires >= time.time()))
723 if role:
723 if role:
724 tokens = tokens.filter(or_(UserApiKeys.role == role,
724 tokens = tokens.filter(or_(UserApiKeys.role == role,
725 UserApiKeys.role == UserApiKeys.ROLE_ALL))
725 UserApiKeys.role == UserApiKeys.ROLE_ALL))
726 return tokens.all()
726 return tokens.all()
727
727
728 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
728 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
729 from rhodecode.lib import auth
729 from rhodecode.lib import auth
730
730
731 log.debug('Trying to authenticate user: %s via auth-token, '
731 log.debug('Trying to authenticate user: %s via auth-token, '
732 'and roles: %s', self, roles)
732 'and roles: %s', self, roles)
733
733
734 if not auth_token:
734 if not auth_token:
735 return False
735 return False
736
736
737 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
737 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
738 tokens_q = UserApiKeys.query()\
738 tokens_q = UserApiKeys.query()\
739 .filter(UserApiKeys.user_id == self.user_id)\
739 .filter(UserApiKeys.user_id == self.user_id)\
740 .filter(or_(UserApiKeys.expires == -1,
740 .filter(or_(UserApiKeys.expires == -1,
741 UserApiKeys.expires >= time.time()))
741 UserApiKeys.expires >= time.time()))
742
742
743 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
743 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
744
744
745 crypto_backend = auth.crypto_backend()
745 crypto_backend = auth.crypto_backend()
746 enc_token_map = {}
746 enc_token_map = {}
747 plain_token_map = {}
747 plain_token_map = {}
748 for token in tokens_q:
748 for token in tokens_q:
749 if token.api_key.startswith(crypto_backend.ENC_PREF):
749 if token.api_key.startswith(crypto_backend.ENC_PREF):
750 enc_token_map[token.api_key] = token
750 enc_token_map[token.api_key] = token
751 else:
751 else:
752 plain_token_map[token.api_key] = token
752 plain_token_map[token.api_key] = token
753 log.debug(
753 log.debug(
754 'Found %s plain and %s encrypted user tokens to check for authentication',
754 'Found %s plain and %s encrypted user tokens to check for authentication',
755 len(plain_token_map), len(enc_token_map))
755 len(plain_token_map), len(enc_token_map))
756
756
757 # plain token match comes first
757 # plain token match comes first
758 match = plain_token_map.get(auth_token)
758 match = plain_token_map.get(auth_token)
759
759
760 # check encrypted tokens now
760 # check encrypted tokens now
761 if not match:
761 if not match:
762 for token_hash, token in enc_token_map.items():
762 for token_hash, token in enc_token_map.items():
763 # NOTE(marcink): this is expensive to calculate, but most secure
763 # NOTE(marcink): this is expensive to calculate, but most secure
764 if crypto_backend.hash_check(auth_token, token_hash):
764 if crypto_backend.hash_check(auth_token, token_hash):
765 match = token
765 match = token
766 break
766 break
767
767
768 if match:
768 if match:
769 log.debug('Found matching token %s', match)
769 log.debug('Found matching token %s', match)
770 if match.repo_id:
770 if match.repo_id:
771 log.debug('Found scope, checking for scope match of token %s', match)
771 log.debug('Found scope, checking for scope match of token %s', match)
772 if match.repo_id == scope_repo_id:
772 if match.repo_id == scope_repo_id:
773 return True
773 return True
774 else:
774 else:
775 log.debug(
775 log.debug(
776 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
776 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
777 'and calling scope is:%s, skipping further checks',
777 'and calling scope is:%s, skipping further checks',
778 match.repo, scope_repo_id)
778 match.repo, scope_repo_id)
779 return False
779 return False
780 else:
780 else:
781 return True
781 return True
782
782
783 return False
783 return False
784
784
785 @property
785 @property
786 def ip_addresses(self):
786 def ip_addresses(self):
787 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
787 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
788 return [x.ip_addr for x in ret]
788 return [x.ip_addr for x in ret]
789
789
790 @property
790 @property
791 def username_and_name(self):
791 def username_and_name(self):
792 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
792 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
793
793
794 @property
794 @property
795 def username_or_name_or_email(self):
795 def username_or_name_or_email(self):
796 full_name = self.full_name if self.full_name is not ' ' else None
796 full_name = self.full_name if self.full_name is not ' ' else None
797 return self.username or full_name or self.email
797 return self.username or full_name or self.email
798
798
799 @property
799 @property
800 def full_name(self):
800 def full_name(self):
801 return '%s %s' % (self.first_name, self.last_name)
801 return '%s %s' % (self.first_name, self.last_name)
802
802
803 @property
803 @property
804 def full_name_or_username(self):
804 def full_name_or_username(self):
805 return ('%s %s' % (self.first_name, self.last_name)
805 return ('%s %s' % (self.first_name, self.last_name)
806 if (self.first_name and self.last_name) else self.username)
806 if (self.first_name and self.last_name) else self.username)
807
807
808 @property
808 @property
809 def full_contact(self):
809 def full_contact(self):
810 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
810 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
811
811
812 @property
812 @property
813 def short_contact(self):
813 def short_contact(self):
814 return '%s %s' % (self.first_name, self.last_name)
814 return '%s %s' % (self.first_name, self.last_name)
815
815
816 @property
816 @property
817 def is_admin(self):
817 def is_admin(self):
818 return self.admin
818 return self.admin
819
819
820 def AuthUser(self, **kwargs):
820 def AuthUser(self, **kwargs):
821 """
821 """
822 Returns instance of AuthUser for this user
822 Returns instance of AuthUser for this user
823 """
823 """
824 from rhodecode.lib.auth import AuthUser
824 from rhodecode.lib.auth import AuthUser
825 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
825 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
826
826
827 @hybrid_property
827 @hybrid_property
828 def user_data(self):
828 def user_data(self):
829 if not self._user_data:
829 if not self._user_data:
830 return {}
830 return {}
831
831
832 try:
832 try:
833 return json.loads(self._user_data)
833 return json.loads(self._user_data)
834 except TypeError:
834 except TypeError:
835 return {}
835 return {}
836
836
837 @user_data.setter
837 @user_data.setter
838 def user_data(self, val):
838 def user_data(self, val):
839 if not isinstance(val, dict):
839 if not isinstance(val, dict):
840 raise Exception('user_data must be dict, got %s' % type(val))
840 raise Exception('user_data must be dict, got %s' % type(val))
841 try:
841 try:
842 self._user_data = json.dumps(val)
842 self._user_data = json.dumps(val)
843 except Exception:
843 except Exception:
844 log.error(traceback.format_exc())
844 log.error(traceback.format_exc())
845
845
846 @classmethod
846 @classmethod
847 def get_by_username(cls, username, case_insensitive=False,
847 def get_by_username(cls, username, case_insensitive=False,
848 cache=False, identity_cache=False):
848 cache=False, identity_cache=False):
849 session = Session()
849 session = Session()
850
850
851 if case_insensitive:
851 if case_insensitive:
852 q = cls.query().filter(
852 q = cls.query().filter(
853 func.lower(cls.username) == func.lower(username))
853 func.lower(cls.username) == func.lower(username))
854 else:
854 else:
855 q = cls.query().filter(cls.username == username)
855 q = cls.query().filter(cls.username == username)
856
856
857 if cache:
857 if cache:
858 if identity_cache:
858 if identity_cache:
859 val = cls.identity_cache(session, 'username', username)
859 val = cls.identity_cache(session, 'username', username)
860 if val:
860 if val:
861 return val
861 return val
862 else:
862 else:
863 cache_key = "get_user_by_name_%s" % _hash_key(username)
863 cache_key = "get_user_by_name_%s" % _hash_key(username)
864 q = q.options(
864 q = q.options(
865 FromCache("sql_cache_short", cache_key))
865 FromCache("sql_cache_short", cache_key))
866
866
867 return q.scalar()
867 return q.scalar()
868
868
869 @classmethod
869 @classmethod
870 def get_by_auth_token(cls, auth_token, cache=False):
870 def get_by_auth_token(cls, auth_token, cache=False):
871 q = UserApiKeys.query()\
871 q = UserApiKeys.query()\
872 .filter(UserApiKeys.api_key == auth_token)\
872 .filter(UserApiKeys.api_key == auth_token)\
873 .filter(or_(UserApiKeys.expires == -1,
873 .filter(or_(UserApiKeys.expires == -1,
874 UserApiKeys.expires >= time.time()))
874 UserApiKeys.expires >= time.time()))
875 if cache:
875 if cache:
876 q = q.options(
876 q = q.options(
877 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
877 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
878
878
879 match = q.first()
879 match = q.first()
880 if match:
880 if match:
881 return match.user
881 return match.user
882
882
883 @classmethod
883 @classmethod
884 def get_by_email(cls, email, case_insensitive=False, cache=False):
884 def get_by_email(cls, email, case_insensitive=False, cache=False):
885
885
886 if case_insensitive:
886 if case_insensitive:
887 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
887 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
888
888
889 else:
889 else:
890 q = cls.query().filter(cls.email == email)
890 q = cls.query().filter(cls.email == email)
891
891
892 email_key = _hash_key(email)
892 email_key = _hash_key(email)
893 if cache:
893 if cache:
894 q = q.options(
894 q = q.options(
895 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
895 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
896
896
897 ret = q.scalar()
897 ret = q.scalar()
898 if ret is None:
898 if ret is None:
899 q = UserEmailMap.query()
899 q = UserEmailMap.query()
900 # try fetching in alternate email map
900 # try fetching in alternate email map
901 if case_insensitive:
901 if case_insensitive:
902 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
902 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
903 else:
903 else:
904 q = q.filter(UserEmailMap.email == email)
904 q = q.filter(UserEmailMap.email == email)
905 q = q.options(joinedload(UserEmailMap.user))
905 q = q.options(joinedload(UserEmailMap.user))
906 if cache:
906 if cache:
907 q = q.options(
907 q = q.options(
908 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
908 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
909 ret = getattr(q.scalar(), 'user', None)
909 ret = getattr(q.scalar(), 'user', None)
910
910
911 return ret
911 return ret
912
912
913 @classmethod
913 @classmethod
914 def get_from_cs_author(cls, author):
914 def get_from_cs_author(cls, author):
915 """
915 """
916 Tries to get User objects out of commit author string
916 Tries to get User objects out of commit author string
917
917
918 :param author:
918 :param author:
919 """
919 """
920 from rhodecode.lib.helpers import email, author_name
920 from rhodecode.lib.helpers import email, author_name
921 # Valid email in the attribute passed, see if they're in the system
921 # Valid email in the attribute passed, see if they're in the system
922 _email = email(author)
922 _email = email(author)
923 if _email:
923 if _email:
924 user = cls.get_by_email(_email, case_insensitive=True)
924 user = cls.get_by_email(_email, case_insensitive=True)
925 if user:
925 if user:
926 return user
926 return user
927 # Maybe we can match by username?
927 # Maybe we can match by username?
928 _author = author_name(author)
928 _author = author_name(author)
929 user = cls.get_by_username(_author, case_insensitive=True)
929 user = cls.get_by_username(_author, case_insensitive=True)
930 if user:
930 if user:
931 return user
931 return user
932
932
933 def update_userdata(self, **kwargs):
933 def update_userdata(self, **kwargs):
934 usr = self
934 usr = self
935 old = usr.user_data
935 old = usr.user_data
936 old.update(**kwargs)
936 old.update(**kwargs)
937 usr.user_data = old
937 usr.user_data = old
938 Session().add(usr)
938 Session().add(usr)
939 log.debug('updated userdata with ', kwargs)
939 log.debug('updated userdata with ', kwargs)
940
940
941 def update_lastlogin(self):
941 def update_lastlogin(self):
942 """Update user lastlogin"""
942 """Update user lastlogin"""
943 self.last_login = datetime.datetime.now()
943 self.last_login = datetime.datetime.now()
944 Session().add(self)
944 Session().add(self)
945 log.debug('updated user %s lastlogin', self.username)
945 log.debug('updated user %s lastlogin', self.username)
946
946
947 def update_password(self, new_password):
947 def update_password(self, new_password):
948 from rhodecode.lib.auth import get_crypt_password
948 from rhodecode.lib.auth import get_crypt_password
949
949
950 self.password = get_crypt_password(new_password)
950 self.password = get_crypt_password(new_password)
951 Session().add(self)
951 Session().add(self)
952
952
953 @classmethod
953 @classmethod
954 def get_first_super_admin(cls):
954 def get_first_super_admin(cls):
955 user = User.query()\
955 user = User.query()\
956 .filter(User.admin == true()) \
956 .filter(User.admin == true()) \
957 .order_by(User.user_id.asc()) \
957 .order_by(User.user_id.asc()) \
958 .first()
958 .first()
959
959
960 if user is None:
960 if user is None:
961 raise Exception('FATAL: Missing administrative account!')
961 raise Exception('FATAL: Missing administrative account!')
962 return user
962 return user
963
963
964 @classmethod
964 @classmethod
965 def get_all_super_admins(cls, only_active=False):
965 def get_all_super_admins(cls, only_active=False):
966 """
966 """
967 Returns all admin accounts sorted by username
967 Returns all admin accounts sorted by username
968 """
968 """
969 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
969 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
970 if only_active:
970 if only_active:
971 qry = qry.filter(User.active == true())
971 qry = qry.filter(User.active == true())
972 return qry.all()
972 return qry.all()
973
973
974 @classmethod
974 @classmethod
975 def get_default_user(cls, cache=False, refresh=False):
975 def get_default_user(cls, cache=False, refresh=False):
976 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
976 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
977 if user is None:
977 if user is None:
978 raise Exception('FATAL: Missing default account!')
978 raise Exception('FATAL: Missing default account!')
979 if refresh:
979 if refresh:
980 # The default user might be based on outdated state which
980 # The default user might be based on outdated state which
981 # has been loaded from the cache.
981 # has been loaded from the cache.
982 # A call to refresh() ensures that the
982 # A call to refresh() ensures that the
983 # latest state from the database is used.
983 # latest state from the database is used.
984 Session().refresh(user)
984 Session().refresh(user)
985 return user
985 return user
986
986
987 def _get_default_perms(self, user, suffix=''):
987 def _get_default_perms(self, user, suffix=''):
988 from rhodecode.model.permission import PermissionModel
988 from rhodecode.model.permission import PermissionModel
989 return PermissionModel().get_default_perms(user.user_perms, suffix)
989 return PermissionModel().get_default_perms(user.user_perms, suffix)
990
990
991 def get_default_perms(self, suffix=''):
991 def get_default_perms(self, suffix=''):
992 return self._get_default_perms(self, suffix)
992 return self._get_default_perms(self, suffix)
993
993
994 def get_api_data(self, include_secrets=False, details='full'):
994 def get_api_data(self, include_secrets=False, details='full'):
995 """
995 """
996 Common function for generating user related data for API
996 Common function for generating user related data for API
997
997
998 :param include_secrets: By default secrets in the API data will be replaced
998 :param include_secrets: By default secrets in the API data will be replaced
999 by a placeholder value to prevent exposing this data by accident. In case
999 by a placeholder value to prevent exposing this data by accident. In case
1000 this data shall be exposed, set this flag to ``True``.
1000 this data shall be exposed, set this flag to ``True``.
1001
1001
1002 :param details: details can be 'basic|full' basic gives only a subset of
1002 :param details: details can be 'basic|full' basic gives only a subset of
1003 the available user information that includes user_id, name and emails.
1003 the available user information that includes user_id, name and emails.
1004 """
1004 """
1005 user = self
1005 user = self
1006 user_data = self.user_data
1006 user_data = self.user_data
1007 data = {
1007 data = {
1008 'user_id': user.user_id,
1008 'user_id': user.user_id,
1009 'username': user.username,
1009 'username': user.username,
1010 'firstname': user.name,
1010 'firstname': user.name,
1011 'lastname': user.lastname,
1011 'lastname': user.lastname,
1012 'email': user.email,
1012 'email': user.email,
1013 'emails': user.emails,
1013 'emails': user.emails,
1014 }
1014 }
1015 if details == 'basic':
1015 if details == 'basic':
1016 return data
1016 return data
1017
1017
1018 auth_token_length = 40
1018 auth_token_length = 40
1019 auth_token_replacement = '*' * auth_token_length
1019 auth_token_replacement = '*' * auth_token_length
1020
1020
1021 extras = {
1021 extras = {
1022 'auth_tokens': [auth_token_replacement],
1022 'auth_tokens': [auth_token_replacement],
1023 'active': user.active,
1023 'active': user.active,
1024 'admin': user.admin,
1024 'admin': user.admin,
1025 'extern_type': user.extern_type,
1025 'extern_type': user.extern_type,
1026 'extern_name': user.extern_name,
1026 'extern_name': user.extern_name,
1027 'last_login': user.last_login,
1027 'last_login': user.last_login,
1028 'last_activity': user.last_activity,
1028 'last_activity': user.last_activity,
1029 'ip_addresses': user.ip_addresses,
1029 'ip_addresses': user.ip_addresses,
1030 'language': user_data.get('language')
1030 'language': user_data.get('language')
1031 }
1031 }
1032 data.update(extras)
1032 data.update(extras)
1033
1033
1034 if include_secrets:
1034 if include_secrets:
1035 data['auth_tokens'] = user.auth_tokens
1035 data['auth_tokens'] = user.auth_tokens
1036 return data
1036 return data
1037
1037
1038 def __json__(self):
1038 def __json__(self):
1039 data = {
1039 data = {
1040 'full_name': self.full_name,
1040 'full_name': self.full_name,
1041 'full_name_or_username': self.full_name_or_username,
1041 'full_name_or_username': self.full_name_or_username,
1042 'short_contact': self.short_contact,
1042 'short_contact': self.short_contact,
1043 'full_contact': self.full_contact,
1043 'full_contact': self.full_contact,
1044 }
1044 }
1045 data.update(self.get_api_data())
1045 data.update(self.get_api_data())
1046 return data
1046 return data
1047
1047
1048
1048
1049 class UserApiKeys(Base, BaseModel):
1049 class UserApiKeys(Base, BaseModel):
1050 __tablename__ = 'user_api_keys'
1050 __tablename__ = 'user_api_keys'
1051 __table_args__ = (
1051 __table_args__ = (
1052 Index('uak_api_key_idx', 'api_key', unique=True),
1052 Index('uak_api_key_idx', 'api_key', unique=True),
1053 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1053 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1054 base_table_args
1054 base_table_args
1055 )
1055 )
1056 __mapper_args__ = {}
1056 __mapper_args__ = {}
1057
1057
1058 # ApiKey role
1058 # ApiKey role
1059 ROLE_ALL = 'token_role_all'
1059 ROLE_ALL = 'token_role_all'
1060 ROLE_HTTP = 'token_role_http'
1060 ROLE_HTTP = 'token_role_http'
1061 ROLE_VCS = 'token_role_vcs'
1061 ROLE_VCS = 'token_role_vcs'
1062 ROLE_API = 'token_role_api'
1062 ROLE_API = 'token_role_api'
1063 ROLE_FEED = 'token_role_feed'
1063 ROLE_FEED = 'token_role_feed'
1064 ROLE_PASSWORD_RESET = 'token_password_reset'
1064 ROLE_PASSWORD_RESET = 'token_password_reset'
1065
1065
1066 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1066 ROLES = [ROLE_ALL, ROLE_HTTP, ROLE_VCS, ROLE_API, ROLE_FEED]
1067
1067
1068 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1068 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1069 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1069 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1070 api_key = Column("api_key", String(255), nullable=False, unique=True)
1070 api_key = Column("api_key", String(255), nullable=False, unique=True)
1071 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1071 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1072 expires = Column('expires', Float(53), nullable=False)
1072 expires = Column('expires', Float(53), nullable=False)
1073 role = Column('role', String(255), nullable=True)
1073 role = Column('role', String(255), nullable=True)
1074 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1074 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1075
1075
1076 # scope columns
1076 # scope columns
1077 repo_id = Column(
1077 repo_id = Column(
1078 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1078 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1079 nullable=True, unique=None, default=None)
1079 nullable=True, unique=None, default=None)
1080 repo = relationship('Repository', lazy='joined')
1080 repo = relationship('Repository', lazy='joined')
1081
1081
1082 repo_group_id = Column(
1082 repo_group_id = Column(
1083 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1083 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1084 nullable=True, unique=None, default=None)
1084 nullable=True, unique=None, default=None)
1085 repo_group = relationship('RepoGroup', lazy='joined')
1085 repo_group = relationship('RepoGroup', lazy='joined')
1086
1086
1087 user = relationship('User', lazy='joined')
1087 user = relationship('User', lazy='joined')
1088
1088
1089 def __unicode__(self):
1089 def __unicode__(self):
1090 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1090 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1091
1091
1092 def __json__(self):
1092 def __json__(self):
1093 data = {
1093 data = {
1094 'auth_token': self.api_key,
1094 'auth_token': self.api_key,
1095 'role': self.role,
1095 'role': self.role,
1096 'scope': self.scope_humanized,
1096 'scope': self.scope_humanized,
1097 'expired': self.expired
1097 'expired': self.expired
1098 }
1098 }
1099 return data
1099 return data
1100
1100
1101 def get_api_data(self, include_secrets=False):
1101 def get_api_data(self, include_secrets=False):
1102 data = self.__json__()
1102 data = self.__json__()
1103 if include_secrets:
1103 if include_secrets:
1104 return data
1104 return data
1105 else:
1105 else:
1106 data['auth_token'] = self.token_obfuscated
1106 data['auth_token'] = self.token_obfuscated
1107 return data
1107 return data
1108
1108
1109 @hybrid_property
1109 @hybrid_property
1110 def description_safe(self):
1110 def description_safe(self):
1111 from rhodecode.lib import helpers as h
1111 from rhodecode.lib import helpers as h
1112 return h.escape(self.description)
1112 return h.escape(self.description)
1113
1113
1114 @property
1114 @property
1115 def expired(self):
1115 def expired(self):
1116 if self.expires == -1:
1116 if self.expires == -1:
1117 return False
1117 return False
1118 return time.time() > self.expires
1118 return time.time() > self.expires
1119
1119
1120 @classmethod
1120 @classmethod
1121 def _get_role_name(cls, role):
1121 def _get_role_name(cls, role):
1122 return {
1122 return {
1123 cls.ROLE_ALL: _('all'),
1123 cls.ROLE_ALL: _('all'),
1124 cls.ROLE_HTTP: _('http/web interface'),
1124 cls.ROLE_HTTP: _('http/web interface'),
1125 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1125 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1126 cls.ROLE_API: _('api calls'),
1126 cls.ROLE_API: _('api calls'),
1127 cls.ROLE_FEED: _('feed access'),
1127 cls.ROLE_FEED: _('feed access'),
1128 }.get(role, role)
1128 }.get(role, role)
1129
1129
1130 @property
1130 @property
1131 def role_humanized(self):
1131 def role_humanized(self):
1132 return self._get_role_name(self.role)
1132 return self._get_role_name(self.role)
1133
1133
1134 def _get_scope(self):
1134 def _get_scope(self):
1135 if self.repo:
1135 if self.repo:
1136 return 'Repository: {}'.format(self.repo.repo_name)
1136 return 'Repository: {}'.format(self.repo.repo_name)
1137 if self.repo_group:
1137 if self.repo_group:
1138 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1138 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1139 return 'Global'
1139 return 'Global'
1140
1140
1141 @property
1141 @property
1142 def scope_humanized(self):
1142 def scope_humanized(self):
1143 return self._get_scope()
1143 return self._get_scope()
1144
1144
1145 @property
1145 @property
1146 def token_obfuscated(self):
1146 def token_obfuscated(self):
1147 if self.api_key:
1147 if self.api_key:
1148 return self.api_key[:4] + "****"
1148 return self.api_key[:4] + "****"
1149
1149
1150
1150
1151 class UserEmailMap(Base, BaseModel):
1151 class UserEmailMap(Base, BaseModel):
1152 __tablename__ = 'user_email_map'
1152 __tablename__ = 'user_email_map'
1153 __table_args__ = (
1153 __table_args__ = (
1154 Index('uem_email_idx', 'email'),
1154 Index('uem_email_idx', 'email'),
1155 UniqueConstraint('email'),
1155 UniqueConstraint('email'),
1156 base_table_args
1156 base_table_args
1157 )
1157 )
1158 __mapper_args__ = {}
1158 __mapper_args__ = {}
1159
1159
1160 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1160 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1161 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1161 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1162 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1162 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1163 user = relationship('User', lazy='joined')
1163 user = relationship('User', lazy='joined')
1164
1164
1165 @validates('_email')
1165 @validates('_email')
1166 def validate_email(self, key, email):
1166 def validate_email(self, key, email):
1167 # check if this email is not main one
1167 # check if this email is not main one
1168 main_email = Session().query(User).filter(User.email == email).scalar()
1168 main_email = Session().query(User).filter(User.email == email).scalar()
1169 if main_email is not None:
1169 if main_email is not None:
1170 raise AttributeError('email %s is present is user table' % email)
1170 raise AttributeError('email %s is present is user table' % email)
1171 return email
1171 return email
1172
1172
1173 @hybrid_property
1173 @hybrid_property
1174 def email(self):
1174 def email(self):
1175 return self._email
1175 return self._email
1176
1176
1177 @email.setter
1177 @email.setter
1178 def email(self, val):
1178 def email(self, val):
1179 self._email = val.lower() if val else None
1179 self._email = val.lower() if val else None
1180
1180
1181
1181
1182 class UserIpMap(Base, BaseModel):
1182 class UserIpMap(Base, BaseModel):
1183 __tablename__ = 'user_ip_map'
1183 __tablename__ = 'user_ip_map'
1184 __table_args__ = (
1184 __table_args__ = (
1185 UniqueConstraint('user_id', 'ip_addr'),
1185 UniqueConstraint('user_id', 'ip_addr'),
1186 base_table_args
1186 base_table_args
1187 )
1187 )
1188 __mapper_args__ = {}
1188 __mapper_args__ = {}
1189
1189
1190 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1190 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1192 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1192 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1193 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1193 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1194 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1194 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1195 user = relationship('User', lazy='joined')
1195 user = relationship('User', lazy='joined')
1196
1196
1197 @hybrid_property
1197 @hybrid_property
1198 def description_safe(self):
1198 def description_safe(self):
1199 from rhodecode.lib import helpers as h
1199 from rhodecode.lib import helpers as h
1200 return h.escape(self.description)
1200 return h.escape(self.description)
1201
1201
1202 @classmethod
1202 @classmethod
1203 def _get_ip_range(cls, ip_addr):
1203 def _get_ip_range(cls, ip_addr):
1204 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1204 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1205 return [str(net.network_address), str(net.broadcast_address)]
1205 return [str(net.network_address), str(net.broadcast_address)]
1206
1206
1207 def __json__(self):
1207 def __json__(self):
1208 return {
1208 return {
1209 'ip_addr': self.ip_addr,
1209 'ip_addr': self.ip_addr,
1210 'ip_range': self._get_ip_range(self.ip_addr),
1210 'ip_range': self._get_ip_range(self.ip_addr),
1211 }
1211 }
1212
1212
1213 def __unicode__(self):
1213 def __unicode__(self):
1214 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1214 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1215 self.user_id, self.ip_addr)
1215 self.user_id, self.ip_addr)
1216
1216
1217
1217
1218 class UserSshKeys(Base, BaseModel):
1218 class UserSshKeys(Base, BaseModel):
1219 __tablename__ = 'user_ssh_keys'
1219 __tablename__ = 'user_ssh_keys'
1220 __table_args__ = (
1220 __table_args__ = (
1221 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1221 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1222
1222
1223 UniqueConstraint('ssh_key_fingerprint'),
1223 UniqueConstraint('ssh_key_fingerprint'),
1224
1224
1225 base_table_args
1225 base_table_args
1226 )
1226 )
1227 __mapper_args__ = {}
1227 __mapper_args__ = {}
1228
1228
1229 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1229 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1230 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1230 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1231 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1231 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1232
1232
1233 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1233 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1234
1234
1235 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1235 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1236 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1236 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1237 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1237 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1238
1238
1239 user = relationship('User', lazy='joined')
1239 user = relationship('User', lazy='joined')
1240
1240
1241 def __json__(self):
1241 def __json__(self):
1242 data = {
1242 data = {
1243 'ssh_fingerprint': self.ssh_key_fingerprint,
1243 'ssh_fingerprint': self.ssh_key_fingerprint,
1244 'description': self.description,
1244 'description': self.description,
1245 'created_on': self.created_on
1245 'created_on': self.created_on
1246 }
1246 }
1247 return data
1247 return data
1248
1248
1249 def get_api_data(self):
1249 def get_api_data(self):
1250 data = self.__json__()
1250 data = self.__json__()
1251 return data
1251 return data
1252
1252
1253
1253
1254 class UserLog(Base, BaseModel):
1254 class UserLog(Base, BaseModel):
1255 __tablename__ = 'user_logs'
1255 __tablename__ = 'user_logs'
1256 __table_args__ = (
1256 __table_args__ = (
1257 base_table_args,
1257 base_table_args,
1258 )
1258 )
1259
1259
1260 VERSION_1 = 'v1'
1260 VERSION_1 = 'v1'
1261 VERSION_2 = 'v2'
1261 VERSION_2 = 'v2'
1262 VERSIONS = [VERSION_1, VERSION_2]
1262 VERSIONS = [VERSION_1, VERSION_2]
1263
1263
1264 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1264 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1265 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1266 username = Column("username", String(255), nullable=True, unique=None, default=None)
1266 username = Column("username", String(255), nullable=True, unique=None, default=None)
1267 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1267 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1268 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1268 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1269 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1269 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1270 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1270 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1271 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1271 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1272
1272
1273 version = Column("version", String(255), nullable=True, default=VERSION_1)
1273 version = Column("version", String(255), nullable=True, default=VERSION_1)
1274 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1274 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1275 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1275 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1276
1276
1277 def __unicode__(self):
1277 def __unicode__(self):
1278 return u"<%s('id:%s:%s')>" % (
1278 return u"<%s('id:%s:%s')>" % (
1279 self.__class__.__name__, self.repository_name, self.action)
1279 self.__class__.__name__, self.repository_name, self.action)
1280
1280
1281 def __json__(self):
1281 def __json__(self):
1282 return {
1282 return {
1283 'user_id': self.user_id,
1283 'user_id': self.user_id,
1284 'username': self.username,
1284 'username': self.username,
1285 'repository_id': self.repository_id,
1285 'repository_id': self.repository_id,
1286 'repository_name': self.repository_name,
1286 'repository_name': self.repository_name,
1287 'user_ip': self.user_ip,
1287 'user_ip': self.user_ip,
1288 'action_date': self.action_date,
1288 'action_date': self.action_date,
1289 'action': self.action,
1289 'action': self.action,
1290 }
1290 }
1291
1291
1292 @hybrid_property
1292 @hybrid_property
1293 def entry_id(self):
1293 def entry_id(self):
1294 return self.user_log_id
1294 return self.user_log_id
1295
1295
1296 @property
1296 @property
1297 def action_as_day(self):
1297 def action_as_day(self):
1298 return datetime.date(*self.action_date.timetuple()[:3])
1298 return datetime.date(*self.action_date.timetuple()[:3])
1299
1299
1300 user = relationship('User')
1300 user = relationship('User')
1301 repository = relationship('Repository', cascade='')
1301 repository = relationship('Repository', cascade='')
1302
1302
1303
1303
1304 class UserGroup(Base, BaseModel):
1304 class UserGroup(Base, BaseModel):
1305 __tablename__ = 'users_groups'
1305 __tablename__ = 'users_groups'
1306 __table_args__ = (
1306 __table_args__ = (
1307 base_table_args,
1307 base_table_args,
1308 )
1308 )
1309
1309
1310 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1310 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1311 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1311 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1312 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1312 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1313 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1313 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1314 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1314 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1315 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1315 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1316 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1317 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1317 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1318
1318
1319 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1319 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
1320 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1320 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1321 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1321 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1322 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1322 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1323 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1323 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1324 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1324 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1325
1325
1326 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1326 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1327 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1327 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1328
1328
1329 @classmethod
1329 @classmethod
1330 def _load_group_data(cls, column):
1330 def _load_group_data(cls, column):
1331 if not column:
1331 if not column:
1332 return {}
1332 return {}
1333
1333
1334 try:
1334 try:
1335 return json.loads(column) or {}
1335 return json.loads(column) or {}
1336 except TypeError:
1336 except TypeError:
1337 return {}
1337 return {}
1338
1338
1339 @hybrid_property
1339 @hybrid_property
1340 def description_safe(self):
1340 def description_safe(self):
1341 from rhodecode.lib import helpers as h
1341 from rhodecode.lib import helpers as h
1342 return h.escape(self.user_group_description)
1342 return h.escape(self.user_group_description)
1343
1343
1344 @hybrid_property
1344 @hybrid_property
1345 def group_data(self):
1345 def group_data(self):
1346 return self._load_group_data(self._group_data)
1346 return self._load_group_data(self._group_data)
1347
1347
1348 @group_data.expression
1348 @group_data.expression
1349 def group_data(self, **kwargs):
1349 def group_data(self, **kwargs):
1350 return self._group_data
1350 return self._group_data
1351
1351
1352 @group_data.setter
1352 @group_data.setter
1353 def group_data(self, val):
1353 def group_data(self, val):
1354 try:
1354 try:
1355 self._group_data = json.dumps(val)
1355 self._group_data = json.dumps(val)
1356 except Exception:
1356 except Exception:
1357 log.error(traceback.format_exc())
1357 log.error(traceback.format_exc())
1358
1358
1359 @classmethod
1359 @classmethod
1360 def _load_sync(cls, group_data):
1360 def _load_sync(cls, group_data):
1361 if group_data:
1361 if group_data:
1362 return group_data.get('extern_type')
1362 return group_data.get('extern_type')
1363
1363
1364 @property
1364 @property
1365 def sync(self):
1365 def sync(self):
1366 return self._load_sync(self.group_data)
1366 return self._load_sync(self.group_data)
1367
1367
1368 def __unicode__(self):
1368 def __unicode__(self):
1369 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1369 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1370 self.users_group_id,
1370 self.users_group_id,
1371 self.users_group_name)
1371 self.users_group_name)
1372
1372
1373 @classmethod
1373 @classmethod
1374 def get_by_group_name(cls, group_name, cache=False,
1374 def get_by_group_name(cls, group_name, cache=False,
1375 case_insensitive=False):
1375 case_insensitive=False):
1376 if case_insensitive:
1376 if case_insensitive:
1377 q = cls.query().filter(func.lower(cls.users_group_name) ==
1377 q = cls.query().filter(func.lower(cls.users_group_name) ==
1378 func.lower(group_name))
1378 func.lower(group_name))
1379
1379
1380 else:
1380 else:
1381 q = cls.query().filter(cls.users_group_name == group_name)
1381 q = cls.query().filter(cls.users_group_name == group_name)
1382 if cache:
1382 if cache:
1383 q = q.options(
1383 q = q.options(
1384 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1384 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1385 return q.scalar()
1385 return q.scalar()
1386
1386
1387 @classmethod
1387 @classmethod
1388 def get(cls, user_group_id, cache=False):
1388 def get(cls, user_group_id, cache=False):
1389 if not user_group_id:
1389 if not user_group_id:
1390 return
1390 return
1391
1391
1392 user_group = cls.query()
1392 user_group = cls.query()
1393 if cache:
1393 if cache:
1394 user_group = user_group.options(
1394 user_group = user_group.options(
1395 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1395 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1396 return user_group.get(user_group_id)
1396 return user_group.get(user_group_id)
1397
1397
1398 def permissions(self, with_admins=True, with_owner=True,
1398 def permissions(self, with_admins=True, with_owner=True,
1399 expand_from_user_groups=False):
1399 expand_from_user_groups=False):
1400 """
1400 """
1401 Permissions for user groups
1401 Permissions for user groups
1402 """
1402 """
1403 _admin_perm = 'usergroup.admin'
1403 _admin_perm = 'usergroup.admin'
1404
1404
1405 owner_row = []
1405 owner_row = []
1406 if with_owner:
1406 if with_owner:
1407 usr = AttributeDict(self.user.get_dict())
1407 usr = AttributeDict(self.user.get_dict())
1408 usr.owner_row = True
1408 usr.owner_row = True
1409 usr.permission = _admin_perm
1409 usr.permission = _admin_perm
1410 owner_row.append(usr)
1410 owner_row.append(usr)
1411
1411
1412 super_admin_ids = []
1412 super_admin_ids = []
1413 super_admin_rows = []
1413 super_admin_rows = []
1414 if with_admins:
1414 if with_admins:
1415 for usr in User.get_all_super_admins():
1415 for usr in User.get_all_super_admins():
1416 super_admin_ids.append(usr.user_id)
1416 super_admin_ids.append(usr.user_id)
1417 # if this admin is also owner, don't double the record
1417 # if this admin is also owner, don't double the record
1418 if usr.user_id == owner_row[0].user_id:
1418 if usr.user_id == owner_row[0].user_id:
1419 owner_row[0].admin_row = True
1419 owner_row[0].admin_row = True
1420 else:
1420 else:
1421 usr = AttributeDict(usr.get_dict())
1421 usr = AttributeDict(usr.get_dict())
1422 usr.admin_row = True
1422 usr.admin_row = True
1423 usr.permission = _admin_perm
1423 usr.permission = _admin_perm
1424 super_admin_rows.append(usr)
1424 super_admin_rows.append(usr)
1425
1425
1426 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1426 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1427 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1427 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1428 joinedload(UserUserGroupToPerm.user),
1428 joinedload(UserUserGroupToPerm.user),
1429 joinedload(UserUserGroupToPerm.permission),)
1429 joinedload(UserUserGroupToPerm.permission),)
1430
1430
1431 # get owners and admins and permissions. We do a trick of re-writing
1431 # get owners and admins and permissions. We do a trick of re-writing
1432 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1432 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1433 # has a global reference and changing one object propagates to all
1433 # has a global reference and changing one object propagates to all
1434 # others. This means if admin is also an owner admin_row that change
1434 # others. This means if admin is also an owner admin_row that change
1435 # would propagate to both objects
1435 # would propagate to both objects
1436 perm_rows = []
1436 perm_rows = []
1437 for _usr in q.all():
1437 for _usr in q.all():
1438 usr = AttributeDict(_usr.user.get_dict())
1438 usr = AttributeDict(_usr.user.get_dict())
1439 # if this user is also owner/admin, mark as duplicate record
1439 # if this user is also owner/admin, mark as duplicate record
1440 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1440 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1441 usr.duplicate_perm = True
1441 usr.duplicate_perm = True
1442 usr.permission = _usr.permission.permission_name
1442 usr.permission = _usr.permission.permission_name
1443 perm_rows.append(usr)
1443 perm_rows.append(usr)
1444
1444
1445 # filter the perm rows by 'default' first and then sort them by
1445 # filter the perm rows by 'default' first and then sort them by
1446 # admin,write,read,none permissions sorted again alphabetically in
1446 # admin,write,read,none permissions sorted again alphabetically in
1447 # each group
1447 # each group
1448 perm_rows = sorted(perm_rows, key=display_user_sort)
1448 perm_rows = sorted(perm_rows, key=display_user_sort)
1449
1449
1450 user_groups_rows = []
1450 user_groups_rows = []
1451 if expand_from_user_groups:
1451 if expand_from_user_groups:
1452 for ug in self.permission_user_groups(with_members=True):
1452 for ug in self.permission_user_groups(with_members=True):
1453 for user_data in ug.members:
1453 for user_data in ug.members:
1454 user_groups_rows.append(user_data)
1454 user_groups_rows.append(user_data)
1455
1455
1456 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1456 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1457
1457
1458 def permission_user_groups(self, with_members=False):
1458 def permission_user_groups(self, with_members=False):
1459 q = UserGroupUserGroupToPerm.query()\
1459 q = UserGroupUserGroupToPerm.query()\
1460 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1460 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1461 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1461 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1462 joinedload(UserGroupUserGroupToPerm.target_user_group),
1462 joinedload(UserGroupUserGroupToPerm.target_user_group),
1463 joinedload(UserGroupUserGroupToPerm.permission),)
1463 joinedload(UserGroupUserGroupToPerm.permission),)
1464
1464
1465 perm_rows = []
1465 perm_rows = []
1466 for _user_group in q.all():
1466 for _user_group in q.all():
1467 entry = AttributeDict(_user_group.user_group.get_dict())
1467 entry = AttributeDict(_user_group.user_group.get_dict())
1468 entry.permission = _user_group.permission.permission_name
1468 entry.permission = _user_group.permission.permission_name
1469 if with_members:
1469 if with_members:
1470 entry.members = [x.user.get_dict()
1470 entry.members = [x.user.get_dict()
1471 for x in _user_group.users_group.members]
1471 for x in _user_group.users_group.members]
1472 perm_rows.append(entry)
1472 perm_rows.append(entry)
1473
1473
1474 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1474 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1475 return perm_rows
1475 return perm_rows
1476
1476
1477 def _get_default_perms(self, user_group, suffix=''):
1477 def _get_default_perms(self, user_group, suffix=''):
1478 from rhodecode.model.permission import PermissionModel
1478 from rhodecode.model.permission import PermissionModel
1479 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1479 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1480
1480
1481 def get_default_perms(self, suffix=''):
1481 def get_default_perms(self, suffix=''):
1482 return self._get_default_perms(self, suffix)
1482 return self._get_default_perms(self, suffix)
1483
1483
1484 def get_api_data(self, with_group_members=True, include_secrets=False):
1484 def get_api_data(self, with_group_members=True, include_secrets=False):
1485 """
1485 """
1486 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1486 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1487 basically forwarded.
1487 basically forwarded.
1488
1488
1489 """
1489 """
1490 user_group = self
1490 user_group = self
1491 data = {
1491 data = {
1492 'users_group_id': user_group.users_group_id,
1492 'users_group_id': user_group.users_group_id,
1493 'group_name': user_group.users_group_name,
1493 'group_name': user_group.users_group_name,
1494 'group_description': user_group.user_group_description,
1494 'group_description': user_group.user_group_description,
1495 'active': user_group.users_group_active,
1495 'active': user_group.users_group_active,
1496 'owner': user_group.user.username,
1496 'owner': user_group.user.username,
1497 'sync': user_group.sync,
1497 'sync': user_group.sync,
1498 'owner_email': user_group.user.email,
1498 'owner_email': user_group.user.email,
1499 }
1499 }
1500
1500
1501 if with_group_members:
1501 if with_group_members:
1502 users = []
1502 users = []
1503 for user in user_group.members:
1503 for user in user_group.members:
1504 user = user.user
1504 user = user.user
1505 users.append(user.get_api_data(include_secrets=include_secrets))
1505 users.append(user.get_api_data(include_secrets=include_secrets))
1506 data['users'] = users
1506 data['users'] = users
1507
1507
1508 return data
1508 return data
1509
1509
1510
1510
1511 class UserGroupMember(Base, BaseModel):
1511 class UserGroupMember(Base, BaseModel):
1512 __tablename__ = 'users_groups_members'
1512 __tablename__ = 'users_groups_members'
1513 __table_args__ = (
1513 __table_args__ = (
1514 base_table_args,
1514 base_table_args,
1515 )
1515 )
1516
1516
1517 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1517 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1518 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1518 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1519 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1519 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1520
1520
1521 user = relationship('User', lazy='joined')
1521 user = relationship('User', lazy='joined')
1522 users_group = relationship('UserGroup')
1522 users_group = relationship('UserGroup')
1523
1523
1524 def __init__(self, gr_id='', u_id=''):
1524 def __init__(self, gr_id='', u_id=''):
1525 self.users_group_id = gr_id
1525 self.users_group_id = gr_id
1526 self.user_id = u_id
1526 self.user_id = u_id
1527
1527
1528
1528
1529 class RepositoryField(Base, BaseModel):
1529 class RepositoryField(Base, BaseModel):
1530 __tablename__ = 'repositories_fields'
1530 __tablename__ = 'repositories_fields'
1531 __table_args__ = (
1531 __table_args__ = (
1532 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1532 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1533 base_table_args,
1533 base_table_args,
1534 )
1534 )
1535
1535
1536 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1536 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1537
1537
1538 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1538 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1539 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1539 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1540 field_key = Column("field_key", String(250))
1540 field_key = Column("field_key", String(250))
1541 field_label = Column("field_label", String(1024), nullable=False)
1541 field_label = Column("field_label", String(1024), nullable=False)
1542 field_value = Column("field_value", String(10000), nullable=False)
1542 field_value = Column("field_value", String(10000), nullable=False)
1543 field_desc = Column("field_desc", String(1024), nullable=False)
1543 field_desc = Column("field_desc", String(1024), nullable=False)
1544 field_type = Column("field_type", String(255), nullable=False, unique=None)
1544 field_type = Column("field_type", String(255), nullable=False, unique=None)
1545 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1545 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1546
1546
1547 repository = relationship('Repository')
1547 repository = relationship('Repository')
1548
1548
1549 @property
1549 @property
1550 def field_key_prefixed(self):
1550 def field_key_prefixed(self):
1551 return 'ex_%s' % self.field_key
1551 return 'ex_%s' % self.field_key
1552
1552
1553 @classmethod
1553 @classmethod
1554 def un_prefix_key(cls, key):
1554 def un_prefix_key(cls, key):
1555 if key.startswith(cls.PREFIX):
1555 if key.startswith(cls.PREFIX):
1556 return key[len(cls.PREFIX):]
1556 return key[len(cls.PREFIX):]
1557 return key
1557 return key
1558
1558
1559 @classmethod
1559 @classmethod
1560 def get_by_key_name(cls, key, repo):
1560 def get_by_key_name(cls, key, repo):
1561 row = cls.query()\
1561 row = cls.query()\
1562 .filter(cls.repository == repo)\
1562 .filter(cls.repository == repo)\
1563 .filter(cls.field_key == key).scalar()
1563 .filter(cls.field_key == key).scalar()
1564 return row
1564 return row
1565
1565
1566
1566
1567 class Repository(Base, BaseModel):
1567 class Repository(Base, BaseModel):
1568 __tablename__ = 'repositories'
1568 __tablename__ = 'repositories'
1569 __table_args__ = (
1569 __table_args__ = (
1570 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1570 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1571 base_table_args,
1571 base_table_args,
1572 )
1572 )
1573 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1573 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1574 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1574 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1575 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1575 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1576
1576
1577 STATE_CREATED = 'repo_state_created'
1577 STATE_CREATED = 'repo_state_created'
1578 STATE_PENDING = 'repo_state_pending'
1578 STATE_PENDING = 'repo_state_pending'
1579 STATE_ERROR = 'repo_state_error'
1579 STATE_ERROR = 'repo_state_error'
1580
1580
1581 LOCK_AUTOMATIC = 'lock_auto'
1581 LOCK_AUTOMATIC = 'lock_auto'
1582 LOCK_API = 'lock_api'
1582 LOCK_API = 'lock_api'
1583 LOCK_WEB = 'lock_web'
1583 LOCK_WEB = 'lock_web'
1584 LOCK_PULL = 'lock_pull'
1584 LOCK_PULL = 'lock_pull'
1585
1585
1586 NAME_SEP = URL_SEP
1586 NAME_SEP = URL_SEP
1587
1587
1588 repo_id = Column(
1588 repo_id = Column(
1589 "repo_id", Integer(), nullable=False, unique=True, default=None,
1589 "repo_id", Integer(), nullable=False, unique=True, default=None,
1590 primary_key=True)
1590 primary_key=True)
1591 _repo_name = Column(
1591 _repo_name = Column(
1592 "repo_name", Text(), nullable=False, default=None)
1592 "repo_name", Text(), nullable=False, default=None)
1593 _repo_name_hash = Column(
1593 _repo_name_hash = Column(
1594 "repo_name_hash", String(255), nullable=False, unique=True)
1594 "repo_name_hash", String(255), nullable=False, unique=True)
1595 repo_state = Column("repo_state", String(255), nullable=True)
1595 repo_state = Column("repo_state", String(255), nullable=True)
1596
1596
1597 clone_uri = Column(
1597 clone_uri = Column(
1598 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1598 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1599 default=None)
1599 default=None)
1600 push_uri = Column(
1600 push_uri = Column(
1601 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1601 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1602 default=None)
1602 default=None)
1603 repo_type = Column(
1603 repo_type = Column(
1604 "repo_type", String(255), nullable=False, unique=False, default=None)
1604 "repo_type", String(255), nullable=False, unique=False, default=None)
1605 user_id = Column(
1605 user_id = Column(
1606 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1606 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1607 unique=False, default=None)
1607 unique=False, default=None)
1608 private = Column(
1608 private = Column(
1609 "private", Boolean(), nullable=True, unique=None, default=None)
1609 "private", Boolean(), nullable=True, unique=None, default=None)
1610 archived = Column(
1610 archived = Column(
1611 "archived", Boolean(), nullable=True, unique=None, default=None)
1611 "archived", Boolean(), nullable=True, unique=None, default=None)
1612 enable_statistics = Column(
1612 enable_statistics = Column(
1613 "statistics", Boolean(), nullable=True, unique=None, default=True)
1613 "statistics", Boolean(), nullable=True, unique=None, default=True)
1614 enable_downloads = Column(
1614 enable_downloads = Column(
1615 "downloads", Boolean(), nullable=True, unique=None, default=True)
1615 "downloads", Boolean(), nullable=True, unique=None, default=True)
1616 description = Column(
1616 description = Column(
1617 "description", String(10000), nullable=True, unique=None, default=None)
1617 "description", String(10000), nullable=True, unique=None, default=None)
1618 created_on = Column(
1618 created_on = Column(
1619 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1619 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1620 default=datetime.datetime.now)
1620 default=datetime.datetime.now)
1621 updated_on = Column(
1621 updated_on = Column(
1622 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1622 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1623 default=datetime.datetime.now)
1623 default=datetime.datetime.now)
1624 _landing_revision = Column(
1624 _landing_revision = Column(
1625 "landing_revision", String(255), nullable=False, unique=False,
1625 "landing_revision", String(255), nullable=False, unique=False,
1626 default=None)
1626 default=None)
1627 enable_locking = Column(
1627 enable_locking = Column(
1628 "enable_locking", Boolean(), nullable=False, unique=None,
1628 "enable_locking", Boolean(), nullable=False, unique=None,
1629 default=False)
1629 default=False)
1630 _locked = Column(
1630 _locked = Column(
1631 "locked", String(255), nullable=True, unique=False, default=None)
1631 "locked", String(255), nullable=True, unique=False, default=None)
1632 _changeset_cache = Column(
1632 _changeset_cache = Column(
1633 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1633 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1634
1634
1635 fork_id = Column(
1635 fork_id = Column(
1636 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1636 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1637 nullable=True, unique=False, default=None)
1637 nullable=True, unique=False, default=None)
1638 group_id = Column(
1638 group_id = Column(
1639 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1639 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1640 unique=False, default=None)
1640 unique=False, default=None)
1641
1641
1642 user = relationship('User', lazy='joined')
1642 user = relationship('User', lazy='joined')
1643 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1643 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1644 group = relationship('RepoGroup', lazy='joined')
1644 group = relationship('RepoGroup', lazy='joined')
1645 repo_to_perm = relationship(
1645 repo_to_perm = relationship(
1646 'UserRepoToPerm', cascade='all',
1646 'UserRepoToPerm', cascade='all',
1647 order_by='UserRepoToPerm.repo_to_perm_id')
1647 order_by='UserRepoToPerm.repo_to_perm_id')
1648 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1648 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1649 stats = relationship('Statistics', cascade='all', uselist=False)
1649 stats = relationship('Statistics', cascade='all', uselist=False)
1650
1650
1651 followers = relationship(
1651 followers = relationship(
1652 'UserFollowing',
1652 'UserFollowing',
1653 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1653 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1654 cascade='all')
1654 cascade='all')
1655 extra_fields = relationship(
1655 extra_fields = relationship(
1656 'RepositoryField', cascade="all, delete, delete-orphan")
1656 'RepositoryField', cascade="all, delete, delete-orphan")
1657 logs = relationship('UserLog')
1657 logs = relationship('UserLog')
1658 comments = relationship(
1658 comments = relationship(
1659 'ChangesetComment', cascade="all, delete, delete-orphan")
1659 'ChangesetComment', cascade="all, delete, delete-orphan")
1660 pull_requests_source = relationship(
1660 pull_requests_source = relationship(
1661 'PullRequest',
1661 'PullRequest',
1662 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1662 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1663 cascade="all, delete, delete-orphan")
1663 cascade="all, delete, delete-orphan")
1664 pull_requests_target = relationship(
1664 pull_requests_target = relationship(
1665 'PullRequest',
1665 'PullRequest',
1666 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1666 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1667 cascade="all, delete, delete-orphan")
1667 cascade="all, delete, delete-orphan")
1668 ui = relationship('RepoRhodeCodeUi', cascade="all")
1668 ui = relationship('RepoRhodeCodeUi', cascade="all")
1669 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1669 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1670 integrations = relationship('Integration',
1670 integrations = relationship('Integration',
1671 cascade="all, delete, delete-orphan")
1671 cascade="all, delete, delete-orphan")
1672
1672
1673 scoped_tokens = relationship('UserApiKeys', cascade="all")
1673 scoped_tokens = relationship('UserApiKeys', cascade="all")
1674
1674
1675 def __unicode__(self):
1675 def __unicode__(self):
1676 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1676 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1677 safe_unicode(self.repo_name))
1677 safe_unicode(self.repo_name))
1678
1678
1679 @hybrid_property
1679 @hybrid_property
1680 def description_safe(self):
1680 def description_safe(self):
1681 from rhodecode.lib import helpers as h
1681 from rhodecode.lib import helpers as h
1682 return h.escape(self.description)
1682 return h.escape(self.description)
1683
1683
1684 @hybrid_property
1684 @hybrid_property
1685 def landing_rev(self):
1685 def landing_rev(self):
1686 # always should return [rev_type, rev]
1686 # always should return [rev_type, rev]
1687 if self._landing_revision:
1687 if self._landing_revision:
1688 _rev_info = self._landing_revision.split(':')
1688 _rev_info = self._landing_revision.split(':')
1689 if len(_rev_info) < 2:
1689 if len(_rev_info) < 2:
1690 _rev_info.insert(0, 'rev')
1690 _rev_info.insert(0, 'rev')
1691 return [_rev_info[0], _rev_info[1]]
1691 return [_rev_info[0], _rev_info[1]]
1692 return [None, None]
1692 return [None, None]
1693
1693
1694 @landing_rev.setter
1694 @landing_rev.setter
1695 def landing_rev(self, val):
1695 def landing_rev(self, val):
1696 if ':' not in val:
1696 if ':' not in val:
1697 raise ValueError('value must be delimited with `:` and consist '
1697 raise ValueError('value must be delimited with `:` and consist '
1698 'of <rev_type>:<rev>, got %s instead' % val)
1698 'of <rev_type>:<rev>, got %s instead' % val)
1699 self._landing_revision = val
1699 self._landing_revision = val
1700
1700
1701 @hybrid_property
1701 @hybrid_property
1702 def locked(self):
1702 def locked(self):
1703 if self._locked:
1703 if self._locked:
1704 user_id, timelocked, reason = self._locked.split(':')
1704 user_id, timelocked, reason = self._locked.split(':')
1705 lock_values = int(user_id), timelocked, reason
1705 lock_values = int(user_id), timelocked, reason
1706 else:
1706 else:
1707 lock_values = [None, None, None]
1707 lock_values = [None, None, None]
1708 return lock_values
1708 return lock_values
1709
1709
1710 @locked.setter
1710 @locked.setter
1711 def locked(self, val):
1711 def locked(self, val):
1712 if val and isinstance(val, (list, tuple)):
1712 if val and isinstance(val, (list, tuple)):
1713 self._locked = ':'.join(map(str, val))
1713 self._locked = ':'.join(map(str, val))
1714 else:
1714 else:
1715 self._locked = None
1715 self._locked = None
1716
1716
1717 @hybrid_property
1717 @hybrid_property
1718 def changeset_cache(self):
1718 def changeset_cache(self):
1719 from rhodecode.lib.vcs.backends.base import EmptyCommit
1719 from rhodecode.lib.vcs.backends.base import EmptyCommit
1720 dummy = EmptyCommit().__json__()
1720 dummy = EmptyCommit().__json__()
1721 if not self._changeset_cache:
1721 if not self._changeset_cache:
1722 return dummy
1722 return dummy
1723 try:
1723 try:
1724 return json.loads(self._changeset_cache)
1724 return json.loads(self._changeset_cache)
1725 except TypeError:
1725 except TypeError:
1726 return dummy
1726 return dummy
1727 except Exception:
1727 except Exception:
1728 log.error(traceback.format_exc())
1728 log.error(traceback.format_exc())
1729 return dummy
1729 return dummy
1730
1730
1731 @changeset_cache.setter
1731 @changeset_cache.setter
1732 def changeset_cache(self, val):
1732 def changeset_cache(self, val):
1733 try:
1733 try:
1734 self._changeset_cache = json.dumps(val)
1734 self._changeset_cache = json.dumps(val)
1735 except Exception:
1735 except Exception:
1736 log.error(traceback.format_exc())
1736 log.error(traceback.format_exc())
1737
1737
1738 @hybrid_property
1738 @hybrid_property
1739 def repo_name(self):
1739 def repo_name(self):
1740 return self._repo_name
1740 return self._repo_name
1741
1741
1742 @repo_name.setter
1742 @repo_name.setter
1743 def repo_name(self, value):
1743 def repo_name(self, value):
1744 self._repo_name = value
1744 self._repo_name = value
1745 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1745 self._repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1746
1746
1747 @classmethod
1747 @classmethod
1748 def normalize_repo_name(cls, repo_name):
1748 def normalize_repo_name(cls, repo_name):
1749 """
1749 """
1750 Normalizes os specific repo_name to the format internally stored inside
1750 Normalizes os specific repo_name to the format internally stored inside
1751 database using URL_SEP
1751 database using URL_SEP
1752
1752
1753 :param cls:
1753 :param cls:
1754 :param repo_name:
1754 :param repo_name:
1755 """
1755 """
1756 return cls.NAME_SEP.join(repo_name.split(os.sep))
1756 return cls.NAME_SEP.join(repo_name.split(os.sep))
1757
1757
1758 @classmethod
1758 @classmethod
1759 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1759 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1760 session = Session()
1760 session = Session()
1761 q = session.query(cls).filter(cls.repo_name == repo_name)
1761 q = session.query(cls).filter(cls.repo_name == repo_name)
1762
1762
1763 if cache:
1763 if cache:
1764 if identity_cache:
1764 if identity_cache:
1765 val = cls.identity_cache(session, 'repo_name', repo_name)
1765 val = cls.identity_cache(session, 'repo_name', repo_name)
1766 if val:
1766 if val:
1767 return val
1767 return val
1768 else:
1768 else:
1769 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1769 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1770 q = q.options(
1770 q = q.options(
1771 FromCache("sql_cache_short", cache_key))
1771 FromCache("sql_cache_short", cache_key))
1772
1772
1773 return q.scalar()
1773 return q.scalar()
1774
1774
1775 @classmethod
1775 @classmethod
1776 def get_by_id_or_repo_name(cls, repoid):
1776 def get_by_id_or_repo_name(cls, repoid):
1777 if isinstance(repoid, (int, long)):
1777 if isinstance(repoid, (int, long)):
1778 try:
1778 try:
1779 repo = cls.get(repoid)
1779 repo = cls.get(repoid)
1780 except ValueError:
1780 except ValueError:
1781 repo = None
1781 repo = None
1782 else:
1782 else:
1783 repo = cls.get_by_repo_name(repoid)
1783 repo = cls.get_by_repo_name(repoid)
1784 return repo
1784 return repo
1785
1785
1786 @classmethod
1786 @classmethod
1787 def get_by_full_path(cls, repo_full_path):
1787 def get_by_full_path(cls, repo_full_path):
1788 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1788 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1789 repo_name = cls.normalize_repo_name(repo_name)
1789 repo_name = cls.normalize_repo_name(repo_name)
1790 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1790 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1791
1791
1792 @classmethod
1792 @classmethod
1793 def get_repo_forks(cls, repo_id):
1793 def get_repo_forks(cls, repo_id):
1794 return cls.query().filter(Repository.fork_id == repo_id)
1794 return cls.query().filter(Repository.fork_id == repo_id)
1795
1795
1796 @classmethod
1796 @classmethod
1797 def base_path(cls):
1797 def base_path(cls):
1798 """
1798 """
1799 Returns base path when all repos are stored
1799 Returns base path when all repos are stored
1800
1800
1801 :param cls:
1801 :param cls:
1802 """
1802 """
1803 q = Session().query(RhodeCodeUi)\
1803 q = Session().query(RhodeCodeUi)\
1804 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1804 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1805 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1805 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1806 return q.one().ui_value
1806 return q.one().ui_value
1807
1807
1808 @classmethod
1808 @classmethod
1809 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1809 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1810 case_insensitive=True, archived=False):
1810 case_insensitive=True, archived=False):
1811 q = Repository.query()
1811 q = Repository.query()
1812
1812
1813 if not archived:
1813 if not archived:
1814 q = q.filter(Repository.archived.isnot(true()))
1814 q = q.filter(Repository.archived.isnot(true()))
1815
1815
1816 if not isinstance(user_id, Optional):
1816 if not isinstance(user_id, Optional):
1817 q = q.filter(Repository.user_id == user_id)
1817 q = q.filter(Repository.user_id == user_id)
1818
1818
1819 if not isinstance(group_id, Optional):
1819 if not isinstance(group_id, Optional):
1820 q = q.filter(Repository.group_id == group_id)
1820 q = q.filter(Repository.group_id == group_id)
1821
1821
1822 if case_insensitive:
1822 if case_insensitive:
1823 q = q.order_by(func.lower(Repository.repo_name))
1823 q = q.order_by(func.lower(Repository.repo_name))
1824 else:
1824 else:
1825 q = q.order_by(Repository.repo_name)
1825 q = q.order_by(Repository.repo_name)
1826
1826
1827 return q.all()
1827 return q.all()
1828
1828
1829 @property
1829 @property
1830 def forks(self):
1830 def forks(self):
1831 """
1831 """
1832 Return forks of this repo
1832 Return forks of this repo
1833 """
1833 """
1834 return Repository.get_repo_forks(self.repo_id)
1834 return Repository.get_repo_forks(self.repo_id)
1835
1835
1836 @property
1836 @property
1837 def parent(self):
1837 def parent(self):
1838 """
1838 """
1839 Returns fork parent
1839 Returns fork parent
1840 """
1840 """
1841 return self.fork
1841 return self.fork
1842
1842
1843 @property
1843 @property
1844 def just_name(self):
1844 def just_name(self):
1845 return self.repo_name.split(self.NAME_SEP)[-1]
1845 return self.repo_name.split(self.NAME_SEP)[-1]
1846
1846
1847 @property
1847 @property
1848 def groups_with_parents(self):
1848 def groups_with_parents(self):
1849 groups = []
1849 groups = []
1850 if self.group is None:
1850 if self.group is None:
1851 return groups
1851 return groups
1852
1852
1853 cur_gr = self.group
1853 cur_gr = self.group
1854 groups.insert(0, cur_gr)
1854 groups.insert(0, cur_gr)
1855 while 1:
1855 while 1:
1856 gr = getattr(cur_gr, 'parent_group', None)
1856 gr = getattr(cur_gr, 'parent_group', None)
1857 cur_gr = cur_gr.parent_group
1857 cur_gr = cur_gr.parent_group
1858 if gr is None:
1858 if gr is None:
1859 break
1859 break
1860 groups.insert(0, gr)
1860 groups.insert(0, gr)
1861
1861
1862 return groups
1862 return groups
1863
1863
1864 @property
1864 @property
1865 def groups_and_repo(self):
1865 def groups_and_repo(self):
1866 return self.groups_with_parents, self
1866 return self.groups_with_parents, self
1867
1867
1868 @LazyProperty
1868 @LazyProperty
1869 def repo_path(self):
1869 def repo_path(self):
1870 """
1870 """
1871 Returns base full path for that repository means where it actually
1871 Returns base full path for that repository means where it actually
1872 exists on a filesystem
1872 exists on a filesystem
1873 """
1873 """
1874 q = Session().query(RhodeCodeUi).filter(
1874 q = Session().query(RhodeCodeUi).filter(
1875 RhodeCodeUi.ui_key == self.NAME_SEP)
1875 RhodeCodeUi.ui_key == self.NAME_SEP)
1876 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1876 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1877 return q.one().ui_value
1877 return q.one().ui_value
1878
1878
1879 @property
1879 @property
1880 def repo_full_path(self):
1880 def repo_full_path(self):
1881 p = [self.repo_path]
1881 p = [self.repo_path]
1882 # we need to split the name by / since this is how we store the
1882 # we need to split the name by / since this is how we store the
1883 # names in the database, but that eventually needs to be converted
1883 # names in the database, but that eventually needs to be converted
1884 # into a valid system path
1884 # into a valid system path
1885 p += self.repo_name.split(self.NAME_SEP)
1885 p += self.repo_name.split(self.NAME_SEP)
1886 return os.path.join(*map(safe_unicode, p))
1886 return os.path.join(*map(safe_unicode, p))
1887
1887
1888 @property
1888 @property
1889 def cache_keys(self):
1889 def cache_keys(self):
1890 """
1890 """
1891 Returns associated cache keys for that repo
1891 Returns associated cache keys for that repo
1892 """
1892 """
1893 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1893 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
1894 repo_id=self.repo_id)
1894 repo_id=self.repo_id)
1895 return CacheKey.query()\
1895 return CacheKey.query()\
1896 .filter(CacheKey.cache_args == invalidation_namespace)\
1896 .filter(CacheKey.cache_args == invalidation_namespace)\
1897 .order_by(CacheKey.cache_key)\
1897 .order_by(CacheKey.cache_key)\
1898 .all()
1898 .all()
1899
1899
1900 @property
1900 @property
1901 def cached_diffs_relative_dir(self):
1901 def cached_diffs_relative_dir(self):
1902 """
1902 """
1903 Return a relative to the repository store path of cached diffs
1903 Return a relative to the repository store path of cached diffs
1904 used for safe display for users, who shouldn't know the absolute store
1904 used for safe display for users, who shouldn't know the absolute store
1905 path
1905 path
1906 """
1906 """
1907 return os.path.join(
1907 return os.path.join(
1908 os.path.dirname(self.repo_name),
1908 os.path.dirname(self.repo_name),
1909 self.cached_diffs_dir.split(os.path.sep)[-1])
1909 self.cached_diffs_dir.split(os.path.sep)[-1])
1910
1910
1911 @property
1911 @property
1912 def cached_diffs_dir(self):
1912 def cached_diffs_dir(self):
1913 path = self.repo_full_path
1913 path = self.repo_full_path
1914 return os.path.join(
1914 return os.path.join(
1915 os.path.dirname(path),
1915 os.path.dirname(path),
1916 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1916 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
1917
1917
1918 def cached_diffs(self):
1918 def cached_diffs(self):
1919 diff_cache_dir = self.cached_diffs_dir
1919 diff_cache_dir = self.cached_diffs_dir
1920 if os.path.isdir(diff_cache_dir):
1920 if os.path.isdir(diff_cache_dir):
1921 return os.listdir(diff_cache_dir)
1921 return os.listdir(diff_cache_dir)
1922 return []
1922 return []
1923
1923
1924 def shadow_repos(self):
1924 def shadow_repos(self):
1925 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1925 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
1926 return [
1926 return [
1927 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1927 x for x in os.listdir(os.path.dirname(self.repo_full_path))
1928 if x.startswith(shadow_repos_pattern)]
1928 if x.startswith(shadow_repos_pattern)]
1929
1929
1930 def get_new_name(self, repo_name):
1930 def get_new_name(self, repo_name):
1931 """
1931 """
1932 returns new full repository name based on assigned group and new new
1932 returns new full repository name based on assigned group and new new
1933
1933
1934 :param group_name:
1934 :param group_name:
1935 """
1935 """
1936 path_prefix = self.group.full_path_splitted if self.group else []
1936 path_prefix = self.group.full_path_splitted if self.group else []
1937 return self.NAME_SEP.join(path_prefix + [repo_name])
1937 return self.NAME_SEP.join(path_prefix + [repo_name])
1938
1938
1939 @property
1939 @property
1940 def _config(self):
1940 def _config(self):
1941 """
1941 """
1942 Returns db based config object.
1942 Returns db based config object.
1943 """
1943 """
1944 from rhodecode.lib.utils import make_db_config
1944 from rhodecode.lib.utils import make_db_config
1945 return make_db_config(clear_session=False, repo=self)
1945 return make_db_config(clear_session=False, repo=self)
1946
1946
1947 def permissions(self, with_admins=True, with_owner=True,
1947 def permissions(self, with_admins=True, with_owner=True,
1948 expand_from_user_groups=False):
1948 expand_from_user_groups=False):
1949 """
1949 """
1950 Permissions for repositories
1950 Permissions for repositories
1951 """
1951 """
1952 _admin_perm = 'repository.admin'
1952 _admin_perm = 'repository.admin'
1953
1953
1954 owner_row = []
1954 owner_row = []
1955 if with_owner:
1955 if with_owner:
1956 usr = AttributeDict(self.user.get_dict())
1956 usr = AttributeDict(self.user.get_dict())
1957 usr.owner_row = True
1957 usr.owner_row = True
1958 usr.permission = _admin_perm
1958 usr.permission = _admin_perm
1959 usr.permission_id = None
1959 usr.permission_id = None
1960 owner_row.append(usr)
1960 owner_row.append(usr)
1961
1961
1962 super_admin_ids = []
1962 super_admin_ids = []
1963 super_admin_rows = []
1963 super_admin_rows = []
1964 if with_admins:
1964 if with_admins:
1965 for usr in User.get_all_super_admins():
1965 for usr in User.get_all_super_admins():
1966 super_admin_ids.append(usr.user_id)
1966 super_admin_ids.append(usr.user_id)
1967 # if this admin is also owner, don't double the record
1967 # if this admin is also owner, don't double the record
1968 if usr.user_id == owner_row[0].user_id:
1968 if usr.user_id == owner_row[0].user_id:
1969 owner_row[0].admin_row = True
1969 owner_row[0].admin_row = True
1970 else:
1970 else:
1971 usr = AttributeDict(usr.get_dict())
1971 usr = AttributeDict(usr.get_dict())
1972 usr.admin_row = True
1972 usr.admin_row = True
1973 usr.permission = _admin_perm
1973 usr.permission = _admin_perm
1974 usr.permission_id = None
1974 usr.permission_id = None
1975 super_admin_rows.append(usr)
1975 super_admin_rows.append(usr)
1976
1976
1977 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1977 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
1978 q = q.options(joinedload(UserRepoToPerm.repository),
1978 q = q.options(joinedload(UserRepoToPerm.repository),
1979 joinedload(UserRepoToPerm.user),
1979 joinedload(UserRepoToPerm.user),
1980 joinedload(UserRepoToPerm.permission),)
1980 joinedload(UserRepoToPerm.permission),)
1981
1981
1982 # get owners and admins and permissions. We do a trick of re-writing
1982 # get owners and admins and permissions. We do a trick of re-writing
1983 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1983 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1984 # has a global reference and changing one object propagates to all
1984 # has a global reference and changing one object propagates to all
1985 # others. This means if admin is also an owner admin_row that change
1985 # others. This means if admin is also an owner admin_row that change
1986 # would propagate to both objects
1986 # would propagate to both objects
1987 perm_rows = []
1987 perm_rows = []
1988 for _usr in q.all():
1988 for _usr in q.all():
1989 usr = AttributeDict(_usr.user.get_dict())
1989 usr = AttributeDict(_usr.user.get_dict())
1990 # if this user is also owner/admin, mark as duplicate record
1990 # if this user is also owner/admin, mark as duplicate record
1991 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1991 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1992 usr.duplicate_perm = True
1992 usr.duplicate_perm = True
1993 # also check if this permission is maybe used by branch_permissions
1993 # also check if this permission is maybe used by branch_permissions
1994 if _usr.branch_perm_entry:
1994 if _usr.branch_perm_entry:
1995 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1995 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
1996
1996
1997 usr.permission = _usr.permission.permission_name
1997 usr.permission = _usr.permission.permission_name
1998 usr.permission_id = _usr.repo_to_perm_id
1998 usr.permission_id = _usr.repo_to_perm_id
1999 perm_rows.append(usr)
1999 perm_rows.append(usr)
2000
2000
2001 # filter the perm rows by 'default' first and then sort them by
2001 # filter the perm rows by 'default' first and then sort them by
2002 # admin,write,read,none permissions sorted again alphabetically in
2002 # admin,write,read,none permissions sorted again alphabetically in
2003 # each group
2003 # each group
2004 perm_rows = sorted(perm_rows, key=display_user_sort)
2004 perm_rows = sorted(perm_rows, key=display_user_sort)
2005
2005
2006 user_groups_rows = []
2006 user_groups_rows = []
2007 if expand_from_user_groups:
2007 if expand_from_user_groups:
2008 for ug in self.permission_user_groups(with_members=True):
2008 for ug in self.permission_user_groups(with_members=True):
2009 for user_data in ug.members:
2009 for user_data in ug.members:
2010 user_groups_rows.append(user_data)
2010 user_groups_rows.append(user_data)
2011
2011
2012 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2012 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2013
2013
2014 def permission_user_groups(self, with_members=True):
2014 def permission_user_groups(self, with_members=True):
2015 q = UserGroupRepoToPerm.query()\
2015 q = UserGroupRepoToPerm.query()\
2016 .filter(UserGroupRepoToPerm.repository == self)
2016 .filter(UserGroupRepoToPerm.repository == self)
2017 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2017 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2018 joinedload(UserGroupRepoToPerm.users_group),
2018 joinedload(UserGroupRepoToPerm.users_group),
2019 joinedload(UserGroupRepoToPerm.permission),)
2019 joinedload(UserGroupRepoToPerm.permission),)
2020
2020
2021 perm_rows = []
2021 perm_rows = []
2022 for _user_group in q.all():
2022 for _user_group in q.all():
2023 entry = AttributeDict(_user_group.users_group.get_dict())
2023 entry = AttributeDict(_user_group.users_group.get_dict())
2024 entry.permission = _user_group.permission.permission_name
2024 entry.permission = _user_group.permission.permission_name
2025 if with_members:
2025 if with_members:
2026 entry.members = [x.user.get_dict()
2026 entry.members = [x.user.get_dict()
2027 for x in _user_group.users_group.members]
2027 for x in _user_group.users_group.members]
2028 perm_rows.append(entry)
2028 perm_rows.append(entry)
2029
2029
2030 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2030 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2031 return perm_rows
2031 return perm_rows
2032
2032
2033 def get_api_data(self, include_secrets=False):
2033 def get_api_data(self, include_secrets=False):
2034 """
2034 """
2035 Common function for generating repo api data
2035 Common function for generating repo api data
2036
2036
2037 :param include_secrets: See :meth:`User.get_api_data`.
2037 :param include_secrets: See :meth:`User.get_api_data`.
2038
2038
2039 """
2039 """
2040 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2040 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2041 # move this methods on models level.
2041 # move this methods on models level.
2042 from rhodecode.model.settings import SettingsModel
2042 from rhodecode.model.settings import SettingsModel
2043 from rhodecode.model.repo import RepoModel
2043 from rhodecode.model.repo import RepoModel
2044
2044
2045 repo = self
2045 repo = self
2046 _user_id, _time, _reason = self.locked
2046 _user_id, _time, _reason = self.locked
2047
2047
2048 data = {
2048 data = {
2049 'repo_id': repo.repo_id,
2049 'repo_id': repo.repo_id,
2050 'repo_name': repo.repo_name,
2050 'repo_name': repo.repo_name,
2051 'repo_type': repo.repo_type,
2051 'repo_type': repo.repo_type,
2052 'clone_uri': repo.clone_uri or '',
2052 'clone_uri': repo.clone_uri or '',
2053 'push_uri': repo.push_uri or '',
2053 'push_uri': repo.push_uri or '',
2054 'url': RepoModel().get_url(self),
2054 'url': RepoModel().get_url(self),
2055 'private': repo.private,
2055 'private': repo.private,
2056 'created_on': repo.created_on,
2056 'created_on': repo.created_on,
2057 'description': repo.description_safe,
2057 'description': repo.description_safe,
2058 'landing_rev': repo.landing_rev,
2058 'landing_rev': repo.landing_rev,
2059 'owner': repo.user.username,
2059 'owner': repo.user.username,
2060 'fork_of': repo.fork.repo_name if repo.fork else None,
2060 'fork_of': repo.fork.repo_name if repo.fork else None,
2061 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2061 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2062 'enable_statistics': repo.enable_statistics,
2062 'enable_statistics': repo.enable_statistics,
2063 'enable_locking': repo.enable_locking,
2063 'enable_locking': repo.enable_locking,
2064 'enable_downloads': repo.enable_downloads,
2064 'enable_downloads': repo.enable_downloads,
2065 'last_changeset': repo.changeset_cache,
2065 'last_changeset': repo.changeset_cache,
2066 'locked_by': User.get(_user_id).get_api_data(
2066 'locked_by': User.get(_user_id).get_api_data(
2067 include_secrets=include_secrets) if _user_id else None,
2067 include_secrets=include_secrets) if _user_id else None,
2068 'locked_date': time_to_datetime(_time) if _time else None,
2068 'locked_date': time_to_datetime(_time) if _time else None,
2069 'lock_reason': _reason if _reason else None,
2069 'lock_reason': _reason if _reason else None,
2070 }
2070 }
2071
2071
2072 # TODO: mikhail: should be per-repo settings here
2072 # TODO: mikhail: should be per-repo settings here
2073 rc_config = SettingsModel().get_all_settings()
2073 rc_config = SettingsModel().get_all_settings()
2074 repository_fields = str2bool(
2074 repository_fields = str2bool(
2075 rc_config.get('rhodecode_repository_fields'))
2075 rc_config.get('rhodecode_repository_fields'))
2076 if repository_fields:
2076 if repository_fields:
2077 for f in self.extra_fields:
2077 for f in self.extra_fields:
2078 data[f.field_key_prefixed] = f.field_value
2078 data[f.field_key_prefixed] = f.field_value
2079
2079
2080 return data
2080 return data
2081
2081
2082 @classmethod
2082 @classmethod
2083 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2083 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2084 if not lock_time:
2084 if not lock_time:
2085 lock_time = time.time()
2085 lock_time = time.time()
2086 if not lock_reason:
2086 if not lock_reason:
2087 lock_reason = cls.LOCK_AUTOMATIC
2087 lock_reason = cls.LOCK_AUTOMATIC
2088 repo.locked = [user_id, lock_time, lock_reason]
2088 repo.locked = [user_id, lock_time, lock_reason]
2089 Session().add(repo)
2089 Session().add(repo)
2090 Session().commit()
2090 Session().commit()
2091
2091
2092 @classmethod
2092 @classmethod
2093 def unlock(cls, repo):
2093 def unlock(cls, repo):
2094 repo.locked = None
2094 repo.locked = None
2095 Session().add(repo)
2095 Session().add(repo)
2096 Session().commit()
2096 Session().commit()
2097
2097
2098 @classmethod
2098 @classmethod
2099 def getlock(cls, repo):
2099 def getlock(cls, repo):
2100 return repo.locked
2100 return repo.locked
2101
2101
2102 def is_user_lock(self, user_id):
2102 def is_user_lock(self, user_id):
2103 if self.lock[0]:
2103 if self.lock[0]:
2104 lock_user_id = safe_int(self.lock[0])
2104 lock_user_id = safe_int(self.lock[0])
2105 user_id = safe_int(user_id)
2105 user_id = safe_int(user_id)
2106 # both are ints, and they are equal
2106 # both are ints, and they are equal
2107 return all([lock_user_id, user_id]) and lock_user_id == user_id
2107 return all([lock_user_id, user_id]) and lock_user_id == user_id
2108
2108
2109 return False
2109 return False
2110
2110
2111 def get_locking_state(self, action, user_id, only_when_enabled=True):
2111 def get_locking_state(self, action, user_id, only_when_enabled=True):
2112 """
2112 """
2113 Checks locking on this repository, if locking is enabled and lock is
2113 Checks locking on this repository, if locking is enabled and lock is
2114 present returns a tuple of make_lock, locked, locked_by.
2114 present returns a tuple of make_lock, locked, locked_by.
2115 make_lock can have 3 states None (do nothing) True, make lock
2115 make_lock can have 3 states None (do nothing) True, make lock
2116 False release lock, This value is later propagated to hooks, which
2116 False release lock, This value is later propagated to hooks, which
2117 do the locking. Think about this as signals passed to hooks what to do.
2117 do the locking. Think about this as signals passed to hooks what to do.
2118
2118
2119 """
2119 """
2120 # TODO: johbo: This is part of the business logic and should be moved
2120 # TODO: johbo: This is part of the business logic and should be moved
2121 # into the RepositoryModel.
2121 # into the RepositoryModel.
2122
2122
2123 if action not in ('push', 'pull'):
2123 if action not in ('push', 'pull'):
2124 raise ValueError("Invalid action value: %s" % repr(action))
2124 raise ValueError("Invalid action value: %s" % repr(action))
2125
2125
2126 # defines if locked error should be thrown to user
2126 # defines if locked error should be thrown to user
2127 currently_locked = False
2127 currently_locked = False
2128 # defines if new lock should be made, tri-state
2128 # defines if new lock should be made, tri-state
2129 make_lock = None
2129 make_lock = None
2130 repo = self
2130 repo = self
2131 user = User.get(user_id)
2131 user = User.get(user_id)
2132
2132
2133 lock_info = repo.locked
2133 lock_info = repo.locked
2134
2134
2135 if repo and (repo.enable_locking or not only_when_enabled):
2135 if repo and (repo.enable_locking or not only_when_enabled):
2136 if action == 'push':
2136 if action == 'push':
2137 # check if it's already locked !, if it is compare users
2137 # check if it's already locked !, if it is compare users
2138 locked_by_user_id = lock_info[0]
2138 locked_by_user_id = lock_info[0]
2139 if user.user_id == locked_by_user_id:
2139 if user.user_id == locked_by_user_id:
2140 log.debug(
2140 log.debug(
2141 'Got `push` action from user %s, now unlocking', user)
2141 'Got `push` action from user %s, now unlocking', user)
2142 # unlock if we have push from user who locked
2142 # unlock if we have push from user who locked
2143 make_lock = False
2143 make_lock = False
2144 else:
2144 else:
2145 # we're not the same user who locked, ban with
2145 # we're not the same user who locked, ban with
2146 # code defined in settings (default is 423 HTTP Locked) !
2146 # code defined in settings (default is 423 HTTP Locked) !
2147 log.debug('Repo %s is currently locked by %s', repo, user)
2147 log.debug('Repo %s is currently locked by %s', repo, user)
2148 currently_locked = True
2148 currently_locked = True
2149 elif action == 'pull':
2149 elif action == 'pull':
2150 # [0] user [1] date
2150 # [0] user [1] date
2151 if lock_info[0] and lock_info[1]:
2151 if lock_info[0] and lock_info[1]:
2152 log.debug('Repo %s is currently locked by %s', repo, user)
2152 log.debug('Repo %s is currently locked by %s', repo, user)
2153 currently_locked = True
2153 currently_locked = True
2154 else:
2154 else:
2155 log.debug('Setting lock on repo %s by %s', repo, user)
2155 log.debug('Setting lock on repo %s by %s', repo, user)
2156 make_lock = True
2156 make_lock = True
2157
2157
2158 else:
2158 else:
2159 log.debug('Repository %s do not have locking enabled', repo)
2159 log.debug('Repository %s do not have locking enabled', repo)
2160
2160
2161 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2161 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2162 make_lock, currently_locked, lock_info)
2162 make_lock, currently_locked, lock_info)
2163
2163
2164 from rhodecode.lib.auth import HasRepoPermissionAny
2164 from rhodecode.lib.auth import HasRepoPermissionAny
2165 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2165 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2166 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2166 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2167 # if we don't have at least write permission we cannot make a lock
2167 # if we don't have at least write permission we cannot make a lock
2168 log.debug('lock state reset back to FALSE due to lack '
2168 log.debug('lock state reset back to FALSE due to lack '
2169 'of at least read permission')
2169 'of at least read permission')
2170 make_lock = False
2170 make_lock = False
2171
2171
2172 return make_lock, currently_locked, lock_info
2172 return make_lock, currently_locked, lock_info
2173
2173
2174 @property
2174 @property
2175 def last_db_change(self):
2175 def last_db_change(self):
2176 return self.updated_on
2176 return self.updated_on
2177
2177
2178 @property
2178 @property
2179 def clone_uri_hidden(self):
2179 def clone_uri_hidden(self):
2180 clone_uri = self.clone_uri
2180 clone_uri = self.clone_uri
2181 if clone_uri:
2181 if clone_uri:
2182 import urlobject
2182 import urlobject
2183 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2183 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2184 if url_obj.password:
2184 if url_obj.password:
2185 clone_uri = url_obj.with_password('*****')
2185 clone_uri = url_obj.with_password('*****')
2186 return clone_uri
2186 return clone_uri
2187
2187
2188 @property
2188 @property
2189 def push_uri_hidden(self):
2189 def push_uri_hidden(self):
2190 push_uri = self.push_uri
2190 push_uri = self.push_uri
2191 if push_uri:
2191 if push_uri:
2192 import urlobject
2192 import urlobject
2193 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2193 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2194 if url_obj.password:
2194 if url_obj.password:
2195 push_uri = url_obj.with_password('*****')
2195 push_uri = url_obj.with_password('*****')
2196 return push_uri
2196 return push_uri
2197
2197
2198 def clone_url(self, **override):
2198 def clone_url(self, **override):
2199 from rhodecode.model.settings import SettingsModel
2199 from rhodecode.model.settings import SettingsModel
2200
2200
2201 uri_tmpl = None
2201 uri_tmpl = None
2202 if 'with_id' in override:
2202 if 'with_id' in override:
2203 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2203 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2204 del override['with_id']
2204 del override['with_id']
2205
2205
2206 if 'uri_tmpl' in override:
2206 if 'uri_tmpl' in override:
2207 uri_tmpl = override['uri_tmpl']
2207 uri_tmpl = override['uri_tmpl']
2208 del override['uri_tmpl']
2208 del override['uri_tmpl']
2209
2209
2210 ssh = False
2210 ssh = False
2211 if 'ssh' in override:
2211 if 'ssh' in override:
2212 ssh = True
2212 ssh = True
2213 del override['ssh']
2213 del override['ssh']
2214
2214
2215 # we didn't override our tmpl from **overrides
2215 # we didn't override our tmpl from **overrides
2216 if not uri_tmpl:
2216 if not uri_tmpl:
2217 rc_config = SettingsModel().get_all_settings(cache=True)
2217 rc_config = SettingsModel().get_all_settings(cache=True)
2218 if ssh:
2218 if ssh:
2219 uri_tmpl = rc_config.get(
2219 uri_tmpl = rc_config.get(
2220 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2220 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2221 else:
2221 else:
2222 uri_tmpl = rc_config.get(
2222 uri_tmpl = rc_config.get(
2223 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2223 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2224
2224
2225 request = get_current_request()
2225 request = get_current_request()
2226 return get_clone_url(request=request,
2226 return get_clone_url(request=request,
2227 uri_tmpl=uri_tmpl,
2227 uri_tmpl=uri_tmpl,
2228 repo_name=self.repo_name,
2228 repo_name=self.repo_name,
2229 repo_id=self.repo_id, **override)
2229 repo_id=self.repo_id, **override)
2230
2230
2231 def set_state(self, state):
2231 def set_state(self, state):
2232 self.repo_state = state
2232 self.repo_state = state
2233 Session().add(self)
2233 Session().add(self)
2234 #==========================================================================
2234 #==========================================================================
2235 # SCM PROPERTIES
2235 # SCM PROPERTIES
2236 #==========================================================================
2236 #==========================================================================
2237
2237
2238 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2238 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None):
2239 return get_commit_safe(
2239 return get_commit_safe(
2240 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2240 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load)
2241
2241
2242 def get_changeset(self, rev=None, pre_load=None):
2242 def get_changeset(self, rev=None, pre_load=None):
2243 warnings.warn("Use get_commit", DeprecationWarning)
2243 warnings.warn("Use get_commit", DeprecationWarning)
2244 commit_id = None
2244 commit_id = None
2245 commit_idx = None
2245 commit_idx = None
2246 if isinstance(rev, compat.string_types):
2246 if isinstance(rev, compat.string_types):
2247 commit_id = rev
2247 commit_id = rev
2248 else:
2248 else:
2249 commit_idx = rev
2249 commit_idx = rev
2250 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2250 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2251 pre_load=pre_load)
2251 pre_load=pre_load)
2252
2252
2253 def get_landing_commit(self):
2253 def get_landing_commit(self):
2254 """
2254 """
2255 Returns landing commit, or if that doesn't exist returns the tip
2255 Returns landing commit, or if that doesn't exist returns the tip
2256 """
2256 """
2257 _rev_type, _rev = self.landing_rev
2257 _rev_type, _rev = self.landing_rev
2258 commit = self.get_commit(_rev)
2258 commit = self.get_commit(_rev)
2259 if isinstance(commit, EmptyCommit):
2259 if isinstance(commit, EmptyCommit):
2260 return self.get_commit()
2260 return self.get_commit()
2261 return commit
2261 return commit
2262
2262
2263 def update_commit_cache(self, cs_cache=None, config=None):
2263 def update_commit_cache(self, cs_cache=None, config=None):
2264 """
2264 """
2265 Update cache of last changeset for repository, keys should be::
2265 Update cache of last changeset for repository, keys should be::
2266
2266
2267 short_id
2267 short_id
2268 raw_id
2268 raw_id
2269 revision
2269 revision
2270 parents
2270 parents
2271 message
2271 message
2272 date
2272 date
2273 author
2273 author
2274
2274
2275 :param cs_cache:
2275 :param cs_cache:
2276 """
2276 """
2277 from rhodecode.lib.vcs.backends.base import BaseChangeset
2277 from rhodecode.lib.vcs.backends.base import BaseChangeset
2278 if cs_cache is None:
2278 if cs_cache is None:
2279 # use no-cache version here
2279 # use no-cache version here
2280 scm_repo = self.scm_instance(cache=False, config=config)
2280 scm_repo = self.scm_instance(cache=False, config=config)
2281
2281
2282 empty = not scm_repo or scm_repo.is_empty()
2282 empty = not scm_repo or scm_repo.is_empty()
2283 if not empty:
2283 if not empty:
2284 cs_cache = scm_repo.get_commit(
2284 cs_cache = scm_repo.get_commit(
2285 pre_load=["author", "date", "message", "parents"])
2285 pre_load=["author", "date", "message", "parents"])
2286 else:
2286 else:
2287 cs_cache = EmptyCommit()
2287 cs_cache = EmptyCommit()
2288
2288
2289 if isinstance(cs_cache, BaseChangeset):
2289 if isinstance(cs_cache, BaseChangeset):
2290 cs_cache = cs_cache.__json__()
2290 cs_cache = cs_cache.__json__()
2291
2291
2292 def is_outdated(new_cs_cache):
2292 def is_outdated(new_cs_cache):
2293 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2293 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2294 new_cs_cache['revision'] != self.changeset_cache['revision']):
2294 new_cs_cache['revision'] != self.changeset_cache['revision']):
2295 return True
2295 return True
2296 return False
2296 return False
2297
2297
2298 # check if we have maybe already latest cached revision
2298 # check if we have maybe already latest cached revision
2299 if is_outdated(cs_cache) or not self.changeset_cache:
2299 if is_outdated(cs_cache) or not self.changeset_cache:
2300 _default = datetime.datetime.utcnow()
2300 _default = datetime.datetime.utcnow()
2301 last_change = cs_cache.get('date') or _default
2301 last_change = cs_cache.get('date') or _default
2302 if self.updated_on and self.updated_on > last_change:
2302 if self.updated_on and self.updated_on > last_change:
2303 # we check if last update is newer than the new value
2303 # we check if last update is newer than the new value
2304 # if yes, we use the current timestamp instead. Imagine you get
2304 # if yes, we use the current timestamp instead. Imagine you get
2305 # old commit pushed 1y ago, we'd set last update 1y to ago.
2305 # old commit pushed 1y ago, we'd set last update 1y to ago.
2306 last_change = _default
2306 last_change = _default
2307 log.debug('updated repo %s with new cs cache %s',
2307 log.debug('updated repo %s with new cs cache %s',
2308 self.repo_name, cs_cache)
2308 self.repo_name, cs_cache)
2309 self.updated_on = last_change
2309 self.updated_on = last_change
2310 self.changeset_cache = cs_cache
2310 self.changeset_cache = cs_cache
2311 Session().add(self)
2311 Session().add(self)
2312 Session().commit()
2312 Session().commit()
2313 else:
2313 else:
2314 log.debug('Skipping update_commit_cache for repo:`%s` '
2314 log.debug('Skipping update_commit_cache for repo:`%s` '
2315 'commit already with latest changes', self.repo_name)
2315 'commit already with latest changes', self.repo_name)
2316
2316
2317 @property
2317 @property
2318 def tip(self):
2318 def tip(self):
2319 return self.get_commit('tip')
2319 return self.get_commit('tip')
2320
2320
2321 @property
2321 @property
2322 def author(self):
2322 def author(self):
2323 return self.tip.author
2323 return self.tip.author
2324
2324
2325 @property
2325 @property
2326 def last_change(self):
2326 def last_change(self):
2327 return self.scm_instance().last_change
2327 return self.scm_instance().last_change
2328
2328
2329 def get_comments(self, revisions=None):
2329 def get_comments(self, revisions=None):
2330 """
2330 """
2331 Returns comments for this repository grouped by revisions
2331 Returns comments for this repository grouped by revisions
2332
2332
2333 :param revisions: filter query by revisions only
2333 :param revisions: filter query by revisions only
2334 """
2334 """
2335 cmts = ChangesetComment.query()\
2335 cmts = ChangesetComment.query()\
2336 .filter(ChangesetComment.repo == self)
2336 .filter(ChangesetComment.repo == self)
2337 if revisions:
2337 if revisions:
2338 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2338 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2339 grouped = collections.defaultdict(list)
2339 grouped = collections.defaultdict(list)
2340 for cmt in cmts.all():
2340 for cmt in cmts.all():
2341 grouped[cmt.revision].append(cmt)
2341 grouped[cmt.revision].append(cmt)
2342 return grouped
2342 return grouped
2343
2343
2344 def statuses(self, revisions=None):
2344 def statuses(self, revisions=None):
2345 """
2345 """
2346 Returns statuses for this repository
2346 Returns statuses for this repository
2347
2347
2348 :param revisions: list of revisions to get statuses for
2348 :param revisions: list of revisions to get statuses for
2349 """
2349 """
2350 statuses = ChangesetStatus.query()\
2350 statuses = ChangesetStatus.query()\
2351 .filter(ChangesetStatus.repo == self)\
2351 .filter(ChangesetStatus.repo == self)\
2352 .filter(ChangesetStatus.version == 0)
2352 .filter(ChangesetStatus.version == 0)
2353
2353
2354 if revisions:
2354 if revisions:
2355 # Try doing the filtering in chunks to avoid hitting limits
2355 # Try doing the filtering in chunks to avoid hitting limits
2356 size = 500
2356 size = 500
2357 status_results = []
2357 status_results = []
2358 for chunk in xrange(0, len(revisions), size):
2358 for chunk in xrange(0, len(revisions), size):
2359 status_results += statuses.filter(
2359 status_results += statuses.filter(
2360 ChangesetStatus.revision.in_(
2360 ChangesetStatus.revision.in_(
2361 revisions[chunk: chunk+size])
2361 revisions[chunk: chunk+size])
2362 ).all()
2362 ).all()
2363 else:
2363 else:
2364 status_results = statuses.all()
2364 status_results = statuses.all()
2365
2365
2366 grouped = {}
2366 grouped = {}
2367
2367
2368 # maybe we have open new pullrequest without a status?
2368 # maybe we have open new pullrequest without a status?
2369 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2369 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2370 status_lbl = ChangesetStatus.get_status_lbl(stat)
2370 status_lbl = ChangesetStatus.get_status_lbl(stat)
2371 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2371 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2372 for rev in pr.revisions:
2372 for rev in pr.revisions:
2373 pr_id = pr.pull_request_id
2373 pr_id = pr.pull_request_id
2374 pr_repo = pr.target_repo.repo_name
2374 pr_repo = pr.target_repo.repo_name
2375 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2375 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2376
2376
2377 for stat in status_results:
2377 for stat in status_results:
2378 pr_id = pr_repo = None
2378 pr_id = pr_repo = None
2379 if stat.pull_request:
2379 if stat.pull_request:
2380 pr_id = stat.pull_request.pull_request_id
2380 pr_id = stat.pull_request.pull_request_id
2381 pr_repo = stat.pull_request.target_repo.repo_name
2381 pr_repo = stat.pull_request.target_repo.repo_name
2382 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2382 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2383 pr_id, pr_repo]
2383 pr_id, pr_repo]
2384 return grouped
2384 return grouped
2385
2385
2386 # ==========================================================================
2386 # ==========================================================================
2387 # SCM CACHE INSTANCE
2387 # SCM CACHE INSTANCE
2388 # ==========================================================================
2388 # ==========================================================================
2389
2389
2390 def scm_instance(self, **kwargs):
2390 def scm_instance(self, **kwargs):
2391 import rhodecode
2391 import rhodecode
2392
2392
2393 # Passing a config will not hit the cache currently only used
2393 # Passing a config will not hit the cache currently only used
2394 # for repo2dbmapper
2394 # for repo2dbmapper
2395 config = kwargs.pop('config', None)
2395 config = kwargs.pop('config', None)
2396 cache = kwargs.pop('cache', None)
2396 cache = kwargs.pop('cache', None)
2397 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2397 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2398 # if cache is NOT defined use default global, else we have a full
2398 # if cache is NOT defined use default global, else we have a full
2399 # control over cache behaviour
2399 # control over cache behaviour
2400 if cache is None and full_cache and not config:
2400 if cache is None and full_cache and not config:
2401 return self._get_instance_cached()
2401 return self._get_instance_cached()
2402 return self._get_instance(cache=bool(cache), config=config)
2402 return self._get_instance(cache=bool(cache), config=config)
2403
2403
2404 def _get_instance_cached(self):
2404 def _get_instance_cached(self):
2405 from rhodecode.lib import rc_cache
2405 from rhodecode.lib import rc_cache
2406
2406
2407 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2407 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2408 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2408 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2409 repo_id=self.repo_id)
2409 repo_id=self.repo_id)
2410 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2410 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2411
2411
2412 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2412 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2413 def get_instance_cached(repo_id, context_id):
2413 def get_instance_cached(repo_id, context_id):
2414 return self._get_instance()
2414 return self._get_instance()
2415
2415
2416 # we must use thread scoped cache here,
2416 # we must use thread scoped cache here,
2417 # because each thread of gevent needs it's own not shared connection and cache
2417 # because each thread of gevent needs it's own not shared connection and cache
2418 # we also alter `args` so the cache key is individual for every green thread.
2418 # we also alter `args` so the cache key is individual for every green thread.
2419 inv_context_manager = rc_cache.InvalidationContext(
2419 inv_context_manager = rc_cache.InvalidationContext(
2420 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2420 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2421 thread_scoped=True)
2421 thread_scoped=True)
2422 with inv_context_manager as invalidation_context:
2422 with inv_context_manager as invalidation_context:
2423 args = (self.repo_id, inv_context_manager.cache_key)
2423 args = (self.repo_id, inv_context_manager.cache_key)
2424 # re-compute and store cache if we get invalidate signal
2424 # re-compute and store cache if we get invalidate signal
2425 if invalidation_context.should_invalidate():
2425 if invalidation_context.should_invalidate():
2426 instance = get_instance_cached.refresh(*args)
2426 instance = get_instance_cached.refresh(*args)
2427 else:
2427 else:
2428 instance = get_instance_cached(*args)
2428 instance = get_instance_cached(*args)
2429
2429
2430 log.debug(
2430 log.debug(
2431 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2431 'Repo instance fetched in %.3fs', inv_context_manager.compute_time)
2432 return instance
2432 return instance
2433
2433
2434 def _get_instance(self, cache=True, config=None):
2434 def _get_instance(self, cache=True, config=None):
2435 config = config or self._config
2435 config = config or self._config
2436 custom_wire = {
2436 custom_wire = {
2437 'cache': cache # controls the vcs.remote cache
2437 'cache': cache # controls the vcs.remote cache
2438 }
2438 }
2439 repo = get_vcs_instance(
2439 repo = get_vcs_instance(
2440 repo_path=safe_str(self.repo_full_path),
2440 repo_path=safe_str(self.repo_full_path),
2441 config=config,
2441 config=config,
2442 with_wire=custom_wire,
2442 with_wire=custom_wire,
2443 create=False,
2443 create=False,
2444 _vcs_alias=self.repo_type)
2444 _vcs_alias=self.repo_type)
2445
2445
2446 return repo
2446 return repo
2447
2447
2448 def __json__(self):
2448 def __json__(self):
2449 return {'landing_rev': self.landing_rev}
2449 return {'landing_rev': self.landing_rev}
2450
2450
2451 def get_dict(self):
2451 def get_dict(self):
2452
2452
2453 # Since we transformed `repo_name` to a hybrid property, we need to
2453 # Since we transformed `repo_name` to a hybrid property, we need to
2454 # keep compatibility with the code which uses `repo_name` field.
2454 # keep compatibility with the code which uses `repo_name` field.
2455
2455
2456 result = super(Repository, self).get_dict()
2456 result = super(Repository, self).get_dict()
2457 result['repo_name'] = result.pop('_repo_name', None)
2457 result['repo_name'] = result.pop('_repo_name', None)
2458 return result
2458 return result
2459
2459
2460
2460
2461 class RepoGroup(Base, BaseModel):
2461 class RepoGroup(Base, BaseModel):
2462 __tablename__ = 'groups'
2462 __tablename__ = 'groups'
2463 __table_args__ = (
2463 __table_args__ = (
2464 UniqueConstraint('group_name', 'group_parent_id'),
2464 UniqueConstraint('group_name', 'group_parent_id'),
2465 base_table_args,
2465 base_table_args,
2466 )
2466 )
2467 __mapper_args__ = {'order_by': 'group_name'}
2467 __mapper_args__ = {'order_by': 'group_name'}
2468
2468
2469 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2469 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2470
2470
2471 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2471 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2472 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2472 group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2473 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2473 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2474 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2474 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2475 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2475 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2476 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2476 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2477 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2477 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2478 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2478 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2479 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2479 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2480
2480
2481 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2481 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2482 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2482 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2483 parent_group = relationship('RepoGroup', remote_side=group_id)
2483 parent_group = relationship('RepoGroup', remote_side=group_id)
2484 user = relationship('User')
2484 user = relationship('User')
2485 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2485 integrations = relationship('Integration', cascade="all, delete, delete-orphan")
2486
2486
2487 def __init__(self, group_name='', parent_group=None):
2487 def __init__(self, group_name='', parent_group=None):
2488 self.group_name = group_name
2488 self.group_name = group_name
2489 self.parent_group = parent_group
2489 self.parent_group = parent_group
2490
2490
2491 def __unicode__(self):
2491 def __unicode__(self):
2492 return u"<%s('id:%s:%s')>" % (
2492 return u"<%s('id:%s:%s')>" % (
2493 self.__class__.__name__, self.group_id, self.group_name)
2493 self.__class__.__name__, self.group_id, self.group_name)
2494
2494
2495 @validates('group_parent_id')
2495 @validates('group_parent_id')
2496 def validate_group_parent_id(self, key, val):
2496 def validate_group_parent_id(self, key, val):
2497 """
2497 """
2498 Check cycle references for a parent group to self
2498 Check cycle references for a parent group to self
2499 """
2499 """
2500 if self.group_id and val:
2500 if self.group_id and val:
2501 assert val != self.group_id
2501 assert val != self.group_id
2502
2502
2503 return val
2503 return val
2504
2504
2505 @hybrid_property
2505 @hybrid_property
2506 def description_safe(self):
2506 def description_safe(self):
2507 from rhodecode.lib import helpers as h
2507 from rhodecode.lib import helpers as h
2508 return h.escape(self.group_description)
2508 return h.escape(self.group_description)
2509
2509
2510 @classmethod
2510 @classmethod
2511 def _generate_choice(cls, repo_group):
2511 def _generate_choice(cls, repo_group):
2512 from webhelpers.html import literal as _literal
2512 from webhelpers.html import literal as _literal
2513 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2513 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2514 return repo_group.group_id, _name(repo_group.full_path_splitted)
2514 return repo_group.group_id, _name(repo_group.full_path_splitted)
2515
2515
2516 @classmethod
2516 @classmethod
2517 def groups_choices(cls, groups=None, show_empty_group=True):
2517 def groups_choices(cls, groups=None, show_empty_group=True):
2518 if not groups:
2518 if not groups:
2519 groups = cls.query().all()
2519 groups = cls.query().all()
2520
2520
2521 repo_groups = []
2521 repo_groups = []
2522 if show_empty_group:
2522 if show_empty_group:
2523 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2523 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2524
2524
2525 repo_groups.extend([cls._generate_choice(x) for x in groups])
2525 repo_groups.extend([cls._generate_choice(x) for x in groups])
2526
2526
2527 repo_groups = sorted(
2527 repo_groups = sorted(
2528 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2528 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2529 return repo_groups
2529 return repo_groups
2530
2530
2531 @classmethod
2531 @classmethod
2532 def url_sep(cls):
2532 def url_sep(cls):
2533 return URL_SEP
2533 return URL_SEP
2534
2534
2535 @classmethod
2535 @classmethod
2536 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2536 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2537 if case_insensitive:
2537 if case_insensitive:
2538 gr = cls.query().filter(func.lower(cls.group_name)
2538 gr = cls.query().filter(func.lower(cls.group_name)
2539 == func.lower(group_name))
2539 == func.lower(group_name))
2540 else:
2540 else:
2541 gr = cls.query().filter(cls.group_name == group_name)
2541 gr = cls.query().filter(cls.group_name == group_name)
2542 if cache:
2542 if cache:
2543 name_key = _hash_key(group_name)
2543 name_key = _hash_key(group_name)
2544 gr = gr.options(
2544 gr = gr.options(
2545 FromCache("sql_cache_short", "get_group_%s" % name_key))
2545 FromCache("sql_cache_short", "get_group_%s" % name_key))
2546 return gr.scalar()
2546 return gr.scalar()
2547
2547
2548 @classmethod
2548 @classmethod
2549 def get_user_personal_repo_group(cls, user_id):
2549 def get_user_personal_repo_group(cls, user_id):
2550 user = User.get(user_id)
2550 user = User.get(user_id)
2551 if user.username == User.DEFAULT_USER:
2551 if user.username == User.DEFAULT_USER:
2552 return None
2552 return None
2553
2553
2554 return cls.query()\
2554 return cls.query()\
2555 .filter(cls.personal == true()) \
2555 .filter(cls.personal == true()) \
2556 .filter(cls.user == user) \
2556 .filter(cls.user == user) \
2557 .order_by(cls.group_id.asc()) \
2557 .order_by(cls.group_id.asc()) \
2558 .first()
2558 .first()
2559
2559
2560 @classmethod
2560 @classmethod
2561 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2561 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2562 case_insensitive=True):
2562 case_insensitive=True):
2563 q = RepoGroup.query()
2563 q = RepoGroup.query()
2564
2564
2565 if not isinstance(user_id, Optional):
2565 if not isinstance(user_id, Optional):
2566 q = q.filter(RepoGroup.user_id == user_id)
2566 q = q.filter(RepoGroup.user_id == user_id)
2567
2567
2568 if not isinstance(group_id, Optional):
2568 if not isinstance(group_id, Optional):
2569 q = q.filter(RepoGroup.group_parent_id == group_id)
2569 q = q.filter(RepoGroup.group_parent_id == group_id)
2570
2570
2571 if case_insensitive:
2571 if case_insensitive:
2572 q = q.order_by(func.lower(RepoGroup.group_name))
2572 q = q.order_by(func.lower(RepoGroup.group_name))
2573 else:
2573 else:
2574 q = q.order_by(RepoGroup.group_name)
2574 q = q.order_by(RepoGroup.group_name)
2575 return q.all()
2575 return q.all()
2576
2576
2577 @property
2577 @property
2578 def parents(self):
2578 def parents(self):
2579 parents_recursion_limit = 10
2579 parents_recursion_limit = 10
2580 groups = []
2580 groups = []
2581 if self.parent_group is None:
2581 if self.parent_group is None:
2582 return groups
2582 return groups
2583 cur_gr = self.parent_group
2583 cur_gr = self.parent_group
2584 groups.insert(0, cur_gr)
2584 groups.insert(0, cur_gr)
2585 cnt = 0
2585 cnt = 0
2586 while 1:
2586 while 1:
2587 cnt += 1
2587 cnt += 1
2588 gr = getattr(cur_gr, 'parent_group', None)
2588 gr = getattr(cur_gr, 'parent_group', None)
2589 cur_gr = cur_gr.parent_group
2589 cur_gr = cur_gr.parent_group
2590 if gr is None:
2590 if gr is None:
2591 break
2591 break
2592 if cnt == parents_recursion_limit:
2592 if cnt == parents_recursion_limit:
2593 # this will prevent accidental infinit loops
2593 # this will prevent accidental infinit loops
2594 log.error('more than %s parents found for group %s, stopping '
2594 log.error('more than %s parents found for group %s, stopping '
2595 'recursive parent fetching', parents_recursion_limit, self)
2595 'recursive parent fetching', parents_recursion_limit, self)
2596 break
2596 break
2597
2597
2598 groups.insert(0, gr)
2598 groups.insert(0, gr)
2599 return groups
2599 return groups
2600
2600
2601 @property
2601 @property
2602 def last_db_change(self):
2602 def last_db_change(self):
2603 return self.updated_on
2603 return self.updated_on
2604
2604
2605 @property
2605 @property
2606 def children(self):
2606 def children(self):
2607 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2607 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2608
2608
2609 @property
2609 @property
2610 def name(self):
2610 def name(self):
2611 return self.group_name.split(RepoGroup.url_sep())[-1]
2611 return self.group_name.split(RepoGroup.url_sep())[-1]
2612
2612
2613 @property
2613 @property
2614 def full_path(self):
2614 def full_path(self):
2615 return self.group_name
2615 return self.group_name
2616
2616
2617 @property
2617 @property
2618 def full_path_splitted(self):
2618 def full_path_splitted(self):
2619 return self.group_name.split(RepoGroup.url_sep())
2619 return self.group_name.split(RepoGroup.url_sep())
2620
2620
2621 @property
2621 @property
2622 def repositories(self):
2622 def repositories(self):
2623 return Repository.query()\
2623 return Repository.query()\
2624 .filter(Repository.group == self)\
2624 .filter(Repository.group == self)\
2625 .order_by(Repository.repo_name)
2625 .order_by(Repository.repo_name)
2626
2626
2627 @property
2627 @property
2628 def repositories_recursive_count(self):
2628 def repositories_recursive_count(self):
2629 cnt = self.repositories.count()
2629 cnt = self.repositories.count()
2630
2630
2631 def children_count(group):
2631 def children_count(group):
2632 cnt = 0
2632 cnt = 0
2633 for child in group.children:
2633 for child in group.children:
2634 cnt += child.repositories.count()
2634 cnt += child.repositories.count()
2635 cnt += children_count(child)
2635 cnt += children_count(child)
2636 return cnt
2636 return cnt
2637
2637
2638 return cnt + children_count(self)
2638 return cnt + children_count(self)
2639
2639
2640 def _recursive_objects(self, include_repos=True):
2640 def _recursive_objects(self, include_repos=True):
2641 all_ = []
2641 all_ = []
2642
2642
2643 def _get_members(root_gr):
2643 def _get_members(root_gr):
2644 if include_repos:
2644 if include_repos:
2645 for r in root_gr.repositories:
2645 for r in root_gr.repositories:
2646 all_.append(r)
2646 all_.append(r)
2647 childs = root_gr.children.all()
2647 childs = root_gr.children.all()
2648 if childs:
2648 if childs:
2649 for gr in childs:
2649 for gr in childs:
2650 all_.append(gr)
2650 all_.append(gr)
2651 _get_members(gr)
2651 _get_members(gr)
2652
2652
2653 _get_members(self)
2653 _get_members(self)
2654 return [self] + all_
2654 return [self] + all_
2655
2655
2656 def recursive_groups_and_repos(self):
2656 def recursive_groups_and_repos(self):
2657 """
2657 """
2658 Recursive return all groups, with repositories in those groups
2658 Recursive return all groups, with repositories in those groups
2659 """
2659 """
2660 return self._recursive_objects()
2660 return self._recursive_objects()
2661
2661
2662 def recursive_groups(self):
2662 def recursive_groups(self):
2663 """
2663 """
2664 Returns all children groups for this group including children of children
2664 Returns all children groups for this group including children of children
2665 """
2665 """
2666 return self._recursive_objects(include_repos=False)
2666 return self._recursive_objects(include_repos=False)
2667
2667
2668 def get_new_name(self, group_name):
2668 def get_new_name(self, group_name):
2669 """
2669 """
2670 returns new full group name based on parent and new name
2670 returns new full group name based on parent and new name
2671
2671
2672 :param group_name:
2672 :param group_name:
2673 """
2673 """
2674 path_prefix = (self.parent_group.full_path_splitted if
2674 path_prefix = (self.parent_group.full_path_splitted if
2675 self.parent_group else [])
2675 self.parent_group else [])
2676 return RepoGroup.url_sep().join(path_prefix + [group_name])
2676 return RepoGroup.url_sep().join(path_prefix + [group_name])
2677
2677
2678 def permissions(self, with_admins=True, with_owner=True,
2678 def permissions(self, with_admins=True, with_owner=True,
2679 expand_from_user_groups=False):
2679 expand_from_user_groups=False):
2680 """
2680 """
2681 Permissions for repository groups
2681 Permissions for repository groups
2682 """
2682 """
2683 _admin_perm = 'group.admin'
2683 _admin_perm = 'group.admin'
2684
2684
2685 owner_row = []
2685 owner_row = []
2686 if with_owner:
2686 if with_owner:
2687 usr = AttributeDict(self.user.get_dict())
2687 usr = AttributeDict(self.user.get_dict())
2688 usr.owner_row = True
2688 usr.owner_row = True
2689 usr.permission = _admin_perm
2689 usr.permission = _admin_perm
2690 owner_row.append(usr)
2690 owner_row.append(usr)
2691
2691
2692 super_admin_ids = []
2692 super_admin_ids = []
2693 super_admin_rows = []
2693 super_admin_rows = []
2694 if with_admins:
2694 if with_admins:
2695 for usr in User.get_all_super_admins():
2695 for usr in User.get_all_super_admins():
2696 super_admin_ids.append(usr.user_id)
2696 super_admin_ids.append(usr.user_id)
2697 # if this admin is also owner, don't double the record
2697 # if this admin is also owner, don't double the record
2698 if usr.user_id == owner_row[0].user_id:
2698 if usr.user_id == owner_row[0].user_id:
2699 owner_row[0].admin_row = True
2699 owner_row[0].admin_row = True
2700 else:
2700 else:
2701 usr = AttributeDict(usr.get_dict())
2701 usr = AttributeDict(usr.get_dict())
2702 usr.admin_row = True
2702 usr.admin_row = True
2703 usr.permission = _admin_perm
2703 usr.permission = _admin_perm
2704 super_admin_rows.append(usr)
2704 super_admin_rows.append(usr)
2705
2705
2706 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2706 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
2707 q = q.options(joinedload(UserRepoGroupToPerm.group),
2707 q = q.options(joinedload(UserRepoGroupToPerm.group),
2708 joinedload(UserRepoGroupToPerm.user),
2708 joinedload(UserRepoGroupToPerm.user),
2709 joinedload(UserRepoGroupToPerm.permission),)
2709 joinedload(UserRepoGroupToPerm.permission),)
2710
2710
2711 # get owners and admins and permissions. We do a trick of re-writing
2711 # get owners and admins and permissions. We do a trick of re-writing
2712 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2712 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2713 # has a global reference and changing one object propagates to all
2713 # has a global reference and changing one object propagates to all
2714 # others. This means if admin is also an owner admin_row that change
2714 # others. This means if admin is also an owner admin_row that change
2715 # would propagate to both objects
2715 # would propagate to both objects
2716 perm_rows = []
2716 perm_rows = []
2717 for _usr in q.all():
2717 for _usr in q.all():
2718 usr = AttributeDict(_usr.user.get_dict())
2718 usr = AttributeDict(_usr.user.get_dict())
2719 # if this user is also owner/admin, mark as duplicate record
2719 # if this user is also owner/admin, mark as duplicate record
2720 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2720 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2721 usr.duplicate_perm = True
2721 usr.duplicate_perm = True
2722 usr.permission = _usr.permission.permission_name
2722 usr.permission = _usr.permission.permission_name
2723 perm_rows.append(usr)
2723 perm_rows.append(usr)
2724
2724
2725 # filter the perm rows by 'default' first and then sort them by
2725 # filter the perm rows by 'default' first and then sort them by
2726 # admin,write,read,none permissions sorted again alphabetically in
2726 # admin,write,read,none permissions sorted again alphabetically in
2727 # each group
2727 # each group
2728 perm_rows = sorted(perm_rows, key=display_user_sort)
2728 perm_rows = sorted(perm_rows, key=display_user_sort)
2729
2729
2730 user_groups_rows = []
2730 user_groups_rows = []
2731 if expand_from_user_groups:
2731 if expand_from_user_groups:
2732 for ug in self.permission_user_groups(with_members=True):
2732 for ug in self.permission_user_groups(with_members=True):
2733 for user_data in ug.members:
2733 for user_data in ug.members:
2734 user_groups_rows.append(user_data)
2734 user_groups_rows.append(user_data)
2735
2735
2736 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2736 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2737
2737
2738 def permission_user_groups(self, with_members=False):
2738 def permission_user_groups(self, with_members=False):
2739 q = UserGroupRepoGroupToPerm.query()\
2739 q = UserGroupRepoGroupToPerm.query()\
2740 .filter(UserGroupRepoGroupToPerm.group == self)
2740 .filter(UserGroupRepoGroupToPerm.group == self)
2741 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2741 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
2742 joinedload(UserGroupRepoGroupToPerm.users_group),
2742 joinedload(UserGroupRepoGroupToPerm.users_group),
2743 joinedload(UserGroupRepoGroupToPerm.permission),)
2743 joinedload(UserGroupRepoGroupToPerm.permission),)
2744
2744
2745 perm_rows = []
2745 perm_rows = []
2746 for _user_group in q.all():
2746 for _user_group in q.all():
2747 entry = AttributeDict(_user_group.users_group.get_dict())
2747 entry = AttributeDict(_user_group.users_group.get_dict())
2748 entry.permission = _user_group.permission.permission_name
2748 entry.permission = _user_group.permission.permission_name
2749 if with_members:
2749 if with_members:
2750 entry.members = [x.user.get_dict()
2750 entry.members = [x.user.get_dict()
2751 for x in _user_group.users_group.members]
2751 for x in _user_group.users_group.members]
2752 perm_rows.append(entry)
2752 perm_rows.append(entry)
2753
2753
2754 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2754 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2755 return perm_rows
2755 return perm_rows
2756
2756
2757 def get_api_data(self):
2757 def get_api_data(self):
2758 """
2758 """
2759 Common function for generating api data
2759 Common function for generating api data
2760
2760
2761 """
2761 """
2762 group = self
2762 group = self
2763 data = {
2763 data = {
2764 'group_id': group.group_id,
2764 'group_id': group.group_id,
2765 'group_name': group.group_name,
2765 'group_name': group.group_name,
2766 'group_description': group.description_safe,
2766 'group_description': group.description_safe,
2767 'parent_group': group.parent_group.group_name if group.parent_group else None,
2767 'parent_group': group.parent_group.group_name if group.parent_group else None,
2768 'repositories': [x.repo_name for x in group.repositories],
2768 'repositories': [x.repo_name for x in group.repositories],
2769 'owner': group.user.username,
2769 'owner': group.user.username,
2770 }
2770 }
2771 return data
2771 return data
2772
2772
2773
2773
2774 class Permission(Base, BaseModel):
2774 class Permission(Base, BaseModel):
2775 __tablename__ = 'permissions'
2775 __tablename__ = 'permissions'
2776 __table_args__ = (
2776 __table_args__ = (
2777 Index('p_perm_name_idx', 'permission_name'),
2777 Index('p_perm_name_idx', 'permission_name'),
2778 base_table_args,
2778 base_table_args,
2779 )
2779 )
2780
2780
2781 PERMS = [
2781 PERMS = [
2782 ('hg.admin', _('RhodeCode Super Administrator')),
2782 ('hg.admin', _('RhodeCode Super Administrator')),
2783
2783
2784 ('repository.none', _('Repository no access')),
2784 ('repository.none', _('Repository no access')),
2785 ('repository.read', _('Repository read access')),
2785 ('repository.read', _('Repository read access')),
2786 ('repository.write', _('Repository write access')),
2786 ('repository.write', _('Repository write access')),
2787 ('repository.admin', _('Repository admin access')),
2787 ('repository.admin', _('Repository admin access')),
2788
2788
2789 ('group.none', _('Repository group no access')),
2789 ('group.none', _('Repository group no access')),
2790 ('group.read', _('Repository group read access')),
2790 ('group.read', _('Repository group read access')),
2791 ('group.write', _('Repository group write access')),
2791 ('group.write', _('Repository group write access')),
2792 ('group.admin', _('Repository group admin access')),
2792 ('group.admin', _('Repository group admin access')),
2793
2793
2794 ('usergroup.none', _('User group no access')),
2794 ('usergroup.none', _('User group no access')),
2795 ('usergroup.read', _('User group read access')),
2795 ('usergroup.read', _('User group read access')),
2796 ('usergroup.write', _('User group write access')),
2796 ('usergroup.write', _('User group write access')),
2797 ('usergroup.admin', _('User group admin access')),
2797 ('usergroup.admin', _('User group admin access')),
2798
2798
2799 ('branch.none', _('Branch no permissions')),
2799 ('branch.none', _('Branch no permissions')),
2800 ('branch.merge', _('Branch access by web merge')),
2800 ('branch.merge', _('Branch access by web merge')),
2801 ('branch.push', _('Branch access by push')),
2801 ('branch.push', _('Branch access by push')),
2802 ('branch.push_force', _('Branch access by push with force')),
2802 ('branch.push_force', _('Branch access by push with force')),
2803
2803
2804 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2804 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
2805 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2805 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
2806
2806
2807 ('hg.usergroup.create.false', _('User Group creation disabled')),
2807 ('hg.usergroup.create.false', _('User Group creation disabled')),
2808 ('hg.usergroup.create.true', _('User Group creation enabled')),
2808 ('hg.usergroup.create.true', _('User Group creation enabled')),
2809
2809
2810 ('hg.create.none', _('Repository creation disabled')),
2810 ('hg.create.none', _('Repository creation disabled')),
2811 ('hg.create.repository', _('Repository creation enabled')),
2811 ('hg.create.repository', _('Repository creation enabled')),
2812 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2812 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
2813 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2813 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
2814
2814
2815 ('hg.fork.none', _('Repository forking disabled')),
2815 ('hg.fork.none', _('Repository forking disabled')),
2816 ('hg.fork.repository', _('Repository forking enabled')),
2816 ('hg.fork.repository', _('Repository forking enabled')),
2817
2817
2818 ('hg.register.none', _('Registration disabled')),
2818 ('hg.register.none', _('Registration disabled')),
2819 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2819 ('hg.register.manual_activate', _('User Registration with manual account activation')),
2820 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2820 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
2821
2821
2822 ('hg.password_reset.enabled', _('Password reset enabled')),
2822 ('hg.password_reset.enabled', _('Password reset enabled')),
2823 ('hg.password_reset.hidden', _('Password reset hidden')),
2823 ('hg.password_reset.hidden', _('Password reset hidden')),
2824 ('hg.password_reset.disabled', _('Password reset disabled')),
2824 ('hg.password_reset.disabled', _('Password reset disabled')),
2825
2825
2826 ('hg.extern_activate.manual', _('Manual activation of external account')),
2826 ('hg.extern_activate.manual', _('Manual activation of external account')),
2827 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2827 ('hg.extern_activate.auto', _('Automatic activation of external account')),
2828
2828
2829 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2829 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
2830 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2830 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
2831 ]
2831 ]
2832
2832
2833 # definition of system default permissions for DEFAULT user, created on
2833 # definition of system default permissions for DEFAULT user, created on
2834 # system setup
2834 # system setup
2835 DEFAULT_USER_PERMISSIONS = [
2835 DEFAULT_USER_PERMISSIONS = [
2836 # object perms
2836 # object perms
2837 'repository.read',
2837 'repository.read',
2838 'group.read',
2838 'group.read',
2839 'usergroup.read',
2839 'usergroup.read',
2840 # branch, for backward compat we need same value as before so forced pushed
2840 # branch, for backward compat we need same value as before so forced pushed
2841 'branch.push_force',
2841 'branch.push_force',
2842 # global
2842 # global
2843 'hg.create.repository',
2843 'hg.create.repository',
2844 'hg.repogroup.create.false',
2844 'hg.repogroup.create.false',
2845 'hg.usergroup.create.false',
2845 'hg.usergroup.create.false',
2846 'hg.create.write_on_repogroup.true',
2846 'hg.create.write_on_repogroup.true',
2847 'hg.fork.repository',
2847 'hg.fork.repository',
2848 'hg.register.manual_activate',
2848 'hg.register.manual_activate',
2849 'hg.password_reset.enabled',
2849 'hg.password_reset.enabled',
2850 'hg.extern_activate.auto',
2850 'hg.extern_activate.auto',
2851 'hg.inherit_default_perms.true',
2851 'hg.inherit_default_perms.true',
2852 ]
2852 ]
2853
2853
2854 # defines which permissions are more important higher the more important
2854 # defines which permissions are more important higher the more important
2855 # Weight defines which permissions are more important.
2855 # Weight defines which permissions are more important.
2856 # The higher number the more important.
2856 # The higher number the more important.
2857 PERM_WEIGHTS = {
2857 PERM_WEIGHTS = {
2858 'repository.none': 0,
2858 'repository.none': 0,
2859 'repository.read': 1,
2859 'repository.read': 1,
2860 'repository.write': 3,
2860 'repository.write': 3,
2861 'repository.admin': 4,
2861 'repository.admin': 4,
2862
2862
2863 'group.none': 0,
2863 'group.none': 0,
2864 'group.read': 1,
2864 'group.read': 1,
2865 'group.write': 3,
2865 'group.write': 3,
2866 'group.admin': 4,
2866 'group.admin': 4,
2867
2867
2868 'usergroup.none': 0,
2868 'usergroup.none': 0,
2869 'usergroup.read': 1,
2869 'usergroup.read': 1,
2870 'usergroup.write': 3,
2870 'usergroup.write': 3,
2871 'usergroup.admin': 4,
2871 'usergroup.admin': 4,
2872
2872
2873 'branch.none': 0,
2873 'branch.none': 0,
2874 'branch.merge': 1,
2874 'branch.merge': 1,
2875 'branch.push': 3,
2875 'branch.push': 3,
2876 'branch.push_force': 4,
2876 'branch.push_force': 4,
2877
2877
2878 'hg.repogroup.create.false': 0,
2878 'hg.repogroup.create.false': 0,
2879 'hg.repogroup.create.true': 1,
2879 'hg.repogroup.create.true': 1,
2880
2880
2881 'hg.usergroup.create.false': 0,
2881 'hg.usergroup.create.false': 0,
2882 'hg.usergroup.create.true': 1,
2882 'hg.usergroup.create.true': 1,
2883
2883
2884 'hg.fork.none': 0,
2884 'hg.fork.none': 0,
2885 'hg.fork.repository': 1,
2885 'hg.fork.repository': 1,
2886 'hg.create.none': 0,
2886 'hg.create.none': 0,
2887 'hg.create.repository': 1
2887 'hg.create.repository': 1
2888 }
2888 }
2889
2889
2890 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2890 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2891 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2891 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
2892 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2892 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
2893
2893
2894 def __unicode__(self):
2894 def __unicode__(self):
2895 return u"<%s('%s:%s')>" % (
2895 return u"<%s('%s:%s')>" % (
2896 self.__class__.__name__, self.permission_id, self.permission_name
2896 self.__class__.__name__, self.permission_id, self.permission_name
2897 )
2897 )
2898
2898
2899 @classmethod
2899 @classmethod
2900 def get_by_key(cls, key):
2900 def get_by_key(cls, key):
2901 return cls.query().filter(cls.permission_name == key).scalar()
2901 return cls.query().filter(cls.permission_name == key).scalar()
2902
2902
2903 @classmethod
2903 @classmethod
2904 def get_default_repo_perms(cls, user_id, repo_id=None):
2904 def get_default_repo_perms(cls, user_id, repo_id=None):
2905 q = Session().query(UserRepoToPerm, Repository, Permission)\
2905 q = Session().query(UserRepoToPerm, Repository, Permission)\
2906 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2906 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
2907 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2907 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
2908 .filter(UserRepoToPerm.user_id == user_id)
2908 .filter(UserRepoToPerm.user_id == user_id)
2909 if repo_id:
2909 if repo_id:
2910 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2910 q = q.filter(UserRepoToPerm.repository_id == repo_id)
2911 return q.all()
2911 return q.all()
2912
2912
2913 @classmethod
2913 @classmethod
2914 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2914 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
2915 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2915 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
2916 .join(
2916 .join(
2917 Permission,
2917 Permission,
2918 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2918 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
2919 .join(
2919 .join(
2920 UserRepoToPerm,
2920 UserRepoToPerm,
2921 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2921 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
2922 .filter(UserRepoToPerm.user_id == user_id)
2922 .filter(UserRepoToPerm.user_id == user_id)
2923
2923
2924 if repo_id:
2924 if repo_id:
2925 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2925 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
2926 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2926 return q.order_by(UserToRepoBranchPermission.rule_order).all()
2927
2927
2928 @classmethod
2928 @classmethod
2929 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2929 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
2930 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2930 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
2931 .join(
2931 .join(
2932 Permission,
2932 Permission,
2933 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2933 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
2934 .join(
2934 .join(
2935 Repository,
2935 Repository,
2936 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2936 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
2937 .join(
2937 .join(
2938 UserGroup,
2938 UserGroup,
2939 UserGroupRepoToPerm.users_group_id ==
2939 UserGroupRepoToPerm.users_group_id ==
2940 UserGroup.users_group_id)\
2940 UserGroup.users_group_id)\
2941 .join(
2941 .join(
2942 UserGroupMember,
2942 UserGroupMember,
2943 UserGroupRepoToPerm.users_group_id ==
2943 UserGroupRepoToPerm.users_group_id ==
2944 UserGroupMember.users_group_id)\
2944 UserGroupMember.users_group_id)\
2945 .filter(
2945 .filter(
2946 UserGroupMember.user_id == user_id,
2946 UserGroupMember.user_id == user_id,
2947 UserGroup.users_group_active == true())
2947 UserGroup.users_group_active == true())
2948 if repo_id:
2948 if repo_id:
2949 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2949 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
2950 return q.all()
2950 return q.all()
2951
2951
2952 @classmethod
2952 @classmethod
2953 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2953 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
2954 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2954 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
2955 .join(
2955 .join(
2956 Permission,
2956 Permission,
2957 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2957 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
2958 .join(
2958 .join(
2959 UserGroupRepoToPerm,
2959 UserGroupRepoToPerm,
2960 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2960 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
2961 .join(
2961 .join(
2962 UserGroup,
2962 UserGroup,
2963 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2963 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
2964 .join(
2964 .join(
2965 UserGroupMember,
2965 UserGroupMember,
2966 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2966 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
2967 .filter(
2967 .filter(
2968 UserGroupMember.user_id == user_id,
2968 UserGroupMember.user_id == user_id,
2969 UserGroup.users_group_active == true())
2969 UserGroup.users_group_active == true())
2970
2970
2971 if repo_id:
2971 if repo_id:
2972 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
2972 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
2973 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
2973 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
2974
2974
2975 @classmethod
2975 @classmethod
2976 def get_default_group_perms(cls, user_id, repo_group_id=None):
2976 def get_default_group_perms(cls, user_id, repo_group_id=None):
2977 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2977 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
2978 .join(
2978 .join(
2979 Permission,
2979 Permission,
2980 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
2980 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
2981 .join(
2981 .join(
2982 RepoGroup,
2982 RepoGroup,
2983 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
2983 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
2984 .filter(UserRepoGroupToPerm.user_id == user_id)
2984 .filter(UserRepoGroupToPerm.user_id == user_id)
2985 if repo_group_id:
2985 if repo_group_id:
2986 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2986 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
2987 return q.all()
2987 return q.all()
2988
2988
2989 @classmethod
2989 @classmethod
2990 def get_default_group_perms_from_user_group(
2990 def get_default_group_perms_from_user_group(
2991 cls, user_id, repo_group_id=None):
2991 cls, user_id, repo_group_id=None):
2992 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2992 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
2993 .join(
2993 .join(
2994 Permission,
2994 Permission,
2995 UserGroupRepoGroupToPerm.permission_id ==
2995 UserGroupRepoGroupToPerm.permission_id ==
2996 Permission.permission_id)\
2996 Permission.permission_id)\
2997 .join(
2997 .join(
2998 RepoGroup,
2998 RepoGroup,
2999 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
2999 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3000 .join(
3000 .join(
3001 UserGroup,
3001 UserGroup,
3002 UserGroupRepoGroupToPerm.users_group_id ==
3002 UserGroupRepoGroupToPerm.users_group_id ==
3003 UserGroup.users_group_id)\
3003 UserGroup.users_group_id)\
3004 .join(
3004 .join(
3005 UserGroupMember,
3005 UserGroupMember,
3006 UserGroupRepoGroupToPerm.users_group_id ==
3006 UserGroupRepoGroupToPerm.users_group_id ==
3007 UserGroupMember.users_group_id)\
3007 UserGroupMember.users_group_id)\
3008 .filter(
3008 .filter(
3009 UserGroupMember.user_id == user_id,
3009 UserGroupMember.user_id == user_id,
3010 UserGroup.users_group_active == true())
3010 UserGroup.users_group_active == true())
3011 if repo_group_id:
3011 if repo_group_id:
3012 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3012 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3013 return q.all()
3013 return q.all()
3014
3014
3015 @classmethod
3015 @classmethod
3016 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3016 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3017 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3017 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3018 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3018 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3019 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3019 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3020 .filter(UserUserGroupToPerm.user_id == user_id)
3020 .filter(UserUserGroupToPerm.user_id == user_id)
3021 if user_group_id:
3021 if user_group_id:
3022 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3022 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3023 return q.all()
3023 return q.all()
3024
3024
3025 @classmethod
3025 @classmethod
3026 def get_default_user_group_perms_from_user_group(
3026 def get_default_user_group_perms_from_user_group(
3027 cls, user_id, user_group_id=None):
3027 cls, user_id, user_group_id=None):
3028 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3028 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3029 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3029 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3030 .join(
3030 .join(
3031 Permission,
3031 Permission,
3032 UserGroupUserGroupToPerm.permission_id ==
3032 UserGroupUserGroupToPerm.permission_id ==
3033 Permission.permission_id)\
3033 Permission.permission_id)\
3034 .join(
3034 .join(
3035 TargetUserGroup,
3035 TargetUserGroup,
3036 UserGroupUserGroupToPerm.target_user_group_id ==
3036 UserGroupUserGroupToPerm.target_user_group_id ==
3037 TargetUserGroup.users_group_id)\
3037 TargetUserGroup.users_group_id)\
3038 .join(
3038 .join(
3039 UserGroup,
3039 UserGroup,
3040 UserGroupUserGroupToPerm.user_group_id ==
3040 UserGroupUserGroupToPerm.user_group_id ==
3041 UserGroup.users_group_id)\
3041 UserGroup.users_group_id)\
3042 .join(
3042 .join(
3043 UserGroupMember,
3043 UserGroupMember,
3044 UserGroupUserGroupToPerm.user_group_id ==
3044 UserGroupUserGroupToPerm.user_group_id ==
3045 UserGroupMember.users_group_id)\
3045 UserGroupMember.users_group_id)\
3046 .filter(
3046 .filter(
3047 UserGroupMember.user_id == user_id,
3047 UserGroupMember.user_id == user_id,
3048 UserGroup.users_group_active == true())
3048 UserGroup.users_group_active == true())
3049 if user_group_id:
3049 if user_group_id:
3050 q = q.filter(
3050 q = q.filter(
3051 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3051 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3052
3052
3053 return q.all()
3053 return q.all()
3054
3054
3055
3055
3056 class UserRepoToPerm(Base, BaseModel):
3056 class UserRepoToPerm(Base, BaseModel):
3057 __tablename__ = 'repo_to_perm'
3057 __tablename__ = 'repo_to_perm'
3058 __table_args__ = (
3058 __table_args__ = (
3059 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3059 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3060 base_table_args
3060 base_table_args
3061 )
3061 )
3062
3062
3063 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3063 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3064 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3065 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3065 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3066 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3066 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3067
3067
3068 user = relationship('User')
3068 user = relationship('User')
3069 repository = relationship('Repository')
3069 repository = relationship('Repository')
3070 permission = relationship('Permission')
3070 permission = relationship('Permission')
3071
3071
3072 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3072 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete, delete-orphan", lazy='joined')
3073
3073
3074 @classmethod
3074 @classmethod
3075 def create(cls, user, repository, permission):
3075 def create(cls, user, repository, permission):
3076 n = cls()
3076 n = cls()
3077 n.user = user
3077 n.user = user
3078 n.repository = repository
3078 n.repository = repository
3079 n.permission = permission
3079 n.permission = permission
3080 Session().add(n)
3080 Session().add(n)
3081 return n
3081 return n
3082
3082
3083 def __unicode__(self):
3083 def __unicode__(self):
3084 return u'<%s => %s >' % (self.user, self.repository)
3084 return u'<%s => %s >' % (self.user, self.repository)
3085
3085
3086
3086
3087 class UserUserGroupToPerm(Base, BaseModel):
3087 class UserUserGroupToPerm(Base, BaseModel):
3088 __tablename__ = 'user_user_group_to_perm'
3088 __tablename__ = 'user_user_group_to_perm'
3089 __table_args__ = (
3089 __table_args__ = (
3090 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3090 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3091 base_table_args
3091 base_table_args
3092 )
3092 )
3093
3093
3094 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3094 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3095 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3096 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3096 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3097 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3097 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3098
3098
3099 user = relationship('User')
3099 user = relationship('User')
3100 user_group = relationship('UserGroup')
3100 user_group = relationship('UserGroup')
3101 permission = relationship('Permission')
3101 permission = relationship('Permission')
3102
3102
3103 @classmethod
3103 @classmethod
3104 def create(cls, user, user_group, permission):
3104 def create(cls, user, user_group, permission):
3105 n = cls()
3105 n = cls()
3106 n.user = user
3106 n.user = user
3107 n.user_group = user_group
3107 n.user_group = user_group
3108 n.permission = permission
3108 n.permission = permission
3109 Session().add(n)
3109 Session().add(n)
3110 return n
3110 return n
3111
3111
3112 def __unicode__(self):
3112 def __unicode__(self):
3113 return u'<%s => %s >' % (self.user, self.user_group)
3113 return u'<%s => %s >' % (self.user, self.user_group)
3114
3114
3115
3115
3116 class UserToPerm(Base, BaseModel):
3116 class UserToPerm(Base, BaseModel):
3117 __tablename__ = 'user_to_perm'
3117 __tablename__ = 'user_to_perm'
3118 __table_args__ = (
3118 __table_args__ = (
3119 UniqueConstraint('user_id', 'permission_id'),
3119 UniqueConstraint('user_id', 'permission_id'),
3120 base_table_args
3120 base_table_args
3121 )
3121 )
3122
3122
3123 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3123 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3124 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3125 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3126
3126
3127 user = relationship('User')
3127 user = relationship('User')
3128 permission = relationship('Permission', lazy='joined')
3128 permission = relationship('Permission', lazy='joined')
3129
3129
3130 def __unicode__(self):
3130 def __unicode__(self):
3131 return u'<%s => %s >' % (self.user, self.permission)
3131 return u'<%s => %s >' % (self.user, self.permission)
3132
3132
3133
3133
3134 class UserGroupRepoToPerm(Base, BaseModel):
3134 class UserGroupRepoToPerm(Base, BaseModel):
3135 __tablename__ = 'users_group_repo_to_perm'
3135 __tablename__ = 'users_group_repo_to_perm'
3136 __table_args__ = (
3136 __table_args__ = (
3137 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3137 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3138 base_table_args
3138 base_table_args
3139 )
3139 )
3140
3140
3141 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3141 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3142 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3142 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3143 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3143 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3144 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3144 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3145
3145
3146 users_group = relationship('UserGroup')
3146 users_group = relationship('UserGroup')
3147 permission = relationship('Permission')
3147 permission = relationship('Permission')
3148 repository = relationship('Repository')
3148 repository = relationship('Repository')
3149 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3149 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3150
3150
3151 @classmethod
3151 @classmethod
3152 def create(cls, users_group, repository, permission):
3152 def create(cls, users_group, repository, permission):
3153 n = cls()
3153 n = cls()
3154 n.users_group = users_group
3154 n.users_group = users_group
3155 n.repository = repository
3155 n.repository = repository
3156 n.permission = permission
3156 n.permission = permission
3157 Session().add(n)
3157 Session().add(n)
3158 return n
3158 return n
3159
3159
3160 def __unicode__(self):
3160 def __unicode__(self):
3161 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3161 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3162
3162
3163
3163
3164 class UserGroupUserGroupToPerm(Base, BaseModel):
3164 class UserGroupUserGroupToPerm(Base, BaseModel):
3165 __tablename__ = 'user_group_user_group_to_perm'
3165 __tablename__ = 'user_group_user_group_to_perm'
3166 __table_args__ = (
3166 __table_args__ = (
3167 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3167 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3168 CheckConstraint('target_user_group_id != user_group_id'),
3168 CheckConstraint('target_user_group_id != user_group_id'),
3169 base_table_args
3169 base_table_args
3170 )
3170 )
3171
3171
3172 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3172 user_group_user_group_to_perm_id = Column("user_group_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3173 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3173 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3174 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3174 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3175 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3175 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3176
3176
3177 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3177 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3178 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3178 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3179 permission = relationship('Permission')
3179 permission = relationship('Permission')
3180
3180
3181 @classmethod
3181 @classmethod
3182 def create(cls, target_user_group, user_group, permission):
3182 def create(cls, target_user_group, user_group, permission):
3183 n = cls()
3183 n = cls()
3184 n.target_user_group = target_user_group
3184 n.target_user_group = target_user_group
3185 n.user_group = user_group
3185 n.user_group = user_group
3186 n.permission = permission
3186 n.permission = permission
3187 Session().add(n)
3187 Session().add(n)
3188 return n
3188 return n
3189
3189
3190 def __unicode__(self):
3190 def __unicode__(self):
3191 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3191 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3192
3192
3193
3193
3194 class UserGroupToPerm(Base, BaseModel):
3194 class UserGroupToPerm(Base, BaseModel):
3195 __tablename__ = 'users_group_to_perm'
3195 __tablename__ = 'users_group_to_perm'
3196 __table_args__ = (
3196 __table_args__ = (
3197 UniqueConstraint('users_group_id', 'permission_id',),
3197 UniqueConstraint('users_group_id', 'permission_id',),
3198 base_table_args
3198 base_table_args
3199 )
3199 )
3200
3200
3201 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3201 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3202 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3202 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3203 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3203 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3204
3204
3205 users_group = relationship('UserGroup')
3205 users_group = relationship('UserGroup')
3206 permission = relationship('Permission')
3206 permission = relationship('Permission')
3207
3207
3208
3208
3209 class UserRepoGroupToPerm(Base, BaseModel):
3209 class UserRepoGroupToPerm(Base, BaseModel):
3210 __tablename__ = 'user_repo_group_to_perm'
3210 __tablename__ = 'user_repo_group_to_perm'
3211 __table_args__ = (
3211 __table_args__ = (
3212 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3212 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3213 base_table_args
3213 base_table_args
3214 )
3214 )
3215
3215
3216 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3216 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3217 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3218 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3218 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3219 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3219 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3220
3220
3221 user = relationship('User')
3221 user = relationship('User')
3222 group = relationship('RepoGroup')
3222 group = relationship('RepoGroup')
3223 permission = relationship('Permission')
3223 permission = relationship('Permission')
3224
3224
3225 @classmethod
3225 @classmethod
3226 def create(cls, user, repository_group, permission):
3226 def create(cls, user, repository_group, permission):
3227 n = cls()
3227 n = cls()
3228 n.user = user
3228 n.user = user
3229 n.group = repository_group
3229 n.group = repository_group
3230 n.permission = permission
3230 n.permission = permission
3231 Session().add(n)
3231 Session().add(n)
3232 return n
3232 return n
3233
3233
3234
3234
3235 class UserGroupRepoGroupToPerm(Base, BaseModel):
3235 class UserGroupRepoGroupToPerm(Base, BaseModel):
3236 __tablename__ = 'users_group_repo_group_to_perm'
3236 __tablename__ = 'users_group_repo_group_to_perm'
3237 __table_args__ = (
3237 __table_args__ = (
3238 UniqueConstraint('users_group_id', 'group_id'),
3238 UniqueConstraint('users_group_id', 'group_id'),
3239 base_table_args
3239 base_table_args
3240 )
3240 )
3241
3241
3242 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)
3242 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)
3243 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3243 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3244 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3244 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3245 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3246
3246
3247 users_group = relationship('UserGroup')
3247 users_group = relationship('UserGroup')
3248 permission = relationship('Permission')
3248 permission = relationship('Permission')
3249 group = relationship('RepoGroup')
3249 group = relationship('RepoGroup')
3250
3250
3251 @classmethod
3251 @classmethod
3252 def create(cls, user_group, repository_group, permission):
3252 def create(cls, user_group, repository_group, permission):
3253 n = cls()
3253 n = cls()
3254 n.users_group = user_group
3254 n.users_group = user_group
3255 n.group = repository_group
3255 n.group = repository_group
3256 n.permission = permission
3256 n.permission = permission
3257 Session().add(n)
3257 Session().add(n)
3258 return n
3258 return n
3259
3259
3260 def __unicode__(self):
3260 def __unicode__(self):
3261 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3261 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3262
3262
3263
3263
3264 class Statistics(Base, BaseModel):
3264 class Statistics(Base, BaseModel):
3265 __tablename__ = 'statistics'
3265 __tablename__ = 'statistics'
3266 __table_args__ = (
3266 __table_args__ = (
3267 base_table_args
3267 base_table_args
3268 )
3268 )
3269
3269
3270 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3270 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3271 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3271 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3272 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3272 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3273 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3273 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3274 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3274 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3275 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3275 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3276
3276
3277 repository = relationship('Repository', single_parent=True)
3277 repository = relationship('Repository', single_parent=True)
3278
3278
3279
3279
3280 class UserFollowing(Base, BaseModel):
3280 class UserFollowing(Base, BaseModel):
3281 __tablename__ = 'user_followings'
3281 __tablename__ = 'user_followings'
3282 __table_args__ = (
3282 __table_args__ = (
3283 UniqueConstraint('user_id', 'follows_repository_id'),
3283 UniqueConstraint('user_id', 'follows_repository_id'),
3284 UniqueConstraint('user_id', 'follows_user_id'),
3284 UniqueConstraint('user_id', 'follows_user_id'),
3285 base_table_args
3285 base_table_args
3286 )
3286 )
3287
3287
3288 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3288 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3289 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3289 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3290 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3290 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3291 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3291 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3292 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3292 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3293
3293
3294 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3294 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3295
3295
3296 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3296 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3297 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3297 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3298
3298
3299 @classmethod
3299 @classmethod
3300 def get_repo_followers(cls, repo_id):
3300 def get_repo_followers(cls, repo_id):
3301 return cls.query().filter(cls.follows_repo_id == repo_id)
3301 return cls.query().filter(cls.follows_repo_id == repo_id)
3302
3302
3303
3303
3304 class CacheKey(Base, BaseModel):
3304 class CacheKey(Base, BaseModel):
3305 __tablename__ = 'cache_invalidation'
3305 __tablename__ = 'cache_invalidation'
3306 __table_args__ = (
3306 __table_args__ = (
3307 UniqueConstraint('cache_key'),
3307 UniqueConstraint('cache_key'),
3308 Index('key_idx', 'cache_key'),
3308 Index('key_idx', 'cache_key'),
3309 base_table_args,
3309 base_table_args,
3310 )
3310 )
3311
3311
3312 CACHE_TYPE_FEED = 'FEED'
3312 CACHE_TYPE_FEED = 'FEED'
3313 CACHE_TYPE_README = 'README'
3313 CACHE_TYPE_README = 'README'
3314 # namespaces used to register process/thread aware caches
3314 # namespaces used to register process/thread aware caches
3315 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3315 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3316 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3316 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3317
3317
3318 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3318 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3319 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3319 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3320 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3320 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3321 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3321 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3322
3322
3323 def __init__(self, cache_key, cache_args=''):
3323 def __init__(self, cache_key, cache_args=''):
3324 self.cache_key = cache_key
3324 self.cache_key = cache_key
3325 self.cache_args = cache_args
3325 self.cache_args = cache_args
3326 self.cache_active = False
3326 self.cache_active = False
3327
3327
3328 def __unicode__(self):
3328 def __unicode__(self):
3329 return u"<%s('%s:%s[%s]')>" % (
3329 return u"<%s('%s:%s[%s]')>" % (
3330 self.__class__.__name__,
3330 self.__class__.__name__,
3331 self.cache_id, self.cache_key, self.cache_active)
3331 self.cache_id, self.cache_key, self.cache_active)
3332
3332
3333 def _cache_key_partition(self):
3333 def _cache_key_partition(self):
3334 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3334 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3335 return prefix, repo_name, suffix
3335 return prefix, repo_name, suffix
3336
3336
3337 def get_prefix(self):
3337 def get_prefix(self):
3338 """
3338 """
3339 Try to extract prefix from existing cache key. The key could consist
3339 Try to extract prefix from existing cache key. The key could consist
3340 of prefix, repo_name, suffix
3340 of prefix, repo_name, suffix
3341 """
3341 """
3342 # this returns prefix, repo_name, suffix
3342 # this returns prefix, repo_name, suffix
3343 return self._cache_key_partition()[0]
3343 return self._cache_key_partition()[0]
3344
3344
3345 def get_suffix(self):
3345 def get_suffix(self):
3346 """
3346 """
3347 get suffix that might have been used in _get_cache_key to
3347 get suffix that might have been used in _get_cache_key to
3348 generate self.cache_key. Only used for informational purposes
3348 generate self.cache_key. Only used for informational purposes
3349 in repo_edit.mako.
3349 in repo_edit.mako.
3350 """
3350 """
3351 # prefix, repo_name, suffix
3351 # prefix, repo_name, suffix
3352 return self._cache_key_partition()[2]
3352 return self._cache_key_partition()[2]
3353
3353
3354 @classmethod
3354 @classmethod
3355 def delete_all_cache(cls):
3355 def delete_all_cache(cls):
3356 """
3356 """
3357 Delete all cache keys from database.
3357 Delete all cache keys from database.
3358 Should only be run when all instances are down and all entries
3358 Should only be run when all instances are down and all entries
3359 thus stale.
3359 thus stale.
3360 """
3360 """
3361 cls.query().delete()
3361 cls.query().delete()
3362 Session().commit()
3362 Session().commit()
3363
3363
3364 @classmethod
3364 @classmethod
3365 def set_invalidate(cls, cache_uid, delete=False):
3365 def set_invalidate(cls, cache_uid, delete=False):
3366 """
3366 """
3367 Mark all caches of a repo as invalid in the database.
3367 Mark all caches of a repo as invalid in the database.
3368 """
3368 """
3369
3369
3370 try:
3370 try:
3371 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3371 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3372 if delete:
3372 if delete:
3373 qry.delete()
3373 qry.delete()
3374 log.debug('cache objects deleted for cache args %s',
3374 log.debug('cache objects deleted for cache args %s',
3375 safe_str(cache_uid))
3375 safe_str(cache_uid))
3376 else:
3376 else:
3377 qry.update({"cache_active": False})
3377 qry.update({"cache_active": False})
3378 log.debug('cache objects marked as invalid for cache args %s',
3378 log.debug('cache objects marked as invalid for cache args %s',
3379 safe_str(cache_uid))
3379 safe_str(cache_uid))
3380
3380
3381 Session().commit()
3381 Session().commit()
3382 except Exception:
3382 except Exception:
3383 log.exception(
3383 log.exception(
3384 'Cache key invalidation failed for cache args %s',
3384 'Cache key invalidation failed for cache args %s',
3385 safe_str(cache_uid))
3385 safe_str(cache_uid))
3386 Session().rollback()
3386 Session().rollback()
3387
3387
3388 @classmethod
3388 @classmethod
3389 def get_active_cache(cls, cache_key):
3389 def get_active_cache(cls, cache_key):
3390 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3390 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3391 if inv_obj:
3391 if inv_obj:
3392 return inv_obj
3392 return inv_obj
3393 return None
3393 return None
3394
3394
3395
3395
3396 class ChangesetComment(Base, BaseModel):
3396 class ChangesetComment(Base, BaseModel):
3397 __tablename__ = 'changeset_comments'
3397 __tablename__ = 'changeset_comments'
3398 __table_args__ = (
3398 __table_args__ = (
3399 Index('cc_revision_idx', 'revision'),
3399 Index('cc_revision_idx', 'revision'),
3400 base_table_args,
3400 base_table_args,
3401 )
3401 )
3402
3402
3403 COMMENT_OUTDATED = u'comment_outdated'
3403 COMMENT_OUTDATED = u'comment_outdated'
3404 COMMENT_TYPE_NOTE = u'note'
3404 COMMENT_TYPE_NOTE = u'note'
3405 COMMENT_TYPE_TODO = u'todo'
3405 COMMENT_TYPE_TODO = u'todo'
3406 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3406 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3407
3407
3408 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3408 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3409 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3409 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3410 revision = Column('revision', String(40), nullable=True)
3410 revision = Column('revision', String(40), nullable=True)
3411 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3411 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3412 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3412 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3413 line_no = Column('line_no', Unicode(10), nullable=True)
3413 line_no = Column('line_no', Unicode(10), nullable=True)
3414 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3414 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3415 f_path = Column('f_path', Unicode(1000), nullable=True)
3415 f_path = Column('f_path', Unicode(1000), nullable=True)
3416 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3416 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3417 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3417 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3418 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3418 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3419 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3419 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3420 renderer = Column('renderer', Unicode(64), nullable=True)
3420 renderer = Column('renderer', Unicode(64), nullable=True)
3421 display_state = Column('display_state', Unicode(128), nullable=True)
3421 display_state = Column('display_state', Unicode(128), nullable=True)
3422
3422
3423 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3423 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3424 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3424 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3425
3425
3426 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3426 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3427 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3427 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3428
3428
3429 author = relationship('User', lazy='joined')
3429 author = relationship('User', lazy='joined')
3430 repo = relationship('Repository')
3430 repo = relationship('Repository')
3431 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3431 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan", lazy='joined')
3432 pull_request = relationship('PullRequest', lazy='joined')
3432 pull_request = relationship('PullRequest', lazy='joined')
3433 pull_request_version = relationship('PullRequestVersion')
3433 pull_request_version = relationship('PullRequestVersion')
3434
3434
3435 @classmethod
3435 @classmethod
3436 def get_users(cls, revision=None, pull_request_id=None):
3436 def get_users(cls, revision=None, pull_request_id=None):
3437 """
3437 """
3438 Returns user associated with this ChangesetComment. ie those
3438 Returns user associated with this ChangesetComment. ie those
3439 who actually commented
3439 who actually commented
3440
3440
3441 :param cls:
3441 :param cls:
3442 :param revision:
3442 :param revision:
3443 """
3443 """
3444 q = Session().query(User)\
3444 q = Session().query(User)\
3445 .join(ChangesetComment.author)
3445 .join(ChangesetComment.author)
3446 if revision:
3446 if revision:
3447 q = q.filter(cls.revision == revision)
3447 q = q.filter(cls.revision == revision)
3448 elif pull_request_id:
3448 elif pull_request_id:
3449 q = q.filter(cls.pull_request_id == pull_request_id)
3449 q = q.filter(cls.pull_request_id == pull_request_id)
3450 return q.all()
3450 return q.all()
3451
3451
3452 @classmethod
3452 @classmethod
3453 def get_index_from_version(cls, pr_version, versions):
3453 def get_index_from_version(cls, pr_version, versions):
3454 num_versions = [x.pull_request_version_id for x in versions]
3454 num_versions = [x.pull_request_version_id for x in versions]
3455 try:
3455 try:
3456 return num_versions.index(pr_version) +1
3456 return num_versions.index(pr_version) +1
3457 except (IndexError, ValueError):
3457 except (IndexError, ValueError):
3458 return
3458 return
3459
3459
3460 @property
3460 @property
3461 def outdated(self):
3461 def outdated(self):
3462 return self.display_state == self.COMMENT_OUTDATED
3462 return self.display_state == self.COMMENT_OUTDATED
3463
3463
3464 def outdated_at_version(self, version):
3464 def outdated_at_version(self, version):
3465 """
3465 """
3466 Checks if comment is outdated for given pull request version
3466 Checks if comment is outdated for given pull request version
3467 """
3467 """
3468 return self.outdated and self.pull_request_version_id != version
3468 return self.outdated and self.pull_request_version_id != version
3469
3469
3470 def older_than_version(self, version):
3470 def older_than_version(self, version):
3471 """
3471 """
3472 Checks if comment is made from previous version than given
3472 Checks if comment is made from previous version than given
3473 """
3473 """
3474 if version is None:
3474 if version is None:
3475 return self.pull_request_version_id is not None
3475 return self.pull_request_version_id is not None
3476
3476
3477 return self.pull_request_version_id < version
3477 return self.pull_request_version_id < version
3478
3478
3479 @property
3479 @property
3480 def resolved(self):
3480 def resolved(self):
3481 return self.resolved_by[0] if self.resolved_by else None
3481 return self.resolved_by[0] if self.resolved_by else None
3482
3482
3483 @property
3483 @property
3484 def is_todo(self):
3484 def is_todo(self):
3485 return self.comment_type == self.COMMENT_TYPE_TODO
3485 return self.comment_type == self.COMMENT_TYPE_TODO
3486
3486
3487 @property
3487 @property
3488 def is_inline(self):
3488 def is_inline(self):
3489 return self.line_no and self.f_path
3489 return self.line_no and self.f_path
3490
3490
3491 def get_index_version(self, versions):
3491 def get_index_version(self, versions):
3492 return self.get_index_from_version(
3492 return self.get_index_from_version(
3493 self.pull_request_version_id, versions)
3493 self.pull_request_version_id, versions)
3494
3494
3495 def __repr__(self):
3495 def __repr__(self):
3496 if self.comment_id:
3496 if self.comment_id:
3497 return '<DB:Comment #%s>' % self.comment_id
3497 return '<DB:Comment #%s>' % self.comment_id
3498 else:
3498 else:
3499 return '<DB:Comment at %#x>' % id(self)
3499 return '<DB:Comment at %#x>' % id(self)
3500
3500
3501 def get_api_data(self):
3501 def get_api_data(self):
3502 comment = self
3502 comment = self
3503 data = {
3503 data = {
3504 'comment_id': comment.comment_id,
3504 'comment_id': comment.comment_id,
3505 'comment_type': comment.comment_type,
3505 'comment_type': comment.comment_type,
3506 'comment_text': comment.text,
3506 'comment_text': comment.text,
3507 'comment_status': comment.status_change,
3507 'comment_status': comment.status_change,
3508 'comment_f_path': comment.f_path,
3508 'comment_f_path': comment.f_path,
3509 'comment_lineno': comment.line_no,
3509 'comment_lineno': comment.line_no,
3510 'comment_author': comment.author,
3510 'comment_author': comment.author,
3511 'comment_created_on': comment.created_on,
3511 'comment_created_on': comment.created_on,
3512 'comment_resolved_by': self.resolved
3512 'comment_resolved_by': self.resolved
3513 }
3513 }
3514 return data
3514 return data
3515
3515
3516 def __json__(self):
3516 def __json__(self):
3517 data = dict()
3517 data = dict()
3518 data.update(self.get_api_data())
3518 data.update(self.get_api_data())
3519 return data
3519 return data
3520
3520
3521
3521
3522 class ChangesetStatus(Base, BaseModel):
3522 class ChangesetStatus(Base, BaseModel):
3523 __tablename__ = 'changeset_statuses'
3523 __tablename__ = 'changeset_statuses'
3524 __table_args__ = (
3524 __table_args__ = (
3525 Index('cs_revision_idx', 'revision'),
3525 Index('cs_revision_idx', 'revision'),
3526 Index('cs_version_idx', 'version'),
3526 Index('cs_version_idx', 'version'),
3527 UniqueConstraint('repo_id', 'revision', 'version'),
3527 UniqueConstraint('repo_id', 'revision', 'version'),
3528 base_table_args
3528 base_table_args
3529 )
3529 )
3530
3530
3531 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3531 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3532 STATUS_APPROVED = 'approved'
3532 STATUS_APPROVED = 'approved'
3533 STATUS_REJECTED = 'rejected'
3533 STATUS_REJECTED = 'rejected'
3534 STATUS_UNDER_REVIEW = 'under_review'
3534 STATUS_UNDER_REVIEW = 'under_review'
3535
3535
3536 STATUSES = [
3536 STATUSES = [
3537 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3537 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3538 (STATUS_APPROVED, _("Approved")),
3538 (STATUS_APPROVED, _("Approved")),
3539 (STATUS_REJECTED, _("Rejected")),
3539 (STATUS_REJECTED, _("Rejected")),
3540 (STATUS_UNDER_REVIEW, _("Under Review")),
3540 (STATUS_UNDER_REVIEW, _("Under Review")),
3541 ]
3541 ]
3542
3542
3543 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3543 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3544 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3544 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3545 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3546 revision = Column('revision', String(40), nullable=False)
3546 revision = Column('revision', String(40), nullable=False)
3547 status = Column('status', String(128), nullable=False, default=DEFAULT)
3547 status = Column('status', String(128), nullable=False, default=DEFAULT)
3548 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3548 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3549 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3549 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3550 version = Column('version', Integer(), nullable=False, default=0)
3550 version = Column('version', Integer(), nullable=False, default=0)
3551 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3551 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3552
3552
3553 author = relationship('User', lazy='joined')
3553 author = relationship('User', lazy='joined')
3554 repo = relationship('Repository')
3554 repo = relationship('Repository')
3555 comment = relationship('ChangesetComment', lazy='joined')
3555 comment = relationship('ChangesetComment', lazy='joined')
3556 pull_request = relationship('PullRequest', lazy='joined')
3556 pull_request = relationship('PullRequest', lazy='joined')
3557
3557
3558 def __unicode__(self):
3558 def __unicode__(self):
3559 return u"<%s('%s[v%s]:%s')>" % (
3559 return u"<%s('%s[v%s]:%s')>" % (
3560 self.__class__.__name__,
3560 self.__class__.__name__,
3561 self.status, self.version, self.author
3561 self.status, self.version, self.author
3562 )
3562 )
3563
3563
3564 @classmethod
3564 @classmethod
3565 def get_status_lbl(cls, value):
3565 def get_status_lbl(cls, value):
3566 return dict(cls.STATUSES).get(value)
3566 return dict(cls.STATUSES).get(value)
3567
3567
3568 @property
3568 @property
3569 def status_lbl(self):
3569 def status_lbl(self):
3570 return ChangesetStatus.get_status_lbl(self.status)
3570 return ChangesetStatus.get_status_lbl(self.status)
3571
3571
3572 def get_api_data(self):
3572 def get_api_data(self):
3573 status = self
3573 status = self
3574 data = {
3574 data = {
3575 'status_id': status.changeset_status_id,
3575 'status_id': status.changeset_status_id,
3576 'status': status.status,
3576 'status': status.status,
3577 }
3577 }
3578 return data
3578 return data
3579
3579
3580 def __json__(self):
3580 def __json__(self):
3581 data = dict()
3581 data = dict()
3582 data.update(self.get_api_data())
3582 data.update(self.get_api_data())
3583 return data
3583 return data
3584
3584
3585
3585
3586 class _SetState(object):
3586 class _SetState(object):
3587 """
3587 """
3588 Context processor allowing changing state for sensitive operation such as
3588 Context processor allowing changing state for sensitive operation such as
3589 pull request update or merge
3589 pull request update or merge
3590 """
3590 """
3591
3591
3592 def __init__(self, pull_request, pr_state, back_state=None):
3592 def __init__(self, pull_request, pr_state, back_state=None):
3593 self._pr = pull_request
3593 self._pr = pull_request
3594 self._org_state = back_state or pull_request.pull_request_state
3594 self._org_state = back_state or pull_request.pull_request_state
3595 self._pr_state = pr_state
3595 self._pr_state = pr_state
3596
3596
3597 def __enter__(self):
3597 def __enter__(self):
3598 log.debug('StateLock: entering set state context, setting state to: `%s`',
3598 log.debug('StateLock: entering set state context, setting state to: `%s`',
3599 self._pr_state)
3599 self._pr_state)
3600 self._pr.pull_request_state = self._pr_state
3600 self._pr.pull_request_state = self._pr_state
3601 Session().add(self._pr)
3601 Session().add(self._pr)
3602 Session().commit()
3602 Session().commit()
3603
3603
3604 def __exit__(self, exc_type, exc_val, exc_tb):
3604 def __exit__(self, exc_type, exc_val, exc_tb):
3605 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3605 log.debug('StateLock: exiting set state context, setting state to: `%s`',
3606 self._org_state)
3606 self._org_state)
3607 self._pr.pull_request_state = self._org_state
3607 self._pr.pull_request_state = self._org_state
3608 Session().add(self._pr)
3608 Session().add(self._pr)
3609 Session().commit()
3609 Session().commit()
3610
3610
3611
3611
3612 class _PullRequestBase(BaseModel):
3612 class _PullRequestBase(BaseModel):
3613 """
3613 """
3614 Common attributes of pull request and version entries.
3614 Common attributes of pull request and version entries.
3615 """
3615 """
3616
3616
3617 # .status values
3617 # .status values
3618 STATUS_NEW = u'new'
3618 STATUS_NEW = u'new'
3619 STATUS_OPEN = u'open'
3619 STATUS_OPEN = u'open'
3620 STATUS_CLOSED = u'closed'
3620 STATUS_CLOSED = u'closed'
3621
3621
3622 # available states
3622 # available states
3623 STATE_CREATING = u'creating'
3623 STATE_CREATING = u'creating'
3624 STATE_UPDATING = u'updating'
3624 STATE_UPDATING = u'updating'
3625 STATE_MERGING = u'merging'
3625 STATE_MERGING = u'merging'
3626 STATE_CREATED = u'created'
3626 STATE_CREATED = u'created'
3627
3627
3628 title = Column('title', Unicode(255), nullable=True)
3628 title = Column('title', Unicode(255), nullable=True)
3629 description = Column(
3629 description = Column(
3630 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3630 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
3631 nullable=True)
3631 nullable=True)
3632 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3632 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
3633
3633
3634 # new/open/closed status of pull request (not approve/reject/etc)
3634 # new/open/closed status of pull request (not approve/reject/etc)
3635 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3635 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
3636 created_on = Column(
3636 created_on = Column(
3637 'created_on', DateTime(timezone=False), nullable=False,
3637 'created_on', DateTime(timezone=False), nullable=False,
3638 default=datetime.datetime.now)
3638 default=datetime.datetime.now)
3639 updated_on = Column(
3639 updated_on = Column(
3640 'updated_on', DateTime(timezone=False), nullable=False,
3640 'updated_on', DateTime(timezone=False), nullable=False,
3641 default=datetime.datetime.now)
3641 default=datetime.datetime.now)
3642
3642
3643 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3643 pull_request_state = Column("pull_request_state", String(255), nullable=True)
3644
3644
3645 @declared_attr
3645 @declared_attr
3646 def user_id(cls):
3646 def user_id(cls):
3647 return Column(
3647 return Column(
3648 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3648 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
3649 unique=None)
3649 unique=None)
3650
3650
3651 # 500 revisions max
3651 # 500 revisions max
3652 _revisions = Column(
3652 _revisions = Column(
3653 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3653 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3654
3654
3655 @declared_attr
3655 @declared_attr
3656 def source_repo_id(cls):
3656 def source_repo_id(cls):
3657 # TODO: dan: rename column to source_repo_id
3657 # TODO: dan: rename column to source_repo_id
3658 return Column(
3658 return Column(
3659 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3659 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3660 nullable=False)
3660 nullable=False)
3661
3661
3662 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3662 _source_ref = Column('org_ref', Unicode(255), nullable=False)
3663
3663
3664 @hybrid_property
3664 @hybrid_property
3665 def source_ref(self):
3665 def source_ref(self):
3666 return self._source_ref
3666 return self._source_ref
3667
3667
3668 @source_ref.setter
3668 @source_ref.setter
3669 def source_ref(self, val):
3669 def source_ref(self, val):
3670 parts = (val or '').split(':')
3670 parts = (val or '').split(':')
3671 if len(parts) != 3:
3671 if len(parts) != 3:
3672 raise ValueError(
3672 raise ValueError(
3673 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3673 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3674 self._source_ref = safe_unicode(val)
3674 self._source_ref = safe_unicode(val)
3675
3675
3676 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3676 _target_ref = Column('other_ref', Unicode(255), nullable=False)
3677
3677
3678 @hybrid_property
3678 @hybrid_property
3679 def target_ref(self):
3679 def target_ref(self):
3680 return self._target_ref
3680 return self._target_ref
3681
3681
3682 @target_ref.setter
3682 @target_ref.setter
3683 def target_ref(self, val):
3683 def target_ref(self, val):
3684 parts = (val or '').split(':')
3684 parts = (val or '').split(':')
3685 if len(parts) != 3:
3685 if len(parts) != 3:
3686 raise ValueError(
3686 raise ValueError(
3687 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3687 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
3688 self._target_ref = safe_unicode(val)
3688 self._target_ref = safe_unicode(val)
3689
3689
3690 @declared_attr
3690 @declared_attr
3691 def target_repo_id(cls):
3691 def target_repo_id(cls):
3692 # TODO: dan: rename column to target_repo_id
3692 # TODO: dan: rename column to target_repo_id
3693 return Column(
3693 return Column(
3694 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3694 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
3695 nullable=False)
3695 nullable=False)
3696
3696
3697 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3697 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
3698
3698
3699 # TODO: dan: rename column to last_merge_source_rev
3699 # TODO: dan: rename column to last_merge_source_rev
3700 _last_merge_source_rev = Column(
3700 _last_merge_source_rev = Column(
3701 'last_merge_org_rev', String(40), nullable=True)
3701 'last_merge_org_rev', String(40), nullable=True)
3702 # TODO: dan: rename column to last_merge_target_rev
3702 # TODO: dan: rename column to last_merge_target_rev
3703 _last_merge_target_rev = Column(
3703 _last_merge_target_rev = Column(
3704 'last_merge_other_rev', String(40), nullable=True)
3704 'last_merge_other_rev', String(40), nullable=True)
3705 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3705 _last_merge_status = Column('merge_status', Integer(), nullable=True)
3706 merge_rev = Column('merge_rev', String(40), nullable=True)
3706 merge_rev = Column('merge_rev', String(40), nullable=True)
3707
3707
3708 reviewer_data = Column(
3708 reviewer_data = Column(
3709 'reviewer_data_json', MutationObj.as_mutable(
3709 'reviewer_data_json', MutationObj.as_mutable(
3710 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3710 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
3711
3711
3712 @property
3712 @property
3713 def reviewer_data_json(self):
3713 def reviewer_data_json(self):
3714 return json.dumps(self.reviewer_data)
3714 return json.dumps(self.reviewer_data)
3715
3715
3716 @hybrid_property
3716 @hybrid_property
3717 def description_safe(self):
3717 def description_safe(self):
3718 from rhodecode.lib import helpers as h
3718 from rhodecode.lib import helpers as h
3719 return h.escape(self.description)
3719 return h.escape(self.description)
3720
3720
3721 @hybrid_property
3721 @hybrid_property
3722 def revisions(self):
3722 def revisions(self):
3723 return self._revisions.split(':') if self._revisions else []
3723 return self._revisions.split(':') if self._revisions else []
3724
3724
3725 @revisions.setter
3725 @revisions.setter
3726 def revisions(self, val):
3726 def revisions(self, val):
3727 self._revisions = ':'.join(val)
3727 self._revisions = ':'.join(val)
3728
3728
3729 @hybrid_property
3729 @hybrid_property
3730 def last_merge_status(self):
3730 def last_merge_status(self):
3731 return safe_int(self._last_merge_status)
3731 return safe_int(self._last_merge_status)
3732
3732
3733 @last_merge_status.setter
3733 @last_merge_status.setter
3734 def last_merge_status(self, val):
3734 def last_merge_status(self, val):
3735 self._last_merge_status = val
3735 self._last_merge_status = val
3736
3736
3737 @declared_attr
3737 @declared_attr
3738 def author(cls):
3738 def author(cls):
3739 return relationship('User', lazy='joined')
3739 return relationship('User', lazy='joined')
3740
3740
3741 @declared_attr
3741 @declared_attr
3742 def source_repo(cls):
3742 def source_repo(cls):
3743 return relationship(
3743 return relationship(
3744 'Repository',
3744 'Repository',
3745 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3745 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
3746
3746
3747 @property
3747 @property
3748 def source_ref_parts(self):
3748 def source_ref_parts(self):
3749 return self.unicode_to_reference(self.source_ref)
3749 return self.unicode_to_reference(self.source_ref)
3750
3750
3751 @declared_attr
3751 @declared_attr
3752 def target_repo(cls):
3752 def target_repo(cls):
3753 return relationship(
3753 return relationship(
3754 'Repository',
3754 'Repository',
3755 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3755 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
3756
3756
3757 @property
3757 @property
3758 def target_ref_parts(self):
3758 def target_ref_parts(self):
3759 return self.unicode_to_reference(self.target_ref)
3759 return self.unicode_to_reference(self.target_ref)
3760
3760
3761 @property
3761 @property
3762 def shadow_merge_ref(self):
3762 def shadow_merge_ref(self):
3763 return self.unicode_to_reference(self._shadow_merge_ref)
3763 return self.unicode_to_reference(self._shadow_merge_ref)
3764
3764
3765 @shadow_merge_ref.setter
3765 @shadow_merge_ref.setter
3766 def shadow_merge_ref(self, ref):
3766 def shadow_merge_ref(self, ref):
3767 self._shadow_merge_ref = self.reference_to_unicode(ref)
3767 self._shadow_merge_ref = self.reference_to_unicode(ref)
3768
3768
3769 @staticmethod
3769 @staticmethod
3770 def unicode_to_reference(raw):
3770 def unicode_to_reference(raw):
3771 """
3771 """
3772 Convert a unicode (or string) to a reference object.
3772 Convert a unicode (or string) to a reference object.
3773 If unicode evaluates to False it returns None.
3773 If unicode evaluates to False it returns None.
3774 """
3774 """
3775 if raw:
3775 if raw:
3776 refs = raw.split(':')
3776 refs = raw.split(':')
3777 return Reference(*refs)
3777 return Reference(*refs)
3778 else:
3778 else:
3779 return None
3779 return None
3780
3780
3781 @staticmethod
3781 @staticmethod
3782 def reference_to_unicode(ref):
3782 def reference_to_unicode(ref):
3783 """
3783 """
3784 Convert a reference object to unicode.
3784 Convert a reference object to unicode.
3785 If reference is None it returns None.
3785 If reference is None it returns None.
3786 """
3786 """
3787 if ref:
3787 if ref:
3788 return u':'.join(ref)
3788 return u':'.join(ref)
3789 else:
3789 else:
3790 return None
3790 return None
3791
3791
3792 def get_api_data(self, with_merge_state=True):
3792 def get_api_data(self, with_merge_state=True):
3793 from rhodecode.model.pull_request import PullRequestModel
3793 from rhodecode.model.pull_request import PullRequestModel
3794
3794
3795 pull_request = self
3795 pull_request = self
3796 if with_merge_state:
3796 if with_merge_state:
3797 merge_status = PullRequestModel().merge_status(pull_request)
3797 merge_status = PullRequestModel().merge_status(pull_request)
3798 merge_state = {
3798 merge_state = {
3799 'status': merge_status[0],
3799 'status': merge_status[0],
3800 'message': safe_unicode(merge_status[1]),
3800 'message': safe_unicode(merge_status[1]),
3801 }
3801 }
3802 else:
3802 else:
3803 merge_state = {'status': 'not_available',
3803 merge_state = {'status': 'not_available',
3804 'message': 'not_available'}
3804 'message': 'not_available'}
3805
3805
3806 merge_data = {
3806 merge_data = {
3807 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3807 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
3808 'reference': (
3808 'reference': (
3809 pull_request.shadow_merge_ref._asdict()
3809 pull_request.shadow_merge_ref._asdict()
3810 if pull_request.shadow_merge_ref else None),
3810 if pull_request.shadow_merge_ref else None),
3811 }
3811 }
3812
3812
3813 data = {
3813 data = {
3814 'pull_request_id': pull_request.pull_request_id,
3814 'pull_request_id': pull_request.pull_request_id,
3815 'url': PullRequestModel().get_url(pull_request),
3815 'url': PullRequestModel().get_url(pull_request),
3816 'title': pull_request.title,
3816 'title': pull_request.title,
3817 'description': pull_request.description,
3817 'description': pull_request.description,
3818 'status': pull_request.status,
3818 'status': pull_request.status,
3819 'state': pull_request.pull_request_state,
3819 'state': pull_request.pull_request_state,
3820 'created_on': pull_request.created_on,
3820 'created_on': pull_request.created_on,
3821 'updated_on': pull_request.updated_on,
3821 'updated_on': pull_request.updated_on,
3822 'commit_ids': pull_request.revisions,
3822 'commit_ids': pull_request.revisions,
3823 'review_status': pull_request.calculated_review_status(),
3823 'review_status': pull_request.calculated_review_status(),
3824 'mergeable': merge_state,
3824 'mergeable': merge_state,
3825 'source': {
3825 'source': {
3826 'clone_url': pull_request.source_repo.clone_url(),
3826 'clone_url': pull_request.source_repo.clone_url(),
3827 'repository': pull_request.source_repo.repo_name,
3827 'repository': pull_request.source_repo.repo_name,
3828 'reference': {
3828 'reference': {
3829 'name': pull_request.source_ref_parts.name,
3829 'name': pull_request.source_ref_parts.name,
3830 'type': pull_request.source_ref_parts.type,
3830 'type': pull_request.source_ref_parts.type,
3831 'commit_id': pull_request.source_ref_parts.commit_id,
3831 'commit_id': pull_request.source_ref_parts.commit_id,
3832 },
3832 },
3833 },
3833 },
3834 'target': {
3834 'target': {
3835 'clone_url': pull_request.target_repo.clone_url(),
3835 'clone_url': pull_request.target_repo.clone_url(),
3836 'repository': pull_request.target_repo.repo_name,
3836 'repository': pull_request.target_repo.repo_name,
3837 'reference': {
3837 'reference': {
3838 'name': pull_request.target_ref_parts.name,
3838 'name': pull_request.target_ref_parts.name,
3839 'type': pull_request.target_ref_parts.type,
3839 'type': pull_request.target_ref_parts.type,
3840 'commit_id': pull_request.target_ref_parts.commit_id,
3840 'commit_id': pull_request.target_ref_parts.commit_id,
3841 },
3841 },
3842 },
3842 },
3843 'merge': merge_data,
3843 'merge': merge_data,
3844 'author': pull_request.author.get_api_data(include_secrets=False,
3844 'author': pull_request.author.get_api_data(include_secrets=False,
3845 details='basic'),
3845 details='basic'),
3846 'reviewers': [
3846 'reviewers': [
3847 {
3847 {
3848 'user': reviewer.get_api_data(include_secrets=False,
3848 'user': reviewer.get_api_data(include_secrets=False,
3849 details='basic'),
3849 details='basic'),
3850 'reasons': reasons,
3850 'reasons': reasons,
3851 'review_status': st[0][1].status if st else 'not_reviewed',
3851 'review_status': st[0][1].status if st else 'not_reviewed',
3852 }
3852 }
3853 for obj, reviewer, reasons, mandatory, st in
3853 for obj, reviewer, reasons, mandatory, st in
3854 pull_request.reviewers_statuses()
3854 pull_request.reviewers_statuses()
3855 ]
3855 ]
3856 }
3856 }
3857
3857
3858 return data
3858 return data
3859
3859
3860 def set_state(self, pull_request_state, final_state=None):
3860 def set_state(self, pull_request_state, final_state=None):
3861 """
3861 """
3862 # goes from initial state to updating to initial state.
3862 # goes from initial state to updating to initial state.
3863 # initial state can be changed by specifying back_state=
3863 # initial state can be changed by specifying back_state=
3864 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3864 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
3865 pull_request.merge()
3865 pull_request.merge()
3866
3866
3867 :param pull_request_state:
3867 :param pull_request_state:
3868 :param final_state:
3868 :param final_state:
3869
3869
3870 """
3870 """
3871
3871
3872 return _SetState(self, pull_request_state, back_state=final_state)
3872 return _SetState(self, pull_request_state, back_state=final_state)
3873
3873
3874
3874
3875 class PullRequest(Base, _PullRequestBase):
3875 class PullRequest(Base, _PullRequestBase):
3876 __tablename__ = 'pull_requests'
3876 __tablename__ = 'pull_requests'
3877 __table_args__ = (
3877 __table_args__ = (
3878 base_table_args,
3878 base_table_args,
3879 )
3879 )
3880
3880
3881 pull_request_id = Column(
3881 pull_request_id = Column(
3882 'pull_request_id', Integer(), nullable=False, primary_key=True)
3882 'pull_request_id', Integer(), nullable=False, primary_key=True)
3883
3883
3884 def __repr__(self):
3884 def __repr__(self):
3885 if self.pull_request_id:
3885 if self.pull_request_id:
3886 return '<DB:PullRequest #%s>' % self.pull_request_id
3886 return '<DB:PullRequest #%s>' % self.pull_request_id
3887 else:
3887 else:
3888 return '<DB:PullRequest at %#x>' % id(self)
3888 return '<DB:PullRequest at %#x>' % id(self)
3889
3889
3890 reviewers = relationship('PullRequestReviewers',
3890 reviewers = relationship('PullRequestReviewers',
3891 cascade="all, delete, delete-orphan")
3891 cascade="all, delete, delete-orphan")
3892 statuses = relationship('ChangesetStatus',
3892 statuses = relationship('ChangesetStatus',
3893 cascade="all, delete, delete-orphan")
3893 cascade="all, delete, delete-orphan")
3894 comments = relationship('ChangesetComment',
3894 comments = relationship('ChangesetComment',
3895 cascade="all, delete, delete-orphan")
3895 cascade="all, delete, delete-orphan")
3896 versions = relationship('PullRequestVersion',
3896 versions = relationship('PullRequestVersion',
3897 cascade="all, delete, delete-orphan",
3897 cascade="all, delete, delete-orphan",
3898 lazy='dynamic')
3898 lazy='dynamic')
3899
3899
3900 @classmethod
3900 @classmethod
3901 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3901 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3902 internal_methods=None):
3902 internal_methods=None):
3903
3903
3904 class PullRequestDisplay(object):
3904 class PullRequestDisplay(object):
3905 """
3905 """
3906 Special object wrapper for showing PullRequest data via Versions
3906 Special object wrapper for showing PullRequest data via Versions
3907 It mimics PR object as close as possible. This is read only object
3907 It mimics PR object as close as possible. This is read only object
3908 just for display
3908 just for display
3909 """
3909 """
3910
3910
3911 def __init__(self, attrs, internal=None):
3911 def __init__(self, attrs, internal=None):
3912 self.attrs = attrs
3912 self.attrs = attrs
3913 # internal have priority over the given ones via attrs
3913 # internal have priority over the given ones via attrs
3914 self.internal = internal or ['versions']
3914 self.internal = internal or ['versions']
3915
3915
3916 def __getattr__(self, item):
3916 def __getattr__(self, item):
3917 if item in self.internal:
3917 if item in self.internal:
3918 return getattr(self, item)
3918 return getattr(self, item)
3919 try:
3919 try:
3920 return self.attrs[item]
3920 return self.attrs[item]
3921 except KeyError:
3921 except KeyError:
3922 raise AttributeError(
3922 raise AttributeError(
3923 '%s object has no attribute %s' % (self, item))
3923 '%s object has no attribute %s' % (self, item))
3924
3924
3925 def __repr__(self):
3925 def __repr__(self):
3926 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3926 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
3927
3927
3928 def versions(self):
3928 def versions(self):
3929 return pull_request_obj.versions.order_by(
3929 return pull_request_obj.versions.order_by(
3930 PullRequestVersion.pull_request_version_id).all()
3930 PullRequestVersion.pull_request_version_id).all()
3931
3931
3932 def is_closed(self):
3932 def is_closed(self):
3933 return pull_request_obj.is_closed()
3933 return pull_request_obj.is_closed()
3934
3934
3935 @property
3935 @property
3936 def pull_request_version_id(self):
3936 def pull_request_version_id(self):
3937 return getattr(pull_request_obj, 'pull_request_version_id', None)
3937 return getattr(pull_request_obj, 'pull_request_version_id', None)
3938
3938
3939 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3939 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3940
3940
3941 attrs.author = StrictAttributeDict(
3941 attrs.author = StrictAttributeDict(
3942 pull_request_obj.author.get_api_data())
3942 pull_request_obj.author.get_api_data())
3943 if pull_request_obj.target_repo:
3943 if pull_request_obj.target_repo:
3944 attrs.target_repo = StrictAttributeDict(
3944 attrs.target_repo = StrictAttributeDict(
3945 pull_request_obj.target_repo.get_api_data())
3945 pull_request_obj.target_repo.get_api_data())
3946 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3946 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
3947
3947
3948 if pull_request_obj.source_repo:
3948 if pull_request_obj.source_repo:
3949 attrs.source_repo = StrictAttributeDict(
3949 attrs.source_repo = StrictAttributeDict(
3950 pull_request_obj.source_repo.get_api_data())
3950 pull_request_obj.source_repo.get_api_data())
3951 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3951 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
3952
3952
3953 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3953 attrs.source_ref_parts = pull_request_obj.source_ref_parts
3954 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3954 attrs.target_ref_parts = pull_request_obj.target_ref_parts
3955 attrs.revisions = pull_request_obj.revisions
3955 attrs.revisions = pull_request_obj.revisions
3956
3956
3957 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3957 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
3958 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3958 attrs.reviewer_data = org_pull_request_obj.reviewer_data
3959 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3959 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
3960
3960
3961 return PullRequestDisplay(attrs, internal=internal_methods)
3961 return PullRequestDisplay(attrs, internal=internal_methods)
3962
3962
3963 def is_closed(self):
3963 def is_closed(self):
3964 return self.status == self.STATUS_CLOSED
3964 return self.status == self.STATUS_CLOSED
3965
3965
3966 def __json__(self):
3966 def __json__(self):
3967 return {
3967 return {
3968 'revisions': self.revisions,
3968 'revisions': self.revisions,
3969 }
3969 }
3970
3970
3971 def calculated_review_status(self):
3971 def calculated_review_status(self):
3972 from rhodecode.model.changeset_status import ChangesetStatusModel
3972 from rhodecode.model.changeset_status import ChangesetStatusModel
3973 return ChangesetStatusModel().calculated_review_status(self)
3973 return ChangesetStatusModel().calculated_review_status(self)
3974
3974
3975 def reviewers_statuses(self):
3975 def reviewers_statuses(self):
3976 from rhodecode.model.changeset_status import ChangesetStatusModel
3976 from rhodecode.model.changeset_status import ChangesetStatusModel
3977 return ChangesetStatusModel().reviewers_statuses(self)
3977 return ChangesetStatusModel().reviewers_statuses(self)
3978
3978
3979 @property
3979 @property
3980 def workspace_id(self):
3980 def workspace_id(self):
3981 from rhodecode.model.pull_request import PullRequestModel
3981 from rhodecode.model.pull_request import PullRequestModel
3982 return PullRequestModel()._workspace_id(self)
3982 return PullRequestModel()._workspace_id(self)
3983
3983
3984 def get_shadow_repo(self):
3984 def get_shadow_repo(self):
3985 workspace_id = self.workspace_id
3985 workspace_id = self.workspace_id
3986 vcs_obj = self.target_repo.scm_instance()
3986 vcs_obj = self.target_repo.scm_instance()
3987 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3987 shadow_repository_path = vcs_obj._get_shadow_repository_path(
3988 self.target_repo.repo_id, workspace_id)
3988 self.target_repo.repo_id, workspace_id)
3989 if os.path.isdir(shadow_repository_path):
3989 if os.path.isdir(shadow_repository_path):
3990 return vcs_obj._get_shadow_instance(shadow_repository_path)
3990 return vcs_obj._get_shadow_instance(shadow_repository_path)
3991
3991
3992
3992
3993 class PullRequestVersion(Base, _PullRequestBase):
3993 class PullRequestVersion(Base, _PullRequestBase):
3994 __tablename__ = 'pull_request_versions'
3994 __tablename__ = 'pull_request_versions'
3995 __table_args__ = (
3995 __table_args__ = (
3996 base_table_args,
3996 base_table_args,
3997 )
3997 )
3998
3998
3999 pull_request_version_id = Column(
3999 pull_request_version_id = Column(
4000 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4000 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4001 pull_request_id = Column(
4001 pull_request_id = Column(
4002 'pull_request_id', Integer(),
4002 'pull_request_id', Integer(),
4003 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4003 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4004 pull_request = relationship('PullRequest')
4004 pull_request = relationship('PullRequest')
4005
4005
4006 def __repr__(self):
4006 def __repr__(self):
4007 if self.pull_request_version_id:
4007 if self.pull_request_version_id:
4008 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4008 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4009 else:
4009 else:
4010 return '<DB:PullRequestVersion at %#x>' % id(self)
4010 return '<DB:PullRequestVersion at %#x>' % id(self)
4011
4011
4012 @property
4012 @property
4013 def reviewers(self):
4013 def reviewers(self):
4014 return self.pull_request.reviewers
4014 return self.pull_request.reviewers
4015
4015
4016 @property
4016 @property
4017 def versions(self):
4017 def versions(self):
4018 return self.pull_request.versions
4018 return self.pull_request.versions
4019
4019
4020 def is_closed(self):
4020 def is_closed(self):
4021 # calculate from original
4021 # calculate from original
4022 return self.pull_request.status == self.STATUS_CLOSED
4022 return self.pull_request.status == self.STATUS_CLOSED
4023
4023
4024 def calculated_review_status(self):
4024 def calculated_review_status(self):
4025 return self.pull_request.calculated_review_status()
4025 return self.pull_request.calculated_review_status()
4026
4026
4027 def reviewers_statuses(self):
4027 def reviewers_statuses(self):
4028 return self.pull_request.reviewers_statuses()
4028 return self.pull_request.reviewers_statuses()
4029
4029
4030
4030
4031 class PullRequestReviewers(Base, BaseModel):
4031 class PullRequestReviewers(Base, BaseModel):
4032 __tablename__ = 'pull_request_reviewers'
4032 __tablename__ = 'pull_request_reviewers'
4033 __table_args__ = (
4033 __table_args__ = (
4034 base_table_args,
4034 base_table_args,
4035 )
4035 )
4036
4036
4037 @hybrid_property
4037 @hybrid_property
4038 def reasons(self):
4038 def reasons(self):
4039 if not self._reasons:
4039 if not self._reasons:
4040 return []
4040 return []
4041 return self._reasons
4041 return self._reasons
4042
4042
4043 @reasons.setter
4043 @reasons.setter
4044 def reasons(self, val):
4044 def reasons(self, val):
4045 val = val or []
4045 val = val or []
4046 if any(not isinstance(x, compat.string_types) for x in val):
4046 if any(not isinstance(x, compat.string_types) for x in val):
4047 raise Exception('invalid reasons type, must be list of strings')
4047 raise Exception('invalid reasons type, must be list of strings')
4048 self._reasons = val
4048 self._reasons = val
4049
4049
4050 pull_requests_reviewers_id = Column(
4050 pull_requests_reviewers_id = Column(
4051 'pull_requests_reviewers_id', Integer(), nullable=False,
4051 'pull_requests_reviewers_id', Integer(), nullable=False,
4052 primary_key=True)
4052 primary_key=True)
4053 pull_request_id = Column(
4053 pull_request_id = Column(
4054 "pull_request_id", Integer(),
4054 "pull_request_id", Integer(),
4055 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4055 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4056 user_id = Column(
4056 user_id = Column(
4057 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4057 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4058 _reasons = Column(
4058 _reasons = Column(
4059 'reason', MutationList.as_mutable(
4059 'reason', MutationList.as_mutable(
4060 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4060 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4061
4061
4062 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4062 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4063 user = relationship('User')
4063 user = relationship('User')
4064 pull_request = relationship('PullRequest')
4064 pull_request = relationship('PullRequest')
4065
4065
4066 rule_data = Column(
4066 rule_data = Column(
4067 'rule_data_json',
4067 'rule_data_json',
4068 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4068 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4069
4069
4070 def rule_user_group_data(self):
4070 def rule_user_group_data(self):
4071 """
4071 """
4072 Returns the voting user group rule data for this reviewer
4072 Returns the voting user group rule data for this reviewer
4073 """
4073 """
4074
4074
4075 if self.rule_data and 'vote_rule' in self.rule_data:
4075 if self.rule_data and 'vote_rule' in self.rule_data:
4076 user_group_data = {}
4076 user_group_data = {}
4077 if 'rule_user_group_entry_id' in self.rule_data:
4077 if 'rule_user_group_entry_id' in self.rule_data:
4078 # means a group with voting rules !
4078 # means a group with voting rules !
4079 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4079 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4080 user_group_data['name'] = self.rule_data['rule_name']
4080 user_group_data['name'] = self.rule_data['rule_name']
4081 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4081 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4082
4082
4083 return user_group_data
4083 return user_group_data
4084
4084
4085 def __unicode__(self):
4085 def __unicode__(self):
4086 return u"<%s('id:%s')>" % (self.__class__.__name__,
4086 return u"<%s('id:%s')>" % (self.__class__.__name__,
4087 self.pull_requests_reviewers_id)
4087 self.pull_requests_reviewers_id)
4088
4088
4089
4089
4090 class Notification(Base, BaseModel):
4090 class Notification(Base, BaseModel):
4091 __tablename__ = 'notifications'
4091 __tablename__ = 'notifications'
4092 __table_args__ = (
4092 __table_args__ = (
4093 Index('notification_type_idx', 'type'),
4093 Index('notification_type_idx', 'type'),
4094 base_table_args,
4094 base_table_args,
4095 )
4095 )
4096
4096
4097 TYPE_CHANGESET_COMMENT = u'cs_comment'
4097 TYPE_CHANGESET_COMMENT = u'cs_comment'
4098 TYPE_MESSAGE = u'message'
4098 TYPE_MESSAGE = u'message'
4099 TYPE_MENTION = u'mention'
4099 TYPE_MENTION = u'mention'
4100 TYPE_REGISTRATION = u'registration'
4100 TYPE_REGISTRATION = u'registration'
4101 TYPE_PULL_REQUEST = u'pull_request'
4101 TYPE_PULL_REQUEST = u'pull_request'
4102 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4102 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4103
4103
4104 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4104 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4105 subject = Column('subject', Unicode(512), nullable=True)
4105 subject = Column('subject', Unicode(512), nullable=True)
4106 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4106 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4107 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4107 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4108 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4108 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4109 type_ = Column('type', Unicode(255))
4109 type_ = Column('type', Unicode(255))
4110
4110
4111 created_by_user = relationship('User')
4111 created_by_user = relationship('User')
4112 notifications_to_users = relationship('UserNotification', lazy='joined',
4112 notifications_to_users = relationship('UserNotification', lazy='joined',
4113 cascade="all, delete, delete-orphan")
4113 cascade="all, delete, delete-orphan")
4114
4114
4115 @property
4115 @property
4116 def recipients(self):
4116 def recipients(self):
4117 return [x.user for x in UserNotification.query()\
4117 return [x.user for x in UserNotification.query()\
4118 .filter(UserNotification.notification == self)\
4118 .filter(UserNotification.notification == self)\
4119 .order_by(UserNotification.user_id.asc()).all()]
4119 .order_by(UserNotification.user_id.asc()).all()]
4120
4120
4121 @classmethod
4121 @classmethod
4122 def create(cls, created_by, subject, body, recipients, type_=None):
4122 def create(cls, created_by, subject, body, recipients, type_=None):
4123 if type_ is None:
4123 if type_ is None:
4124 type_ = Notification.TYPE_MESSAGE
4124 type_ = Notification.TYPE_MESSAGE
4125
4125
4126 notification = cls()
4126 notification = cls()
4127 notification.created_by_user = created_by
4127 notification.created_by_user = created_by
4128 notification.subject = subject
4128 notification.subject = subject
4129 notification.body = body
4129 notification.body = body
4130 notification.type_ = type_
4130 notification.type_ = type_
4131 notification.created_on = datetime.datetime.now()
4131 notification.created_on = datetime.datetime.now()
4132
4132
4133 # For each recipient link the created notification to his account
4133 # For each recipient link the created notification to his account
4134 for u in recipients:
4134 for u in recipients:
4135 assoc = UserNotification()
4135 assoc = UserNotification()
4136 assoc.user_id = u.user_id
4136 assoc.user_id = u.user_id
4137 assoc.notification = notification
4137 assoc.notification = notification
4138
4138
4139 # if created_by is inside recipients mark his notification
4139 # if created_by is inside recipients mark his notification
4140 # as read
4140 # as read
4141 if u.user_id == created_by.user_id:
4141 if u.user_id == created_by.user_id:
4142 assoc.read = True
4142 assoc.read = True
4143 Session().add(assoc)
4143 Session().add(assoc)
4144
4144
4145 Session().add(notification)
4145 Session().add(notification)
4146
4146
4147 return notification
4147 return notification
4148
4148
4149
4149
4150 class UserNotification(Base, BaseModel):
4150 class UserNotification(Base, BaseModel):
4151 __tablename__ = 'user_to_notification'
4151 __tablename__ = 'user_to_notification'
4152 __table_args__ = (
4152 __table_args__ = (
4153 UniqueConstraint('user_id', 'notification_id'),
4153 UniqueConstraint('user_id', 'notification_id'),
4154 base_table_args
4154 base_table_args
4155 )
4155 )
4156
4156
4157 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4157 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4158 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4158 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4159 read = Column('read', Boolean, default=False)
4159 read = Column('read', Boolean, default=False)
4160 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4160 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4161
4161
4162 user = relationship('User', lazy="joined")
4162 user = relationship('User', lazy="joined")
4163 notification = relationship('Notification', lazy="joined",
4163 notification = relationship('Notification', lazy="joined",
4164 order_by=lambda: Notification.created_on.desc(),)
4164 order_by=lambda: Notification.created_on.desc(),)
4165
4165
4166 def mark_as_read(self):
4166 def mark_as_read(self):
4167 self.read = True
4167 self.read = True
4168 Session().add(self)
4168 Session().add(self)
4169
4169
4170
4170
4171 class Gist(Base, BaseModel):
4171 class Gist(Base, BaseModel):
4172 __tablename__ = 'gists'
4172 __tablename__ = 'gists'
4173 __table_args__ = (
4173 __table_args__ = (
4174 Index('g_gist_access_id_idx', 'gist_access_id'),
4174 Index('g_gist_access_id_idx', 'gist_access_id'),
4175 Index('g_created_on_idx', 'created_on'),
4175 Index('g_created_on_idx', 'created_on'),
4176 base_table_args
4176 base_table_args
4177 )
4177 )
4178
4178
4179 GIST_PUBLIC = u'public'
4179 GIST_PUBLIC = u'public'
4180 GIST_PRIVATE = u'private'
4180 GIST_PRIVATE = u'private'
4181 DEFAULT_FILENAME = u'gistfile1.txt'
4181 DEFAULT_FILENAME = u'gistfile1.txt'
4182
4182
4183 ACL_LEVEL_PUBLIC = u'acl_public'
4183 ACL_LEVEL_PUBLIC = u'acl_public'
4184 ACL_LEVEL_PRIVATE = u'acl_private'
4184 ACL_LEVEL_PRIVATE = u'acl_private'
4185
4185
4186 gist_id = Column('gist_id', Integer(), primary_key=True)
4186 gist_id = Column('gist_id', Integer(), primary_key=True)
4187 gist_access_id = Column('gist_access_id', Unicode(250))
4187 gist_access_id = Column('gist_access_id', Unicode(250))
4188 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4188 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4189 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4189 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4190 gist_expires = Column('gist_expires', Float(53), nullable=False)
4190 gist_expires = Column('gist_expires', Float(53), nullable=False)
4191 gist_type = Column('gist_type', Unicode(128), nullable=False)
4191 gist_type = Column('gist_type', Unicode(128), nullable=False)
4192 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4192 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4193 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4193 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4194 acl_level = Column('acl_level', Unicode(128), nullable=True)
4194 acl_level = Column('acl_level', Unicode(128), nullable=True)
4195
4195
4196 owner = relationship('User')
4196 owner = relationship('User')
4197
4197
4198 def __repr__(self):
4198 def __repr__(self):
4199 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4199 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4200
4200
4201 @hybrid_property
4201 @hybrid_property
4202 def description_safe(self):
4202 def description_safe(self):
4203 from rhodecode.lib import helpers as h
4203 from rhodecode.lib import helpers as h
4204 return h.escape(self.gist_description)
4204 return h.escape(self.gist_description)
4205
4205
4206 @classmethod
4206 @classmethod
4207 def get_or_404(cls, id_):
4207 def get_or_404(cls, id_):
4208 from pyramid.httpexceptions import HTTPNotFound
4208 from pyramid.httpexceptions import HTTPNotFound
4209
4209
4210 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4210 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4211 if not res:
4211 if not res:
4212 raise HTTPNotFound()
4212 raise HTTPNotFound()
4213 return res
4213 return res
4214
4214
4215 @classmethod
4215 @classmethod
4216 def get_by_access_id(cls, gist_access_id):
4216 def get_by_access_id(cls, gist_access_id):
4217 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4217 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4218
4218
4219 def gist_url(self):
4219 def gist_url(self):
4220 from rhodecode.model.gist import GistModel
4220 from rhodecode.model.gist import GistModel
4221 return GistModel().get_url(self)
4221 return GistModel().get_url(self)
4222
4222
4223 @classmethod
4223 @classmethod
4224 def base_path(cls):
4224 def base_path(cls):
4225 """
4225 """
4226 Returns base path when all gists are stored
4226 Returns base path when all gists are stored
4227
4227
4228 :param cls:
4228 :param cls:
4229 """
4229 """
4230 from rhodecode.model.gist import GIST_STORE_LOC
4230 from rhodecode.model.gist import GIST_STORE_LOC
4231 q = Session().query(RhodeCodeUi)\
4231 q = Session().query(RhodeCodeUi)\
4232 .filter(RhodeCodeUi.ui_key == URL_SEP)
4232 .filter(RhodeCodeUi.ui_key == URL_SEP)
4233 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4233 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4234 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4234 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4235
4235
4236 def get_api_data(self):
4236 def get_api_data(self):
4237 """
4237 """
4238 Common function for generating gist related data for API
4238 Common function for generating gist related data for API
4239 """
4239 """
4240 gist = self
4240 gist = self
4241 data = {
4241 data = {
4242 'gist_id': gist.gist_id,
4242 'gist_id': gist.gist_id,
4243 'type': gist.gist_type,
4243 'type': gist.gist_type,
4244 'access_id': gist.gist_access_id,
4244 'access_id': gist.gist_access_id,
4245 'description': gist.gist_description,
4245 'description': gist.gist_description,
4246 'url': gist.gist_url(),
4246 'url': gist.gist_url(),
4247 'expires': gist.gist_expires,
4247 'expires': gist.gist_expires,
4248 'created_on': gist.created_on,
4248 'created_on': gist.created_on,
4249 'modified_at': gist.modified_at,
4249 'modified_at': gist.modified_at,
4250 'content': None,
4250 'content': None,
4251 'acl_level': gist.acl_level,
4251 'acl_level': gist.acl_level,
4252 }
4252 }
4253 return data
4253 return data
4254
4254
4255 def __json__(self):
4255 def __json__(self):
4256 data = dict(
4256 data = dict(
4257 )
4257 )
4258 data.update(self.get_api_data())
4258 data.update(self.get_api_data())
4259 return data
4259 return data
4260 # SCM functions
4260 # SCM functions
4261
4261
4262 def scm_instance(self, **kwargs):
4262 def scm_instance(self, **kwargs):
4263 """
4264 Get explicit Mercurial repository used
4265 :param kwargs:
4266 :return:
4267 """
4268 from rhodecode.model.gist import GistModel
4263 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4269 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4264 return get_vcs_instance(
4270 return get_vcs_instance(
4265 repo_path=safe_str(full_repo_path), create=False)
4271 repo_path=safe_str(full_repo_path), create=False,
4272 _vcs_alias=GistModel.vcs_backend)
4266
4273
4267
4274
4268 class ExternalIdentity(Base, BaseModel):
4275 class ExternalIdentity(Base, BaseModel):
4269 __tablename__ = 'external_identities'
4276 __tablename__ = 'external_identities'
4270 __table_args__ = (
4277 __table_args__ = (
4271 Index('local_user_id_idx', 'local_user_id'),
4278 Index('local_user_id_idx', 'local_user_id'),
4272 Index('external_id_idx', 'external_id'),
4279 Index('external_id_idx', 'external_id'),
4273 base_table_args
4280 base_table_args
4274 )
4281 )
4275
4282
4276 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4283 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4277 external_username = Column('external_username', Unicode(1024), default=u'')
4284 external_username = Column('external_username', Unicode(1024), default=u'')
4278 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4285 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4279 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4286 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4280 access_token = Column('access_token', String(1024), default=u'')
4287 access_token = Column('access_token', String(1024), default=u'')
4281 alt_token = Column('alt_token', String(1024), default=u'')
4288 alt_token = Column('alt_token', String(1024), default=u'')
4282 token_secret = Column('token_secret', String(1024), default=u'')
4289 token_secret = Column('token_secret', String(1024), default=u'')
4283
4290
4284 @classmethod
4291 @classmethod
4285 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4292 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4286 """
4293 """
4287 Returns ExternalIdentity instance based on search params
4294 Returns ExternalIdentity instance based on search params
4288
4295
4289 :param external_id:
4296 :param external_id:
4290 :param provider_name:
4297 :param provider_name:
4291 :return: ExternalIdentity
4298 :return: ExternalIdentity
4292 """
4299 """
4293 query = cls.query()
4300 query = cls.query()
4294 query = query.filter(cls.external_id == external_id)
4301 query = query.filter(cls.external_id == external_id)
4295 query = query.filter(cls.provider_name == provider_name)
4302 query = query.filter(cls.provider_name == provider_name)
4296 if local_user_id:
4303 if local_user_id:
4297 query = query.filter(cls.local_user_id == local_user_id)
4304 query = query.filter(cls.local_user_id == local_user_id)
4298 return query.first()
4305 return query.first()
4299
4306
4300 @classmethod
4307 @classmethod
4301 def user_by_external_id_and_provider(cls, external_id, provider_name):
4308 def user_by_external_id_and_provider(cls, external_id, provider_name):
4302 """
4309 """
4303 Returns User instance based on search params
4310 Returns User instance based on search params
4304
4311
4305 :param external_id:
4312 :param external_id:
4306 :param provider_name:
4313 :param provider_name:
4307 :return: User
4314 :return: User
4308 """
4315 """
4309 query = User.query()
4316 query = User.query()
4310 query = query.filter(cls.external_id == external_id)
4317 query = query.filter(cls.external_id == external_id)
4311 query = query.filter(cls.provider_name == provider_name)
4318 query = query.filter(cls.provider_name == provider_name)
4312 query = query.filter(User.user_id == cls.local_user_id)
4319 query = query.filter(User.user_id == cls.local_user_id)
4313 return query.first()
4320 return query.first()
4314
4321
4315 @classmethod
4322 @classmethod
4316 def by_local_user_id(cls, local_user_id):
4323 def by_local_user_id(cls, local_user_id):
4317 """
4324 """
4318 Returns all tokens for user
4325 Returns all tokens for user
4319
4326
4320 :param local_user_id:
4327 :param local_user_id:
4321 :return: ExternalIdentity
4328 :return: ExternalIdentity
4322 """
4329 """
4323 query = cls.query()
4330 query = cls.query()
4324 query = query.filter(cls.local_user_id == local_user_id)
4331 query = query.filter(cls.local_user_id == local_user_id)
4325 return query
4332 return query
4326
4333
4327 @classmethod
4334 @classmethod
4328 def load_provider_plugin(cls, plugin_id):
4335 def load_provider_plugin(cls, plugin_id):
4329 from rhodecode.authentication.base import loadplugin
4336 from rhodecode.authentication.base import loadplugin
4330 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4337 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4331 auth_plugin = loadplugin(_plugin_id)
4338 auth_plugin = loadplugin(_plugin_id)
4332 return auth_plugin
4339 return auth_plugin
4333
4340
4334
4341
4335 class Integration(Base, BaseModel):
4342 class Integration(Base, BaseModel):
4336 __tablename__ = 'integrations'
4343 __tablename__ = 'integrations'
4337 __table_args__ = (
4344 __table_args__ = (
4338 base_table_args
4345 base_table_args
4339 )
4346 )
4340
4347
4341 integration_id = Column('integration_id', Integer(), primary_key=True)
4348 integration_id = Column('integration_id', Integer(), primary_key=True)
4342 integration_type = Column('integration_type', String(255))
4349 integration_type = Column('integration_type', String(255))
4343 enabled = Column('enabled', Boolean(), nullable=False)
4350 enabled = Column('enabled', Boolean(), nullable=False)
4344 name = Column('name', String(255), nullable=False)
4351 name = Column('name', String(255), nullable=False)
4345 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4352 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4346 default=False)
4353 default=False)
4347
4354
4348 settings = Column(
4355 settings = Column(
4349 'settings_json', MutationObj.as_mutable(
4356 'settings_json', MutationObj.as_mutable(
4350 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4357 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4351 repo_id = Column(
4358 repo_id = Column(
4352 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4359 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4353 nullable=True, unique=None, default=None)
4360 nullable=True, unique=None, default=None)
4354 repo = relationship('Repository', lazy='joined')
4361 repo = relationship('Repository', lazy='joined')
4355
4362
4356 repo_group_id = Column(
4363 repo_group_id = Column(
4357 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4364 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4358 nullable=True, unique=None, default=None)
4365 nullable=True, unique=None, default=None)
4359 repo_group = relationship('RepoGroup', lazy='joined')
4366 repo_group = relationship('RepoGroup', lazy='joined')
4360
4367
4361 @property
4368 @property
4362 def scope(self):
4369 def scope(self):
4363 if self.repo:
4370 if self.repo:
4364 return repr(self.repo)
4371 return repr(self.repo)
4365 if self.repo_group:
4372 if self.repo_group:
4366 if self.child_repos_only:
4373 if self.child_repos_only:
4367 return repr(self.repo_group) + ' (child repos only)'
4374 return repr(self.repo_group) + ' (child repos only)'
4368 else:
4375 else:
4369 return repr(self.repo_group) + ' (recursive)'
4376 return repr(self.repo_group) + ' (recursive)'
4370 if self.child_repos_only:
4377 if self.child_repos_only:
4371 return 'root_repos'
4378 return 'root_repos'
4372 return 'global'
4379 return 'global'
4373
4380
4374 def __repr__(self):
4381 def __repr__(self):
4375 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4382 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4376
4383
4377
4384
4378 class RepoReviewRuleUser(Base, BaseModel):
4385 class RepoReviewRuleUser(Base, BaseModel):
4379 __tablename__ = 'repo_review_rules_users'
4386 __tablename__ = 'repo_review_rules_users'
4380 __table_args__ = (
4387 __table_args__ = (
4381 base_table_args
4388 base_table_args
4382 )
4389 )
4383
4390
4384 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4391 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4385 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4392 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4386 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4393 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4387 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4394 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4388 user = relationship('User')
4395 user = relationship('User')
4389
4396
4390 def rule_data(self):
4397 def rule_data(self):
4391 return {
4398 return {
4392 'mandatory': self.mandatory
4399 'mandatory': self.mandatory
4393 }
4400 }
4394
4401
4395
4402
4396 class RepoReviewRuleUserGroup(Base, BaseModel):
4403 class RepoReviewRuleUserGroup(Base, BaseModel):
4397 __tablename__ = 'repo_review_rules_users_groups'
4404 __tablename__ = 'repo_review_rules_users_groups'
4398 __table_args__ = (
4405 __table_args__ = (
4399 base_table_args
4406 base_table_args
4400 )
4407 )
4401
4408
4402 VOTE_RULE_ALL = -1
4409 VOTE_RULE_ALL = -1
4403
4410
4404 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4411 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
4405 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4412 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4406 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4413 users_group_id = Column("users_group_id", Integer(),ForeignKey('users_groups.users_group_id'), nullable=False)
4407 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4414 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4408 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4415 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
4409 users_group = relationship('UserGroup')
4416 users_group = relationship('UserGroup')
4410
4417
4411 def rule_data(self):
4418 def rule_data(self):
4412 return {
4419 return {
4413 'mandatory': self.mandatory,
4420 'mandatory': self.mandatory,
4414 'vote_rule': self.vote_rule
4421 'vote_rule': self.vote_rule
4415 }
4422 }
4416
4423
4417 @property
4424 @property
4418 def vote_rule_label(self):
4425 def vote_rule_label(self):
4419 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4426 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
4420 return 'all must vote'
4427 return 'all must vote'
4421 else:
4428 else:
4422 return 'min. vote {}'.format(self.vote_rule)
4429 return 'min. vote {}'.format(self.vote_rule)
4423
4430
4424
4431
4425 class RepoReviewRule(Base, BaseModel):
4432 class RepoReviewRule(Base, BaseModel):
4426 __tablename__ = 'repo_review_rules'
4433 __tablename__ = 'repo_review_rules'
4427 __table_args__ = (
4434 __table_args__ = (
4428 base_table_args
4435 base_table_args
4429 )
4436 )
4430
4437
4431 repo_review_rule_id = Column(
4438 repo_review_rule_id = Column(
4432 'repo_review_rule_id', Integer(), primary_key=True)
4439 'repo_review_rule_id', Integer(), primary_key=True)
4433 repo_id = Column(
4440 repo_id = Column(
4434 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4441 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
4435 repo = relationship('Repository', backref='review_rules')
4442 repo = relationship('Repository', backref='review_rules')
4436
4443
4437 review_rule_name = Column('review_rule_name', String(255))
4444 review_rule_name = Column('review_rule_name', String(255))
4438 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4445 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4439 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4446 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4440 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4447 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
4441
4448
4442 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4449 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
4443 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4450 forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
4444 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4451 forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
4445 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4452 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
4446
4453
4447 rule_users = relationship('RepoReviewRuleUser')
4454 rule_users = relationship('RepoReviewRuleUser')
4448 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4455 rule_user_groups = relationship('RepoReviewRuleUserGroup')
4449
4456
4450 def _validate_pattern(self, value):
4457 def _validate_pattern(self, value):
4451 re.compile('^' + glob2re(value) + '$')
4458 re.compile('^' + glob2re(value) + '$')
4452
4459
4453 @hybrid_property
4460 @hybrid_property
4454 def source_branch_pattern(self):
4461 def source_branch_pattern(self):
4455 return self._branch_pattern or '*'
4462 return self._branch_pattern or '*'
4456
4463
4457 @source_branch_pattern.setter
4464 @source_branch_pattern.setter
4458 def source_branch_pattern(self, value):
4465 def source_branch_pattern(self, value):
4459 self._validate_pattern(value)
4466 self._validate_pattern(value)
4460 self._branch_pattern = value or '*'
4467 self._branch_pattern = value or '*'
4461
4468
4462 @hybrid_property
4469 @hybrid_property
4463 def target_branch_pattern(self):
4470 def target_branch_pattern(self):
4464 return self._target_branch_pattern or '*'
4471 return self._target_branch_pattern or '*'
4465
4472
4466 @target_branch_pattern.setter
4473 @target_branch_pattern.setter
4467 def target_branch_pattern(self, value):
4474 def target_branch_pattern(self, value):
4468 self._validate_pattern(value)
4475 self._validate_pattern(value)
4469 self._target_branch_pattern = value or '*'
4476 self._target_branch_pattern = value or '*'
4470
4477
4471 @hybrid_property
4478 @hybrid_property
4472 def file_pattern(self):
4479 def file_pattern(self):
4473 return self._file_pattern or '*'
4480 return self._file_pattern or '*'
4474
4481
4475 @file_pattern.setter
4482 @file_pattern.setter
4476 def file_pattern(self, value):
4483 def file_pattern(self, value):
4477 self._validate_pattern(value)
4484 self._validate_pattern(value)
4478 self._file_pattern = value or '*'
4485 self._file_pattern = value or '*'
4479
4486
4480 def matches(self, source_branch, target_branch, files_changed):
4487 def matches(self, source_branch, target_branch, files_changed):
4481 """
4488 """
4482 Check if this review rule matches a branch/files in a pull request
4489 Check if this review rule matches a branch/files in a pull request
4483
4490
4484 :param source_branch: source branch name for the commit
4491 :param source_branch: source branch name for the commit
4485 :param target_branch: target branch name for the commit
4492 :param target_branch: target branch name for the commit
4486 :param files_changed: list of file paths changed in the pull request
4493 :param files_changed: list of file paths changed in the pull request
4487 """
4494 """
4488
4495
4489 source_branch = source_branch or ''
4496 source_branch = source_branch or ''
4490 target_branch = target_branch or ''
4497 target_branch = target_branch or ''
4491 files_changed = files_changed or []
4498 files_changed = files_changed or []
4492
4499
4493 branch_matches = True
4500 branch_matches = True
4494 if source_branch or target_branch:
4501 if source_branch or target_branch:
4495 if self.source_branch_pattern == '*':
4502 if self.source_branch_pattern == '*':
4496 source_branch_match = True
4503 source_branch_match = True
4497 else:
4504 else:
4498 if self.source_branch_pattern.startswith('re:'):
4505 if self.source_branch_pattern.startswith('re:'):
4499 source_pattern = self.source_branch_pattern[3:]
4506 source_pattern = self.source_branch_pattern[3:]
4500 else:
4507 else:
4501 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4508 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
4502 source_branch_regex = re.compile(source_pattern)
4509 source_branch_regex = re.compile(source_pattern)
4503 source_branch_match = bool(source_branch_regex.search(source_branch))
4510 source_branch_match = bool(source_branch_regex.search(source_branch))
4504 if self.target_branch_pattern == '*':
4511 if self.target_branch_pattern == '*':
4505 target_branch_match = True
4512 target_branch_match = True
4506 else:
4513 else:
4507 if self.target_branch_pattern.startswith('re:'):
4514 if self.target_branch_pattern.startswith('re:'):
4508 target_pattern = self.target_branch_pattern[3:]
4515 target_pattern = self.target_branch_pattern[3:]
4509 else:
4516 else:
4510 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4517 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
4511 target_branch_regex = re.compile(target_pattern)
4518 target_branch_regex = re.compile(target_pattern)
4512 target_branch_match = bool(target_branch_regex.search(target_branch))
4519 target_branch_match = bool(target_branch_regex.search(target_branch))
4513
4520
4514 branch_matches = source_branch_match and target_branch_match
4521 branch_matches = source_branch_match and target_branch_match
4515
4522
4516 files_matches = True
4523 files_matches = True
4517 if self.file_pattern != '*':
4524 if self.file_pattern != '*':
4518 files_matches = False
4525 files_matches = False
4519 if self.file_pattern.startswith('re:'):
4526 if self.file_pattern.startswith('re:'):
4520 file_pattern = self.file_pattern[3:]
4527 file_pattern = self.file_pattern[3:]
4521 else:
4528 else:
4522 file_pattern = glob2re(self.file_pattern)
4529 file_pattern = glob2re(self.file_pattern)
4523 file_regex = re.compile(file_pattern)
4530 file_regex = re.compile(file_pattern)
4524 for filename in files_changed:
4531 for filename in files_changed:
4525 if file_regex.search(filename):
4532 if file_regex.search(filename):
4526 files_matches = True
4533 files_matches = True
4527 break
4534 break
4528
4535
4529 return branch_matches and files_matches
4536 return branch_matches and files_matches
4530
4537
4531 @property
4538 @property
4532 def review_users(self):
4539 def review_users(self):
4533 """ Returns the users which this rule applies to """
4540 """ Returns the users which this rule applies to """
4534
4541
4535 users = collections.OrderedDict()
4542 users = collections.OrderedDict()
4536
4543
4537 for rule_user in self.rule_users:
4544 for rule_user in self.rule_users:
4538 if rule_user.user.active:
4545 if rule_user.user.active:
4539 if rule_user.user not in users:
4546 if rule_user.user not in users:
4540 users[rule_user.user.username] = {
4547 users[rule_user.user.username] = {
4541 'user': rule_user.user,
4548 'user': rule_user.user,
4542 'source': 'user',
4549 'source': 'user',
4543 'source_data': {},
4550 'source_data': {},
4544 'data': rule_user.rule_data()
4551 'data': rule_user.rule_data()
4545 }
4552 }
4546
4553
4547 for rule_user_group in self.rule_user_groups:
4554 for rule_user_group in self.rule_user_groups:
4548 source_data = {
4555 source_data = {
4549 'user_group_id': rule_user_group.users_group.users_group_id,
4556 'user_group_id': rule_user_group.users_group.users_group_id,
4550 'name': rule_user_group.users_group.users_group_name,
4557 'name': rule_user_group.users_group.users_group_name,
4551 'members': len(rule_user_group.users_group.members)
4558 'members': len(rule_user_group.users_group.members)
4552 }
4559 }
4553 for member in rule_user_group.users_group.members:
4560 for member in rule_user_group.users_group.members:
4554 if member.user.active:
4561 if member.user.active:
4555 key = member.user.username
4562 key = member.user.username
4556 if key in users:
4563 if key in users:
4557 # skip this member as we have him already
4564 # skip this member as we have him already
4558 # this prevents from override the "first" matched
4565 # this prevents from override the "first" matched
4559 # users with duplicates in multiple groups
4566 # users with duplicates in multiple groups
4560 continue
4567 continue
4561
4568
4562 users[key] = {
4569 users[key] = {
4563 'user': member.user,
4570 'user': member.user,
4564 'source': 'user_group',
4571 'source': 'user_group',
4565 'source_data': source_data,
4572 'source_data': source_data,
4566 'data': rule_user_group.rule_data()
4573 'data': rule_user_group.rule_data()
4567 }
4574 }
4568
4575
4569 return users
4576 return users
4570
4577
4571 def user_group_vote_rule(self, user_id):
4578 def user_group_vote_rule(self, user_id):
4572
4579
4573 rules = []
4580 rules = []
4574 if not self.rule_user_groups:
4581 if not self.rule_user_groups:
4575 return rules
4582 return rules
4576
4583
4577 for user_group in self.rule_user_groups:
4584 for user_group in self.rule_user_groups:
4578 user_group_members = [x.user_id for x in user_group.users_group.members]
4585 user_group_members = [x.user_id for x in user_group.users_group.members]
4579 if user_id in user_group_members:
4586 if user_id in user_group_members:
4580 rules.append(user_group)
4587 rules.append(user_group)
4581 return rules
4588 return rules
4582
4589
4583 def __repr__(self):
4590 def __repr__(self):
4584 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4591 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
4585 self.repo_review_rule_id, self.repo)
4592 self.repo_review_rule_id, self.repo)
4586
4593
4587
4594
4588 class ScheduleEntry(Base, BaseModel):
4595 class ScheduleEntry(Base, BaseModel):
4589 __tablename__ = 'schedule_entries'
4596 __tablename__ = 'schedule_entries'
4590 __table_args__ = (
4597 __table_args__ = (
4591 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4598 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
4592 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4599 UniqueConstraint('task_uid', name='s_task_uid_idx'),
4593 base_table_args,
4600 base_table_args,
4594 )
4601 )
4595
4602
4596 schedule_types = ['crontab', 'timedelta', 'integer']
4603 schedule_types = ['crontab', 'timedelta', 'integer']
4597 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4604 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
4598
4605
4599 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4606 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
4600 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4607 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
4601 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4608 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
4602
4609
4603 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4610 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
4604 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4611 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
4605
4612
4606 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4613 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
4607 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4614 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
4608
4615
4609 # task
4616 # task
4610 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4617 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
4611 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4618 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
4612 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4619 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
4613 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4620 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
4614
4621
4615 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4622 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4616 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4623 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
4617
4624
4618 @hybrid_property
4625 @hybrid_property
4619 def schedule_type(self):
4626 def schedule_type(self):
4620 return self._schedule_type
4627 return self._schedule_type
4621
4628
4622 @schedule_type.setter
4629 @schedule_type.setter
4623 def schedule_type(self, val):
4630 def schedule_type(self, val):
4624 if val not in self.schedule_types:
4631 if val not in self.schedule_types:
4625 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4632 raise ValueError('Value must be on of `{}` and got `{}`'.format(
4626 val, self.schedule_type))
4633 val, self.schedule_type))
4627
4634
4628 self._schedule_type = val
4635 self._schedule_type = val
4629
4636
4630 @classmethod
4637 @classmethod
4631 def get_uid(cls, obj):
4638 def get_uid(cls, obj):
4632 args = obj.task_args
4639 args = obj.task_args
4633 kwargs = obj.task_kwargs
4640 kwargs = obj.task_kwargs
4634 if isinstance(args, JsonRaw):
4641 if isinstance(args, JsonRaw):
4635 try:
4642 try:
4636 args = json.loads(args)
4643 args = json.loads(args)
4637 except ValueError:
4644 except ValueError:
4638 args = tuple()
4645 args = tuple()
4639
4646
4640 if isinstance(kwargs, JsonRaw):
4647 if isinstance(kwargs, JsonRaw):
4641 try:
4648 try:
4642 kwargs = json.loads(kwargs)
4649 kwargs = json.loads(kwargs)
4643 except ValueError:
4650 except ValueError:
4644 kwargs = dict()
4651 kwargs = dict()
4645
4652
4646 dot_notation = obj.task_dot_notation
4653 dot_notation = obj.task_dot_notation
4647 val = '.'.join(map(safe_str, [
4654 val = '.'.join(map(safe_str, [
4648 sorted(dot_notation), args, sorted(kwargs.items())]))
4655 sorted(dot_notation), args, sorted(kwargs.items())]))
4649 return hashlib.sha1(val).hexdigest()
4656 return hashlib.sha1(val).hexdigest()
4650
4657
4651 @classmethod
4658 @classmethod
4652 def get_by_schedule_name(cls, schedule_name):
4659 def get_by_schedule_name(cls, schedule_name):
4653 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4660 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
4654
4661
4655 @classmethod
4662 @classmethod
4656 def get_by_schedule_id(cls, schedule_id):
4663 def get_by_schedule_id(cls, schedule_id):
4657 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4664 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
4658
4665
4659 @property
4666 @property
4660 def task(self):
4667 def task(self):
4661 return self.task_dot_notation
4668 return self.task_dot_notation
4662
4669
4663 @property
4670 @property
4664 def schedule(self):
4671 def schedule(self):
4665 from rhodecode.lib.celerylib.utils import raw_2_schedule
4672 from rhodecode.lib.celerylib.utils import raw_2_schedule
4666 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4673 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
4667 return schedule
4674 return schedule
4668
4675
4669 @property
4676 @property
4670 def args(self):
4677 def args(self):
4671 try:
4678 try:
4672 return list(self.task_args or [])
4679 return list(self.task_args or [])
4673 except ValueError:
4680 except ValueError:
4674 return list()
4681 return list()
4675
4682
4676 @property
4683 @property
4677 def kwargs(self):
4684 def kwargs(self):
4678 try:
4685 try:
4679 return dict(self.task_kwargs or {})
4686 return dict(self.task_kwargs or {})
4680 except ValueError:
4687 except ValueError:
4681 return dict()
4688 return dict()
4682
4689
4683 def _as_raw(self, val):
4690 def _as_raw(self, val):
4684 if hasattr(val, 'de_coerce'):
4691 if hasattr(val, 'de_coerce'):
4685 val = val.de_coerce()
4692 val = val.de_coerce()
4686 if val:
4693 if val:
4687 val = json.dumps(val)
4694 val = json.dumps(val)
4688
4695
4689 return val
4696 return val
4690
4697
4691 @property
4698 @property
4692 def schedule_definition_raw(self):
4699 def schedule_definition_raw(self):
4693 return self._as_raw(self.schedule_definition)
4700 return self._as_raw(self.schedule_definition)
4694
4701
4695 @property
4702 @property
4696 def args_raw(self):
4703 def args_raw(self):
4697 return self._as_raw(self.task_args)
4704 return self._as_raw(self.task_args)
4698
4705
4699 @property
4706 @property
4700 def kwargs_raw(self):
4707 def kwargs_raw(self):
4701 return self._as_raw(self.task_kwargs)
4708 return self._as_raw(self.task_kwargs)
4702
4709
4703 def __repr__(self):
4710 def __repr__(self):
4704 return '<DB:ScheduleEntry({}:{})>'.format(
4711 return '<DB:ScheduleEntry({}:{})>'.format(
4705 self.schedule_entry_id, self.schedule_name)
4712 self.schedule_entry_id, self.schedule_name)
4706
4713
4707
4714
4708 @event.listens_for(ScheduleEntry, 'before_update')
4715 @event.listens_for(ScheduleEntry, 'before_update')
4709 def update_task_uid(mapper, connection, target):
4716 def update_task_uid(mapper, connection, target):
4710 target.task_uid = ScheduleEntry.get_uid(target)
4717 target.task_uid = ScheduleEntry.get_uid(target)
4711
4718
4712
4719
4713 @event.listens_for(ScheduleEntry, 'before_insert')
4720 @event.listens_for(ScheduleEntry, 'before_insert')
4714 def set_task_uid(mapper, connection, target):
4721 def set_task_uid(mapper, connection, target):
4715 target.task_uid = ScheduleEntry.get_uid(target)
4722 target.task_uid = ScheduleEntry.get_uid(target)
4716
4723
4717
4724
4718 class _BaseBranchPerms(BaseModel):
4725 class _BaseBranchPerms(BaseModel):
4719 @classmethod
4726 @classmethod
4720 def compute_hash(cls, value):
4727 def compute_hash(cls, value):
4721 return sha1_safe(value)
4728 return sha1_safe(value)
4722
4729
4723 @hybrid_property
4730 @hybrid_property
4724 def branch_pattern(self):
4731 def branch_pattern(self):
4725 return self._branch_pattern or '*'
4732 return self._branch_pattern or '*'
4726
4733
4727 @hybrid_property
4734 @hybrid_property
4728 def branch_hash(self):
4735 def branch_hash(self):
4729 return self._branch_hash
4736 return self._branch_hash
4730
4737
4731 def _validate_glob(self, value):
4738 def _validate_glob(self, value):
4732 re.compile('^' + glob2re(value) + '$')
4739 re.compile('^' + glob2re(value) + '$')
4733
4740
4734 @branch_pattern.setter
4741 @branch_pattern.setter
4735 def branch_pattern(self, value):
4742 def branch_pattern(self, value):
4736 self._validate_glob(value)
4743 self._validate_glob(value)
4737 self._branch_pattern = value or '*'
4744 self._branch_pattern = value or '*'
4738 # set the Hash when setting the branch pattern
4745 # set the Hash when setting the branch pattern
4739 self._branch_hash = self.compute_hash(self._branch_pattern)
4746 self._branch_hash = self.compute_hash(self._branch_pattern)
4740
4747
4741 def matches(self, branch):
4748 def matches(self, branch):
4742 """
4749 """
4743 Check if this the branch matches entry
4750 Check if this the branch matches entry
4744
4751
4745 :param branch: branch name for the commit
4752 :param branch: branch name for the commit
4746 """
4753 """
4747
4754
4748 branch = branch or ''
4755 branch = branch or ''
4749
4756
4750 branch_matches = True
4757 branch_matches = True
4751 if branch:
4758 if branch:
4752 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4759 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
4753 branch_matches = bool(branch_regex.search(branch))
4760 branch_matches = bool(branch_regex.search(branch))
4754
4761
4755 return branch_matches
4762 return branch_matches
4756
4763
4757
4764
4758 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4765 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
4759 __tablename__ = 'user_to_repo_branch_permissions'
4766 __tablename__ = 'user_to_repo_branch_permissions'
4760 __table_args__ = (
4767 __table_args__ = (
4761 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4768 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4762 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4769 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4763 )
4770 )
4764
4771
4765 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4772 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4766
4773
4767 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4774 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4768 repo = relationship('Repository', backref='user_branch_perms')
4775 repo = relationship('Repository', backref='user_branch_perms')
4769
4776
4770 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4777 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4771 permission = relationship('Permission')
4778 permission = relationship('Permission')
4772
4779
4773 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4780 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
4774 user_repo_to_perm = relationship('UserRepoToPerm')
4781 user_repo_to_perm = relationship('UserRepoToPerm')
4775
4782
4776 rule_order = Column('rule_order', Integer(), nullable=False)
4783 rule_order = Column('rule_order', Integer(), nullable=False)
4777 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4784 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4778 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4785 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4779
4786
4780 def __unicode__(self):
4787 def __unicode__(self):
4781 return u'<UserBranchPermission(%s => %r)>' % (
4788 return u'<UserBranchPermission(%s => %r)>' % (
4782 self.user_repo_to_perm, self.branch_pattern)
4789 self.user_repo_to_perm, self.branch_pattern)
4783
4790
4784
4791
4785 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4792 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
4786 __tablename__ = 'user_group_to_repo_branch_permissions'
4793 __tablename__ = 'user_group_to_repo_branch_permissions'
4787 __table_args__ = (
4794 __table_args__ = (
4788 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4795 {'extend_existing': True, 'mysql_engine': 'InnoDB',
4789 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4796 'mysql_charset': 'utf8', 'sqlite_autoincrement': True,}
4790 )
4797 )
4791
4798
4792 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4799 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
4793
4800
4794 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4801 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
4795 repo = relationship('Repository', backref='user_group_branch_perms')
4802 repo = relationship('Repository', backref='user_group_branch_perms')
4796
4803
4797 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4804 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
4798 permission = relationship('Permission')
4805 permission = relationship('Permission')
4799
4806
4800 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4807 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('users_group_repo_to_perm.users_group_to_perm_id'), nullable=False, unique=None, default=None)
4801 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4808 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
4802
4809
4803 rule_order = Column('rule_order', Integer(), nullable=False)
4810 rule_order = Column('rule_order', Integer(), nullable=False)
4804 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4811 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
4805 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4812 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
4806
4813
4807 def __unicode__(self):
4814 def __unicode__(self):
4808 return u'<UserBranchPermission(%s => %r)>' % (
4815 return u'<UserBranchPermission(%s => %r)>' % (
4809 self.user_group_repo_to_perm, self.branch_pattern)
4816 self.user_group_repo_to_perm, self.branch_pattern)
4810
4817
4811
4818
4812 class UserBookmark(Base, BaseModel):
4819 class UserBookmark(Base, BaseModel):
4813 __tablename__ = 'user_bookmarks'
4820 __tablename__ = 'user_bookmarks'
4814 __table_args__ = (
4821 __table_args__ = (
4815 UniqueConstraint('user_id', 'bookmark_repo_id'),
4822 UniqueConstraint('user_id', 'bookmark_repo_id'),
4816 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4823 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
4817 UniqueConstraint('user_id', 'bookmark_position'),
4824 UniqueConstraint('user_id', 'bookmark_position'),
4818 base_table_args
4825 base_table_args
4819 )
4826 )
4820
4827
4821 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4828 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
4822 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4829 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
4823 position = Column("bookmark_position", Integer(), nullable=False)
4830 position = Column("bookmark_position", Integer(), nullable=False)
4824 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4831 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
4825 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4832 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
4826 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4833 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4827
4834
4828 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4835 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
4829 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4836 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
4830
4837
4831 user = relationship("User")
4838 user = relationship("User")
4832
4839
4833 repository = relationship("Repository")
4840 repository = relationship("Repository")
4834 repository_group = relationship("RepoGroup")
4841 repository_group = relationship("RepoGroup")
4835
4842
4836 @classmethod
4843 @classmethod
4837 def get_by_position_for_user(cls, position, user_id):
4844 def get_by_position_for_user(cls, position, user_id):
4838 return cls.query() \
4845 return cls.query() \
4839 .filter(UserBookmark.user_id == user_id) \
4846 .filter(UserBookmark.user_id == user_id) \
4840 .filter(UserBookmark.position == position).scalar()
4847 .filter(UserBookmark.position == position).scalar()
4841
4848
4842 @classmethod
4849 @classmethod
4843 def get_bookmarks_for_user(cls, user_id):
4850 def get_bookmarks_for_user(cls, user_id):
4844 return cls.query() \
4851 return cls.query() \
4845 .filter(UserBookmark.user_id == user_id) \
4852 .filter(UserBookmark.user_id == user_id) \
4846 .options(joinedload(UserBookmark.repository)) \
4853 .options(joinedload(UserBookmark.repository)) \
4847 .options(joinedload(UserBookmark.repository_group)) \
4854 .options(joinedload(UserBookmark.repository_group)) \
4848 .order_by(UserBookmark.position.asc()) \
4855 .order_by(UserBookmark.position.asc()) \
4849 .all()
4856 .all()
4850
4857
4851 def __unicode__(self):
4858 def __unicode__(self):
4852 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
4859 return u'<UserBookmark(%d @ %r)>' % (self.position, self.redirect_url)
4853
4860
4854
4861
4855 class FileStore(Base, BaseModel):
4862 class FileStore(Base, BaseModel):
4856 __tablename__ = 'file_store'
4863 __tablename__ = 'file_store'
4857 __table_args__ = (
4864 __table_args__ = (
4858 base_table_args
4865 base_table_args
4859 )
4866 )
4860
4867
4861 file_store_id = Column('file_store_id', Integer(), primary_key=True)
4868 file_store_id = Column('file_store_id', Integer(), primary_key=True)
4862 file_uid = Column('file_uid', String(1024), nullable=False)
4869 file_uid = Column('file_uid', String(1024), nullable=False)
4863 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
4870 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
4864 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
4871 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
4865 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
4872 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
4866
4873
4867 # sha256 hash
4874 # sha256 hash
4868 file_hash = Column('file_hash', String(512), nullable=False)
4875 file_hash = Column('file_hash', String(512), nullable=False)
4869 file_size = Column('file_size', Integer(), nullable=False)
4876 file_size = Column('file_size', Integer(), nullable=False)
4870
4877
4871 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4878 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4872 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
4879 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
4873 accessed_count = Column('accessed_count', Integer(), default=0)
4880 accessed_count = Column('accessed_count', Integer(), default=0)
4874
4881
4875 enabled = Column('enabled', Boolean(), nullable=False, default=True)
4882 enabled = Column('enabled', Boolean(), nullable=False, default=True)
4876
4883
4877 # if repo/repo_group reference is set, check for permissions
4884 # if repo/repo_group reference is set, check for permissions
4878 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
4885 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
4879
4886
4880 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
4887 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
4881 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
4888 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
4882
4889
4883 # scope limited to user, which requester have access to
4890 # scope limited to user, which requester have access to
4884 scope_user_id = Column(
4891 scope_user_id = Column(
4885 'scope_user_id', Integer(), ForeignKey('users.user_id'),
4892 'scope_user_id', Integer(), ForeignKey('users.user_id'),
4886 nullable=True, unique=None, default=None)
4893 nullable=True, unique=None, default=None)
4887 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
4894 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
4888
4895
4889 # scope limited to user group, which requester have access to
4896 # scope limited to user group, which requester have access to
4890 scope_user_group_id = Column(
4897 scope_user_group_id = Column(
4891 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
4898 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
4892 nullable=True, unique=None, default=None)
4899 nullable=True, unique=None, default=None)
4893 user_group = relationship('UserGroup', lazy='joined')
4900 user_group = relationship('UserGroup', lazy='joined')
4894
4901
4895 # scope limited to repo, which requester have access to
4902 # scope limited to repo, which requester have access to
4896 scope_repo_id = Column(
4903 scope_repo_id = Column(
4897 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4904 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4898 nullable=True, unique=None, default=None)
4905 nullable=True, unique=None, default=None)
4899 repo = relationship('Repository', lazy='joined')
4906 repo = relationship('Repository', lazy='joined')
4900
4907
4901 # scope limited to repo group, which requester have access to
4908 # scope limited to repo group, which requester have access to
4902 scope_repo_group_id = Column(
4909 scope_repo_group_id = Column(
4903 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
4910 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
4904 nullable=True, unique=None, default=None)
4911 nullable=True, unique=None, default=None)
4905 repo_group = relationship('RepoGroup', lazy='joined')
4912 repo_group = relationship('RepoGroup', lazy='joined')
4906
4913
4907 @classmethod
4914 @classmethod
4908 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
4915 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
4909 file_description='', enabled=True, check_acl=True,
4916 file_description='', enabled=True, check_acl=True,
4910 user_id=None, scope_repo_id=None, scope_repo_group_id=None):
4917 user_id=None, scope_repo_id=None, scope_repo_group_id=None):
4911
4918
4912 store_entry = FileStore()
4919 store_entry = FileStore()
4913 store_entry.file_uid = file_uid
4920 store_entry.file_uid = file_uid
4914 store_entry.file_display_name = file_display_name
4921 store_entry.file_display_name = file_display_name
4915 store_entry.file_org_name = filename
4922 store_entry.file_org_name = filename
4916 store_entry.file_size = file_size
4923 store_entry.file_size = file_size
4917 store_entry.file_hash = file_hash
4924 store_entry.file_hash = file_hash
4918 store_entry.file_description = file_description
4925 store_entry.file_description = file_description
4919
4926
4920 store_entry.check_acl = check_acl
4927 store_entry.check_acl = check_acl
4921 store_entry.enabled = enabled
4928 store_entry.enabled = enabled
4922
4929
4923 store_entry.user_id = user_id
4930 store_entry.user_id = user_id
4924 store_entry.scope_repo_id = scope_repo_id
4931 store_entry.scope_repo_id = scope_repo_id
4925 store_entry.scope_repo_group_id = scope_repo_group_id
4932 store_entry.scope_repo_group_id = scope_repo_group_id
4926 return store_entry
4933 return store_entry
4927
4934
4928 @classmethod
4935 @classmethod
4929 def bump_access_counter(cls, file_uid, commit=True):
4936 def bump_access_counter(cls, file_uid, commit=True):
4930 FileStore().query()\
4937 FileStore().query()\
4931 .filter(FileStore.file_uid == file_uid)\
4938 .filter(FileStore.file_uid == file_uid)\
4932 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
4939 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
4933 FileStore.accessed_on: datetime.datetime.now()})
4940 FileStore.accessed_on: datetime.datetime.now()})
4934 if commit:
4941 if commit:
4935 Session().commit()
4942 Session().commit()
4936
4943
4937 def __repr__(self):
4944 def __repr__(self):
4938 return '<FileStore({})>'.format(self.file_store_id)
4945 return '<FileStore({})>'.format(self.file_store_id)
4939
4946
4940
4947
4941 class DbMigrateVersion(Base, BaseModel):
4948 class DbMigrateVersion(Base, BaseModel):
4942 __tablename__ = 'db_migrate_version'
4949 __tablename__ = 'db_migrate_version'
4943 __table_args__ = (
4950 __table_args__ = (
4944 base_table_args,
4951 base_table_args,
4945 )
4952 )
4946
4953
4947 repository_id = Column('repository_id', String(250), primary_key=True)
4954 repository_id = Column('repository_id', String(250), primary_key=True)
4948 repository_path = Column('repository_path', Text)
4955 repository_path = Column('repository_path', Text)
4949 version = Column('version', Integer)
4956 version = Column('version', Integer)
4950
4957
4951 @classmethod
4958 @classmethod
4952 def set_version(cls, version):
4959 def set_version(cls, version):
4953 """
4960 """
4954 Helper for forcing a different version, usually for debugging purposes via ishell.
4961 Helper for forcing a different version, usually for debugging purposes via ishell.
4955 """
4962 """
4956 ver = DbMigrateVersion.query().first()
4963 ver = DbMigrateVersion.query().first()
4957 ver.version = version
4964 ver.version = version
4958 Session().commit()
4965 Session().commit()
4959
4966
4960
4967
4961 class DbSession(Base, BaseModel):
4968 class DbSession(Base, BaseModel):
4962 __tablename__ = 'db_session'
4969 __tablename__ = 'db_session'
4963 __table_args__ = (
4970 __table_args__ = (
4964 base_table_args,
4971 base_table_args,
4965 )
4972 )
4966
4973
4967 def __repr__(self):
4974 def __repr__(self):
4968 return '<DB:DbSession({})>'.format(self.id)
4975 return '<DB:DbSession({})>'.format(self.id)
4969
4976
4970 id = Column('id', Integer())
4977 id = Column('id', Integer())
4971 namespace = Column('namespace', String(255), primary_key=True)
4978 namespace = Column('namespace', String(255), primary_key=True)
4972 accessed = Column('accessed', DateTime, nullable=False)
4979 accessed = Column('accessed', DateTime, nullable=False)
4973 created = Column('created', DateTime, nullable=False)
4980 created = Column('created', DateTime, nullable=False)
4974 data = Column('data', PickleType, nullable=False)
4981 data = Column('data', PickleType, nullable=False)
@@ -1,255 +1,256 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2019 RhodeCode GmbH
3 # Copyright (C) 2013-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 gist model for RhodeCode
22 gist model for RhodeCode
23 """
23 """
24
24
25 import os
25 import os
26 import time
26 import time
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from pyramid.threadlocal import get_current_request
31 from pyramid.threadlocal import get_current_request
32
32
33 from rhodecode.lib.utils2 import (
33 from rhodecode.lib.utils2 import (
34 safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
34 safe_unicode, unique_id, safe_int, time_to_datetime, AttributeDict)
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.ext_json import json
36 from rhodecode.lib.vcs import VCSError
36 from rhodecode.lib.vcs import VCSError
37 from rhodecode.model import BaseModel
37 from rhodecode.model import BaseModel
38 from rhodecode.model.db import Gist
38 from rhodecode.model.db import Gist
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.scm import ScmModel
40 from rhodecode.model.scm import ScmModel
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44 GIST_STORE_LOC = '.rc_gist_store'
44 GIST_STORE_LOC = '.rc_gist_store'
45 GIST_METADATA_FILE = '.rc_gist_metadata'
45 GIST_METADATA_FILE = '.rc_gist_metadata'
46
46
47
47
48 class GistModel(BaseModel):
48 class GistModel(BaseModel):
49 cls = Gist
49 cls = Gist
50 vcs_backend = 'hg'
50
51
51 def _get_gist(self, gist):
52 def _get_gist(self, gist):
52 """
53 """
53 Helper method to get gist by ID, or gist_access_id as a fallback
54 Helper method to get gist by ID, or gist_access_id as a fallback
54
55
55 :param gist: GistID, gist_access_id, or Gist instance
56 :param gist: GistID, gist_access_id, or Gist instance
56 """
57 """
57 return self._get_instance(Gist, gist, callback=Gist.get_by_access_id)
58 return self._get_instance(Gist, gist, callback=Gist.get_by_access_id)
58
59
59 def __delete_gist(self, gist):
60 def __delete_gist(self, gist):
60 """
61 """
61 removes gist from filesystem
62 removes gist from filesystem
62
63
63 :param gist: gist object
64 :param gist: gist object
64 """
65 """
65 root_path = RepoModel().repos_path
66 root_path = RepoModel().repos_path
66 rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id)
67 rm_path = os.path.join(root_path, GIST_STORE_LOC, gist.gist_access_id)
67 log.info("Removing %s", rm_path)
68 log.info("Removing %s", rm_path)
68 shutil.rmtree(rm_path)
69 shutil.rmtree(rm_path)
69
70
70 def _store_metadata(self, repo, gist_id, gist_access_id, user_id, username,
71 def _store_metadata(self, repo, gist_id, gist_access_id, user_id, username,
71 gist_type, gist_expires, gist_acl_level):
72 gist_type, gist_expires, gist_acl_level):
72 """
73 """
73 store metadata inside the gist repo, this can be later used for imports
74 store metadata inside the gist repo, this can be later used for imports
74 or gist identification. Currently we use this inside RhodeCode tools
75 or gist identification. Currently we use this inside RhodeCode tools
75 to do cleanup of gists that are in storage but not in database.
76 to do cleanup of gists that are in storage but not in database.
76 """
77 """
77 metadata = {
78 metadata = {
78 'metadata_version': '2',
79 'metadata_version': '2',
79 'gist_db_id': gist_id,
80 'gist_db_id': gist_id,
80 'gist_access_id': gist_access_id,
81 'gist_access_id': gist_access_id,
81 'gist_owner_id': user_id,
82 'gist_owner_id': user_id,
82 'gist_owner_username': username,
83 'gist_owner_username': username,
83 'gist_type': gist_type,
84 'gist_type': gist_type,
84 'gist_expires': gist_expires,
85 'gist_expires': gist_expires,
85 'gist_updated': time.time(),
86 'gist_updated': time.time(),
86 'gist_acl_level': gist_acl_level,
87 'gist_acl_level': gist_acl_level,
87 }
88 }
88 metadata_file = os.path.join(repo.path, '.hg', GIST_METADATA_FILE)
89 metadata_file = os.path.join(repo.path, '.hg', GIST_METADATA_FILE)
89 with open(metadata_file, 'wb') as f:
90 with open(metadata_file, 'wb') as f:
90 f.write(json.dumps(metadata))
91 f.write(json.dumps(metadata))
91
92
92 def get_gist(self, gist):
93 def get_gist(self, gist):
93 return self._get_gist(gist)
94 return self._get_gist(gist)
94
95
95 def get_gist_files(self, gist_access_id, revision=None):
96 def get_gist_files(self, gist_access_id, revision=None):
96 """
97 """
97 Get files for given gist
98 Get files for given gist
98
99
99 :param gist_access_id:
100 :param gist_access_id:
100 """
101 """
101 repo = Gist.get_by_access_id(gist_access_id)
102 repo = Gist.get_by_access_id(gist_access_id)
102 vcs_repo = repo.scm_instance()
103 vcs_repo = repo.scm_instance()
103 if not vcs_repo:
104 if not vcs_repo:
104 raise VCSError('Failed to load gist repository for {}'.format(repo))
105 raise VCSError('Failed to load gist repository for {}'.format(repo))
105
106
106 commit = vcs_repo.get_commit(commit_id=revision)
107 commit = vcs_repo.get_commit(commit_id=revision)
107 return commit, [n for n in commit.get_node('/')]
108 return commit, [n for n in commit.get_node('/')]
108
109
109 def create(self, description, owner, gist_mapping,
110 def create(self, description, owner, gist_mapping,
110 gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None,
111 gist_type=Gist.GIST_PUBLIC, lifetime=-1, gist_id=None,
111 gist_acl_level=Gist.ACL_LEVEL_PRIVATE):
112 gist_acl_level=Gist.ACL_LEVEL_PRIVATE):
112 """
113 """
113 Create a gist
114 Create a gist
114
115
115 :param description: description of the gist
116 :param description: description of the gist
116 :param owner: user who created this gist
117 :param owner: user who created this gist
117 :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}]
118 :param gist_mapping: mapping [{'filename': 'file1.txt', 'content': content}, ...}]
118 :param gist_type: type of gist private/public
119 :param gist_type: type of gist private/public
119 :param lifetime: in minutes, -1 == forever
120 :param lifetime: in minutes, -1 == forever
120 :param gist_acl_level: acl level for this gist
121 :param gist_acl_level: acl level for this gist
121 """
122 """
122 owner = self._get_user(owner)
123 owner = self._get_user(owner)
123 gist_id = safe_unicode(gist_id or unique_id(20))
124 gist_id = safe_unicode(gist_id or unique_id(20))
124 lifetime = safe_int(lifetime, -1)
125 lifetime = safe_int(lifetime, -1)
125 gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
126 gist_expires = time.time() + (lifetime * 60) if lifetime != -1 else -1
126 expiration = (time_to_datetime(gist_expires)
127 expiration = (time_to_datetime(gist_expires)
127 if gist_expires != -1 else 'forever')
128 if gist_expires != -1 else 'forever')
128 log.debug('set GIST expiration date to: %s', expiration)
129 log.debug('set GIST expiration date to: %s', expiration)
129 # create the Database version
130 # create the Database version
130 gist = Gist()
131 gist = Gist()
131 gist.gist_description = description
132 gist.gist_description = description
132 gist.gist_access_id = gist_id
133 gist.gist_access_id = gist_id
133 gist.gist_owner = owner.user_id
134 gist.gist_owner = owner.user_id
134 gist.gist_expires = gist_expires
135 gist.gist_expires = gist_expires
135 gist.gist_type = safe_unicode(gist_type)
136 gist.gist_type = safe_unicode(gist_type)
136 gist.acl_level = gist_acl_level
137 gist.acl_level = gist_acl_level
137 self.sa.add(gist)
138 self.sa.add(gist)
138 self.sa.flush()
139 self.sa.flush()
139 if gist_type == Gist.GIST_PUBLIC:
140 if gist_type == Gist.GIST_PUBLIC:
140 # use DB ID for easy to use GIST ID
141 # use DB ID for easy to use GIST ID
141 gist_id = safe_unicode(gist.gist_id)
142 gist_id = safe_unicode(gist.gist_id)
142 gist.gist_access_id = gist_id
143 gist.gist_access_id = gist_id
143 self.sa.add(gist)
144 self.sa.add(gist)
144
145
145 gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
146 gist_repo_path = os.path.join(GIST_STORE_LOC, gist_id)
146 log.debug('Creating new %s GIST repo in %s', gist_type, gist_repo_path)
147 log.debug('Creating new %s GIST repo in %s', gist_type, gist_repo_path)
147 repo = RepoModel()._create_filesystem_repo(
148 repo = RepoModel()._create_filesystem_repo(
148 repo_name=gist_id, repo_type='hg', repo_group=GIST_STORE_LOC,
149 repo_name=gist_id, repo_type=self.vcs_backend, repo_group=GIST_STORE_LOC,
149 use_global_config=True)
150 use_global_config=True)
150
151
151 # now create single multifile commit
152 # now create single multifile commit
152 message = 'added file'
153 message = 'added file'
153 message += 's: ' if len(gist_mapping) > 1 else ': '
154 message += 's: ' if len(gist_mapping) > 1 else ': '
154 message += ', '.join([x for x in gist_mapping])
155 message += ', '.join([x for x in gist_mapping])
155
156
156 # fake RhodeCode Repository object
157 # fake RhodeCode Repository object
157 fake_repo = AttributeDict({
158 fake_repo = AttributeDict({
158 'repo_name': gist_repo_path,
159 'repo_name': gist_repo_path,
159 'scm_instance': lambda *args, **kwargs: repo,
160 'scm_instance': lambda *args, **kwargs: repo,
160 })
161 })
161
162
162 ScmModel().create_nodes(
163 ScmModel().create_nodes(
163 user=owner.user_id, repo=fake_repo,
164 user=owner.user_id, repo=fake_repo,
164 message=message,
165 message=message,
165 nodes=gist_mapping,
166 nodes=gist_mapping,
166 trigger_push_hook=False
167 trigger_push_hook=False
167 )
168 )
168
169
169 self._store_metadata(repo, gist.gist_id, gist.gist_access_id,
170 self._store_metadata(repo, gist.gist_id, gist.gist_access_id,
170 owner.user_id, owner.username, gist.gist_type,
171 owner.user_id, owner.username, gist.gist_type,
171 gist.gist_expires, gist_acl_level)
172 gist.gist_expires, gist_acl_level)
172 return gist
173 return gist
173
174
174 def delete(self, gist, fs_remove=True):
175 def delete(self, gist, fs_remove=True):
175 gist = self._get_gist(gist)
176 gist = self._get_gist(gist)
176 try:
177 try:
177 self.sa.delete(gist)
178 self.sa.delete(gist)
178 if fs_remove:
179 if fs_remove:
179 self.__delete_gist(gist)
180 self.__delete_gist(gist)
180 else:
181 else:
181 log.debug('skipping removal from filesystem')
182 log.debug('skipping removal from filesystem')
182 except Exception:
183 except Exception:
183 log.error(traceback.format_exc())
184 log.error(traceback.format_exc())
184 raise
185 raise
185
186
186 def update(self, gist, description, owner, gist_mapping, lifetime,
187 def update(self, gist, description, owner, gist_mapping, lifetime,
187 gist_acl_level):
188 gist_acl_level):
188 gist = self._get_gist(gist)
189 gist = self._get_gist(gist)
189 gist_repo = gist.scm_instance()
190 gist_repo = gist.scm_instance()
190
191
191 if lifetime == 0: # preserve old value
192 if lifetime == 0: # preserve old value
192 gist_expires = gist.gist_expires
193 gist_expires = gist.gist_expires
193 else:
194 else:
194 gist_expires = (
195 gist_expires = (
195 time.time() + (lifetime * 60) if lifetime != -1 else -1)
196 time.time() + (lifetime * 60) if lifetime != -1 else -1)
196
197
197 # calculate operation type based on given data
198 # calculate operation type based on given data
198 gist_mapping_op = {}
199 gist_mapping_op = {}
199 for k, v in gist_mapping.items():
200 for k, v in gist_mapping.items():
200 # add, mod, del
201 # add, mod, del
201 if not v['filename_org'] and v['filename']:
202 if not v['filename_org'] and v['filename']:
202 op = 'add'
203 op = 'add'
203 elif v['filename_org'] and not v['filename']:
204 elif v['filename_org'] and not v['filename']:
204 op = 'del'
205 op = 'del'
205 else:
206 else:
206 op = 'mod'
207 op = 'mod'
207
208
208 v['op'] = op
209 v['op'] = op
209 gist_mapping_op[k] = v
210 gist_mapping_op[k] = v
210
211
211 gist.gist_description = description
212 gist.gist_description = description
212 gist.gist_expires = gist_expires
213 gist.gist_expires = gist_expires
213 gist.owner = owner
214 gist.owner = owner
214 gist.acl_level = gist_acl_level
215 gist.acl_level = gist_acl_level
215 self.sa.add(gist)
216 self.sa.add(gist)
216 self.sa.flush()
217 self.sa.flush()
217
218
218 message = 'updated file'
219 message = 'updated file'
219 message += 's: ' if len(gist_mapping) > 1 else ': '
220 message += 's: ' if len(gist_mapping) > 1 else ': '
220 message += ', '.join([x for x in gist_mapping])
221 message += ', '.join([x for x in gist_mapping])
221
222
222 # fake RhodeCode Repository object
223 # fake RhodeCode Repository object
223 fake_repo = AttributeDict({
224 fake_repo = AttributeDict({
224 'repo_name': gist_repo.path,
225 'repo_name': gist_repo.path,
225 'scm_instance': lambda *args, **kwargs: gist_repo,
226 'scm_instance': lambda *args, **kwargs: gist_repo,
226 })
227 })
227
228
228 self._store_metadata(gist_repo, gist.gist_id, gist.gist_access_id,
229 self._store_metadata(gist_repo, gist.gist_id, gist.gist_access_id,
229 owner.user_id, owner.username, gist.gist_type,
230 owner.user_id, owner.username, gist.gist_type,
230 gist.gist_expires, gist_acl_level)
231 gist.gist_expires, gist_acl_level)
231
232
232 # this can throw NodeNotChangedError, if changes we're trying to commit
233 # this can throw NodeNotChangedError, if changes we're trying to commit
233 # are not actually changes...
234 # are not actually changes...
234 ScmModel().update_nodes(
235 ScmModel().update_nodes(
235 user=owner.user_id,
236 user=owner.user_id,
236 repo=fake_repo,
237 repo=fake_repo,
237 message=message,
238 message=message,
238 nodes=gist_mapping_op,
239 nodes=gist_mapping_op,
239 trigger_push_hook=False
240 trigger_push_hook=False
240 )
241 )
241
242
242 return gist
243 return gist
243
244
244 def get_url(self, gist, request=None):
245 def get_url(self, gist, request=None):
245 import rhodecode
246 import rhodecode
246
247
247 if not request:
248 if not request:
248 request = get_current_request()
249 request = get_current_request()
249
250
250 alias_url = rhodecode.CONFIG.get('gist_alias_url')
251 alias_url = rhodecode.CONFIG.get('gist_alias_url')
251 if alias_url:
252 if alias_url:
252 return alias_url.replace('{gistid}', gist.gist_access_id)
253 return alias_url.replace('{gistid}', gist.gist_access_id)
253
254
254 return request.route_url('gist_show', gist_id=gist.gist_access_id)
255 return request.route_url('gist_show', gist_id=gist.gist_access_id)
255
256
General Comments 0
You need to be logged in to leave comments. Login now