##// END OF EJS Templates
file-store: changed for stream upload endpoint.
milka -
r4611:29d668ad stable
parent child Browse files
Show More
@@ -1,261 +1,269 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-2020 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 os
21 import os
22 import time
22 import time
23 import errno
23 import errno
24 import shutil
25 import hashlib
24 import hashlib
26
25
27 from rhodecode.lib.ext_json import json
26 from rhodecode.lib.ext_json import json
28 from rhodecode.apps.file_store import utils
27 from rhodecode.apps.file_store import utils
29 from rhodecode.apps.file_store.extensions import resolve_extensions
28 from rhodecode.apps.file_store.extensions import resolve_extensions
30 from rhodecode.apps.file_store.exceptions import (
29 from rhodecode.apps.file_store.exceptions import (
31 FileNotAllowedException, FileOverSizeException)
30 FileNotAllowedException, FileOverSizeException)
32
31
33 METADATA_VER = 'v1'
32 METADATA_VER = 'v1'
34
33
35
34
36 def safe_make_dirs(dir_path):
35 def safe_make_dirs(dir_path):
37 if not os.path.exists(dir_path):
36 if not os.path.exists(dir_path):
38 try:
37 try:
39 os.makedirs(dir_path)
38 os.makedirs(dir_path)
40 except OSError as e:
39 except OSError as e:
41 if e.errno != errno.EEXIST:
40 if e.errno != errno.EEXIST:
42 raise
41 raise
43 return
42 return
44
43
45
44
46 class LocalFileStorage(object):
45 class LocalFileStorage(object):
47
46
48 @classmethod
47 @classmethod
49 def apply_counter(cls, counter, filename):
48 def apply_counter(cls, counter, filename):
50 name_counted = '%d-%s' % (counter, filename)
49 name_counted = '%d-%s' % (counter, filename)
51 return name_counted
50 return name_counted
52
51
53 @classmethod
52 @classmethod
54 def resolve_name(cls, name, directory):
53 def resolve_name(cls, name, directory):
55 """
54 """
56 Resolves a unique name and the correct path. If a filename
55 Resolves a unique name and the correct path. If a filename
57 for that path already exists then a numeric prefix with values > 0 will be
56 for that path already exists then a numeric prefix with values > 0 will be
58 added, for example test.jpg -> 1-test.jpg etc. initially file would have 0 prefix.
57 added, for example test.jpg -> 1-test.jpg etc. initially file would have 0 prefix.
59
58
60 :param name: base name of file
59 :param name: base name of file
61 :param directory: absolute directory path
60 :param directory: absolute directory path
62 """
61 """
63
62
64 counter = 0
63 counter = 0
65 while True:
64 while True:
66 name_counted = cls.apply_counter(counter, name)
65 name_counted = cls.apply_counter(counter, name)
67
66
68 # sub_store prefix to optimize disk usage, e.g some_path/ab/final_file
67 # sub_store prefix to optimize disk usage, e.g some_path/ab/final_file
69 sub_store = cls._sub_store_from_filename(name_counted)
68 sub_store = cls._sub_store_from_filename(name_counted)
70 sub_store_path = os.path.join(directory, sub_store)
69 sub_store_path = os.path.join(directory, sub_store)
71 safe_make_dirs(sub_store_path)
70 safe_make_dirs(sub_store_path)
72
71
73 path = os.path.join(sub_store_path, name_counted)
72 path = os.path.join(sub_store_path, name_counted)
74 if not os.path.exists(path):
73 if not os.path.exists(path):
75 return name_counted, path
74 return name_counted, path
76 counter += 1
75 counter += 1
77
76
78 @classmethod
77 @classmethod
79 def _sub_store_from_filename(cls, filename):
78 def _sub_store_from_filename(cls, filename):
80 return filename[:2]
79 return filename[:2]
81
80
82 @classmethod
81 @classmethod
83 def calculate_path_hash(cls, file_path):
82 def calculate_path_hash(cls, file_path):
84 """
83 """
85 Efficient calculation of file_path sha256 sum
84 Efficient calculation of file_path sha256 sum
86
85
87 :param file_path:
86 :param file_path:
88 :return: sha256sum
87 :return: sha256sum
89 """
88 """
90 digest = hashlib.sha256()
89 digest = hashlib.sha256()
91 with open(file_path, 'rb') as f:
90 with open(file_path, 'rb') as f:
92 for chunk in iter(lambda: f.read(1024 * 100), b""):
91 for chunk in iter(lambda: f.read(1024 * 100), b""):
93 digest.update(chunk)
92 digest.update(chunk)
94
93
95 return digest.hexdigest()
94 return digest.hexdigest()
96
95
97 def __init__(self, base_path, extension_groups=None):
96 def __init__(self, base_path, extension_groups=None):
98
97
99 """
98 """
100 Local file storage
99 Local file storage
101
100
102 :param base_path: the absolute base path where uploads are stored
101 :param base_path: the absolute base path where uploads are stored
103 :param extension_groups: extensions string
102 :param extension_groups: extensions string
104 """
103 """
105
104
106 extension_groups = extension_groups or ['any']
105 extension_groups = extension_groups or ['any']
107 self.base_path = base_path
106 self.base_path = base_path
108 self.extensions = resolve_extensions([], groups=extension_groups)
107 self.extensions = resolve_extensions([], groups=extension_groups)
109
108
110 def __repr__(self):
109 def __repr__(self):
111 return '{}@{}'.format(self.__class__, self.base_path)
110 return '{}@{}'.format(self.__class__, self.base_path)
112
111
113 def store_path(self, filename):
112 def store_path(self, filename):
114 """
113 """
115 Returns absolute file path of the filename, joined to the
114 Returns absolute file path of the filename, joined to the
116 base_path.
115 base_path.
117
116
118 :param filename: base name of file
117 :param filename: base name of file
119 """
118 """
120 prefix_dir = ''
119 prefix_dir = ''
121 if '/' in filename:
120 if '/' in filename:
122 prefix_dir, filename = filename.split('/')
121 prefix_dir, filename = filename.split('/')
123 sub_store = self._sub_store_from_filename(filename)
122 sub_store = self._sub_store_from_filename(filename)
124 else:
123 else:
125 sub_store = self._sub_store_from_filename(filename)
124 sub_store = self._sub_store_from_filename(filename)
126 return os.path.join(self.base_path, prefix_dir, sub_store, filename)
125 return os.path.join(self.base_path, prefix_dir, sub_store, filename)
127
126
128 def delete(self, filename):
127 def delete(self, filename):
129 """
128 """
130 Deletes the filename. Filename is resolved with the
129 Deletes the filename. Filename is resolved with the
131 absolute path based on base_path. If file does not exist,
130 absolute path based on base_path. If file does not exist,
132 returns **False**, otherwise **True**
131 returns **False**, otherwise **True**
133
132
134 :param filename: base name of file
133 :param filename: base name of file
135 """
134 """
136 if self.exists(filename):
135 if self.exists(filename):
137 os.remove(self.store_path(filename))
136 os.remove(self.store_path(filename))
138 return True
137 return True
139 return False
138 return False
140
139
141 def exists(self, filename):
140 def exists(self, filename):
142 """
141 """
143 Checks if file exists. Resolves filename's absolute
142 Checks if file exists. Resolves filename's absolute
144 path based on base_path.
143 path based on base_path.
145
144
146 :param filename: file_uid name of file, e.g 0-f62b2b2d-9708-4079-a071-ec3f958448d4.svg
145 :param filename: file_uid name of file, e.g 0-f62b2b2d-9708-4079-a071-ec3f958448d4.svg
147 """
146 """
148 return os.path.exists(self.store_path(filename))
147 return os.path.exists(self.store_path(filename))
149
148
150 def filename_allowed(self, filename, extensions=None):
149 def filename_allowed(self, filename, extensions=None):
151 """Checks if a filename has an allowed extension
150 """Checks if a filename has an allowed extension
152
151
153 :param filename: base name of file
152 :param filename: base name of file
154 :param extensions: iterable of extensions (or self.extensions)
153 :param extensions: iterable of extensions (or self.extensions)
155 """
154 """
156 _, ext = os.path.splitext(filename)
155 _, ext = os.path.splitext(filename)
157 return self.extension_allowed(ext, extensions)
156 return self.extension_allowed(ext, extensions)
158
157
159 def extension_allowed(self, ext, extensions=None):
158 def extension_allowed(self, ext, extensions=None):
160 """
159 """
161 Checks if an extension is permitted. Both e.g. ".jpg" and
160 Checks if an extension is permitted. Both e.g. ".jpg" and
162 "jpg" can be passed in. Extension lookup is case-insensitive.
161 "jpg" can be passed in. Extension lookup is case-insensitive.
163
162
164 :param ext: extension to check
163 :param ext: extension to check
165 :param extensions: iterable of extensions to validate against (or self.extensions)
164 :param extensions: iterable of extensions to validate against (or self.extensions)
166 """
165 """
167 def normalize_ext(_ext):
166 def normalize_ext(_ext):
168 if _ext.startswith('.'):
167 if _ext.startswith('.'):
169 _ext = _ext[1:]
168 _ext = _ext[1:]
170 return _ext.lower()
169 return _ext.lower()
171
170
172 extensions = extensions or self.extensions
171 extensions = extensions or self.extensions
173 if not extensions:
172 if not extensions:
174 return True
173 return True
175
174
176 ext = normalize_ext(ext)
175 ext = normalize_ext(ext)
177
176
178 return ext in [normalize_ext(x) for x in extensions]
177 return ext in [normalize_ext(x) for x in extensions]
179
178
180 def save_file(self, file_obj, filename, directory=None, extensions=None,
179 def save_file(self, file_obj, filename, directory=None, extensions=None,
181 extra_metadata=None, max_filesize=None, randomized_name=True, **kwargs):
180 extra_metadata=None, max_filesize=None, randomized_name=True, **kwargs):
182 """
181 """
183 Saves a file object to the uploads location.
182 Saves a file object to the uploads location.
184 Returns the resolved filename, i.e. the directory +
183 Returns the resolved filename, i.e. the directory +
185 the (randomized/incremented) base name.
184 the (randomized/incremented) base name.
186
185
187 :param file_obj: **cgi.FieldStorage** object (or similar)
186 :param file_obj: **cgi.FieldStorage** object (or similar)
188 :param filename: original filename
187 :param filename: original filename
189 :param directory: relative path of sub-directory
188 :param directory: relative path of sub-directory
190 :param extensions: iterable of allowed extensions, if not default
189 :param extensions: iterable of allowed extensions, if not default
191 :param max_filesize: maximum size of file that should be allowed
190 :param max_filesize: maximum size of file that should be allowed
192 :param randomized_name: generate random generated UID or fixed based on the filename
191 :param randomized_name: generate random generated UID or fixed based on the filename
193 :param extra_metadata: extra JSON metadata to store next to the file with .meta suffix
192 :param extra_metadata: extra JSON metadata to store next to the file with .meta suffix
194
193
195 """
194 """
196
195
197 extensions = extensions or self.extensions
196 extensions = extensions or self.extensions
198
197
199 if not self.filename_allowed(filename, extensions):
198 if not self.filename_allowed(filename, extensions):
200 raise FileNotAllowedException()
199 raise FileNotAllowedException()
201
200
202 if directory:
201 if directory:
203 dest_directory = os.path.join(self.base_path, directory)
202 dest_directory = os.path.join(self.base_path, directory)
204 else:
203 else:
205 dest_directory = self.base_path
204 dest_directory = self.base_path
206
205
207 safe_make_dirs(dest_directory)
206 safe_make_dirs(dest_directory)
208
207
209 uid_filename = utils.uid_filename(filename, randomized=randomized_name)
208 uid_filename = utils.uid_filename(filename, randomized=randomized_name)
210
209
211 # resolve also produces special sub-dir for file optimized store
210 # resolve also produces special sub-dir for file optimized store
212 filename, path = self.resolve_name(uid_filename, dest_directory)
211 filename, path = self.resolve_name(uid_filename, dest_directory)
213 stored_file_dir = os.path.dirname(path)
212 stored_file_dir = os.path.dirname(path)
214
213
214 no_body_seek = kwargs.pop('no_body_seek', False)
215 if no_body_seek:
216 pass
217 else:
215 file_obj.seek(0)
218 file_obj.seek(0)
216
219
217 with open(path, "wb") as dest:
220 with open(path, "wb") as dest:
218 shutil.copyfileobj(file_obj, dest)
221 length = 256 * 1024
222 while 1:
223 buf = file_obj.read(length)
224 if not buf:
225 break
226 dest.write(buf)
219
227
220 metadata = {}
228 metadata = {}
221 if extra_metadata:
229 if extra_metadata:
222 metadata = extra_metadata
230 metadata = extra_metadata
223
231
224 size = os.stat(path).st_size
232 size = os.stat(path).st_size
225
233
226 if max_filesize and size > max_filesize:
234 if max_filesize and size > max_filesize:
227 # free up the copied file, and raise exc
235 # free up the copied file, and raise exc
228 os.remove(path)
236 os.remove(path)
229 raise FileOverSizeException()
237 raise FileOverSizeException()
230
238
231 file_hash = self.calculate_path_hash(path)
239 file_hash = self.calculate_path_hash(path)
232
240
233 metadata.update({
241 metadata.update({
234 "filename": filename,
242 "filename": filename,
235 "size": size,
243 "size": size,
236 "time": time.time(),
244 "time": time.time(),
237 "sha256": file_hash,
245 "sha256": file_hash,
238 "meta_ver": METADATA_VER
246 "meta_ver": METADATA_VER
239 })
247 })
240
248
241 filename_meta = filename + '.meta'
249 filename_meta = filename + '.meta'
242 with open(os.path.join(stored_file_dir, filename_meta), "wb") as dest_meta:
250 with open(os.path.join(stored_file_dir, filename_meta), "wb") as dest_meta:
243 dest_meta.write(json.dumps(metadata))
251 dest_meta.write(json.dumps(metadata))
244
252
245 if directory:
253 if directory:
246 filename = os.path.join(directory, filename)
254 filename = os.path.join(directory, filename)
247
255
248 return filename, metadata
256 return filename, metadata
249
257
250 def get_metadata(self, filename):
258 def get_metadata(self, filename):
251 """
259 """
252 Reads JSON stored metadata for a file
260 Reads JSON stored metadata for a file
253
261
254 :param filename:
262 :param filename:
255 :return:
263 :return:
256 """
264 """
257 filename = self.store_path(filename)
265 filename = self.store_path(filename)
258 filename_meta = filename + '.meta'
266 filename_meta = filename + '.meta'
259
267
260 with open(filename_meta, "rb") as source_meta:
268 with open(filename_meta, "rb") as source_meta:
261 return json.loads(source_meta.read())
269 return json.loads(source_meta.read())
@@ -1,37 +1,40 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 from rhodecode.lib.ext_json import json
21 from rhodecode.lib.ext_json import json
22
22
23
23
24 def pyramid_ext_json(info):
24 def pyramid_ext_json(info):
25 """
25 """
26 Custom json renderer for pyramid to use our ext_json lib
26 Custom json renderer for pyramid to use our ext_json lib
27 """
27 """
28 def _render(value, system):
28 def _render(value, system):
29 request = system.get('request')
29 request = system.get('request')
30 indent = None
30 if request is not None:
31 if request is not None:
31 response = request.response
32 response = request.response
32 ct = response.content_type
33 ct = response.content_type
33 if ct == response.default_content_type:
34 if ct == response.default_content_type:
34 response.content_type = 'application/json'
35 response.content_type = 'application/json'
35 return json.dumps(value)
36 indent = getattr(request, 'ext_json_indent', None)
37
38 return json.dumps(value, indent=indent)
36
39
37 return _render
40 return _render
@@ -1,5805 +1,5826 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 string
28 import string
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import datetime
31 import datetime
32 import uuid
32 import uuid
33 import warnings
33 import warnings
34 import ipaddress
34 import ipaddress
35 import functools
35 import functools
36 import traceback
36 import traceback
37 import collections
37 import collections
38
38
39 from sqlalchemy import (
39 from sqlalchemy import (
40 or_, and_, not_, func, cast, TypeDecorator, event,
40 or_, and_, not_, func, cast, TypeDecorator, event,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
41 Index, Sequence, UniqueConstraint, ForeignKey, CheckConstraint, Column,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
42 Boolean, String, Unicode, UnicodeText, DateTime, Integer, LargeBinary,
43 Text, Float, PickleType, BigInteger)
43 Text, Float, PickleType, BigInteger)
44 from sqlalchemy.sql.expression import true, false, case
44 from sqlalchemy.sql.expression import true, false, case
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
45 from sqlalchemy.sql.functions import coalesce, count # pragma: no cover
46 from sqlalchemy.orm import (
46 from sqlalchemy.orm import (
47 relationship, joinedload, class_mapper, validates, aliased)
47 relationship, joinedload, class_mapper, validates, aliased)
48 from sqlalchemy.ext.declarative import declared_attr
48 from sqlalchemy.ext.declarative import declared_attr
49 from sqlalchemy.ext.hybrid import hybrid_property
49 from sqlalchemy.ext.hybrid import hybrid_property
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
50 from sqlalchemy.exc import IntegrityError # pragma: no cover
51 from sqlalchemy.dialects.mysql import LONGTEXT
51 from sqlalchemy.dialects.mysql import LONGTEXT
52 from zope.cachedescriptors.property import Lazy as LazyProperty
52 from zope.cachedescriptors.property import Lazy as LazyProperty
53 from pyramid import compat
53 from pyramid import compat
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55 from webhelpers2.text import remove_formatting
55 from webhelpers2.text import remove_formatting
56
56
57 from rhodecode.translation import _
57 from rhodecode.translation import _
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
58 from rhodecode.lib.vcs import get_vcs_instance, VCSError
59 from rhodecode.lib.vcs.backends.base import (
59 from rhodecode.lib.vcs.backends.base import (
60 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
60 EmptyCommit, Reference, unicode_to_reference, reference_to_unicode)
61 from rhodecode.lib.utils2 import (
61 from rhodecode.lib.utils2 import (
62 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
62 str2bool, safe_str, get_commit_safe, safe_unicode, sha1_safe,
63 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
63 time_to_datetime, aslist, Optional, safe_int, get_clone_url, AttributeDict,
64 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
64 glob2re, StrictAttributeDict, cleaned_uri, datetime_to_time, OrderedDefaultDict)
65 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
65 from rhodecode.lib.jsonalchemy import MutationObj, MutationList, JsonType, \
66 JsonRaw
66 JsonRaw
67 from rhodecode.lib.ext_json import json
67 from rhodecode.lib.ext_json import json
68 from rhodecode.lib.caching_query import FromCache
68 from rhodecode.lib.caching_query import FromCache
69 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
69 from rhodecode.lib.encrypt import AESCipher, validate_and_get_enc_data
70 from rhodecode.lib.encrypt2 import Encryptor
70 from rhodecode.lib.encrypt2 import Encryptor
71 from rhodecode.lib.exceptions import (
71 from rhodecode.lib.exceptions import (
72 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
72 ArtifactMetadataDuplicate, ArtifactMetadataBadValueType)
73 from rhodecode.model.meta import Base, Session
73 from rhodecode.model.meta import Base, Session
74
74
75 URL_SEP = '/'
75 URL_SEP = '/'
76 log = logging.getLogger(__name__)
76 log = logging.getLogger(__name__)
77
77
78 # =============================================================================
78 # =============================================================================
79 # BASE CLASSES
79 # BASE CLASSES
80 # =============================================================================
80 # =============================================================================
81
81
82 # this is propagated from .ini file rhodecode.encrypted_values.secret or
82 # this is propagated from .ini file rhodecode.encrypted_values.secret or
83 # beaker.session.secret if first is not set.
83 # beaker.session.secret if first is not set.
84 # and initialized at environment.py
84 # and initialized at environment.py
85 ENCRYPTION_KEY = None
85 ENCRYPTION_KEY = None
86
86
87 # used to sort permissions by types, '#' used here is not allowed to be in
87 # used to sort permissions by types, '#' used here is not allowed to be in
88 # usernames, and it's very early in sorted string.printable table.
88 # usernames, and it's very early in sorted string.printable table.
89 PERMISSION_TYPE_SORT = {
89 PERMISSION_TYPE_SORT = {
90 'admin': '####',
90 'admin': '####',
91 'write': '###',
91 'write': '###',
92 'read': '##',
92 'read': '##',
93 'none': '#',
93 'none': '#',
94 }
94 }
95
95
96
96
97 def display_user_sort(obj):
97 def display_user_sort(obj):
98 """
98 """
99 Sort function used to sort permissions in .permissions() function of
99 Sort function used to sort permissions in .permissions() function of
100 Repository, RepoGroup, UserGroup. Also it put the default user in front
100 Repository, RepoGroup, UserGroup. Also it put the default user in front
101 of all other resources
101 of all other resources
102 """
102 """
103
103
104 if obj.username == User.DEFAULT_USER:
104 if obj.username == User.DEFAULT_USER:
105 return '#####'
105 return '#####'
106 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
106 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
107 extra_sort_num = '1' # default
107 extra_sort_num = '1' # default
108
108
109 # NOTE(dan): inactive duplicates goes last
109 # NOTE(dan): inactive duplicates goes last
110 if getattr(obj, 'duplicate_perm', None):
110 if getattr(obj, 'duplicate_perm', None):
111 extra_sort_num = '9'
111 extra_sort_num = '9'
112 return prefix + extra_sort_num + obj.username
112 return prefix + extra_sort_num + obj.username
113
113
114
114
115 def display_user_group_sort(obj):
115 def display_user_group_sort(obj):
116 """
116 """
117 Sort function used to sort permissions in .permissions() function of
117 Sort function used to sort permissions in .permissions() function of
118 Repository, RepoGroup, UserGroup. Also it put the default user in front
118 Repository, RepoGroup, UserGroup. Also it put the default user in front
119 of all other resources
119 of all other resources
120 """
120 """
121
121
122 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
122 prefix = PERMISSION_TYPE_SORT.get(obj.permission.split('.')[-1], '')
123 return prefix + obj.users_group_name
123 return prefix + obj.users_group_name
124
124
125
125
126 def _hash_key(k):
126 def _hash_key(k):
127 return sha1_safe(k)
127 return sha1_safe(k)
128
128
129
129
130 def in_filter_generator(qry, items, limit=500):
130 def in_filter_generator(qry, items, limit=500):
131 """
131 """
132 Splits IN() into multiple with OR
132 Splits IN() into multiple with OR
133 e.g.::
133 e.g.::
134 cnt = Repository.query().filter(
134 cnt = Repository.query().filter(
135 or_(
135 or_(
136 *in_filter_generator(Repository.repo_id, range(100000))
136 *in_filter_generator(Repository.repo_id, range(100000))
137 )).count()
137 )).count()
138 """
138 """
139 if not items:
139 if not items:
140 # empty list will cause empty query which might cause security issues
140 # empty list will cause empty query which might cause security issues
141 # this can lead to hidden unpleasant results
141 # this can lead to hidden unpleasant results
142 items = [-1]
142 items = [-1]
143
143
144 parts = []
144 parts = []
145 for chunk in xrange(0, len(items), limit):
145 for chunk in xrange(0, len(items), limit):
146 parts.append(
146 parts.append(
147 qry.in_(items[chunk: chunk + limit])
147 qry.in_(items[chunk: chunk + limit])
148 )
148 )
149
149
150 return parts
150 return parts
151
151
152
152
153 base_table_args = {
153 base_table_args = {
154 'extend_existing': True,
154 'extend_existing': True,
155 'mysql_engine': 'InnoDB',
155 'mysql_engine': 'InnoDB',
156 'mysql_charset': 'utf8',
156 'mysql_charset': 'utf8',
157 'sqlite_autoincrement': True
157 'sqlite_autoincrement': True
158 }
158 }
159
159
160
160
161 class EncryptedTextValue(TypeDecorator):
161 class EncryptedTextValue(TypeDecorator):
162 """
162 """
163 Special column for encrypted long text data, use like::
163 Special column for encrypted long text data, use like::
164
164
165 value = Column("encrypted_value", EncryptedValue(), nullable=False)
165 value = Column("encrypted_value", EncryptedValue(), nullable=False)
166
166
167 This column is intelligent so if value is in unencrypted form it return
167 This column is intelligent so if value is in unencrypted form it return
168 unencrypted form, but on save it always encrypts
168 unencrypted form, but on save it always encrypts
169 """
169 """
170 impl = Text
170 impl = Text
171
171
172 def process_bind_param(self, value, dialect):
172 def process_bind_param(self, value, dialect):
173 """
173 """
174 Setter for storing value
174 Setter for storing value
175 """
175 """
176 import rhodecode
176 import rhodecode
177 if not value:
177 if not value:
178 return value
178 return value
179
179
180 # protect against double encrypting if values is already encrypted
180 # protect against double encrypting if values is already encrypted
181 if value.startswith('enc$aes$') \
181 if value.startswith('enc$aes$') \
182 or value.startswith('enc$aes_hmac$') \
182 or value.startswith('enc$aes_hmac$') \
183 or value.startswith('enc2$'):
183 or value.startswith('enc2$'):
184 raise ValueError('value needs to be in unencrypted format, '
184 raise ValueError('value needs to be in unencrypted format, '
185 'ie. not starting with enc$ or enc2$')
185 'ie. not starting with enc$ or enc2$')
186
186
187 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
187 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
188 if algo == 'aes':
188 if algo == 'aes':
189 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
189 return 'enc$aes_hmac$%s' % AESCipher(ENCRYPTION_KEY, hmac=True).encrypt(value)
190 elif algo == 'fernet':
190 elif algo == 'fernet':
191 return Encryptor(ENCRYPTION_KEY).encrypt(value)
191 return Encryptor(ENCRYPTION_KEY).encrypt(value)
192 else:
192 else:
193 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
193 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
194
194
195 def process_result_value(self, value, dialect):
195 def process_result_value(self, value, dialect):
196 """
196 """
197 Getter for retrieving value
197 Getter for retrieving value
198 """
198 """
199
199
200 import rhodecode
200 import rhodecode
201 if not value:
201 if not value:
202 return value
202 return value
203
203
204 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
204 algo = rhodecode.CONFIG.get('rhodecode.encrypted_values.algorithm') or 'aes'
205 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
205 enc_strict_mode = str2bool(rhodecode.CONFIG.get('rhodecode.encrypted_values.strict') or True)
206 if algo == 'aes':
206 if algo == 'aes':
207 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
207 decrypted_data = validate_and_get_enc_data(value, ENCRYPTION_KEY, enc_strict_mode)
208 elif algo == 'fernet':
208 elif algo == 'fernet':
209 return Encryptor(ENCRYPTION_KEY).decrypt(value)
209 return Encryptor(ENCRYPTION_KEY).decrypt(value)
210 else:
210 else:
211 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
211 ValueError('Bad encryption algorithm, should be fernet or aes, got: {}'.format(algo))
212 return decrypted_data
212 return decrypted_data
213
213
214
214
215 class BaseModel(object):
215 class BaseModel(object):
216 """
216 """
217 Base Model for all classes
217 Base Model for all classes
218 """
218 """
219
219
220 @classmethod
220 @classmethod
221 def _get_keys(cls):
221 def _get_keys(cls):
222 """return column names for this model """
222 """return column names for this model """
223 return class_mapper(cls).c.keys()
223 return class_mapper(cls).c.keys()
224
224
225 def get_dict(self):
225 def get_dict(self):
226 """
226 """
227 return dict with keys and values corresponding
227 return dict with keys and values corresponding
228 to this model data """
228 to this model data """
229
229
230 d = {}
230 d = {}
231 for k in self._get_keys():
231 for k in self._get_keys():
232 d[k] = getattr(self, k)
232 d[k] = getattr(self, k)
233
233
234 # also use __json__() if present to get additional fields
234 # also use __json__() if present to get additional fields
235 _json_attr = getattr(self, '__json__', None)
235 _json_attr = getattr(self, '__json__', None)
236 if _json_attr:
236 if _json_attr:
237 # update with attributes from __json__
237 # update with attributes from __json__
238 if callable(_json_attr):
238 if callable(_json_attr):
239 _json_attr = _json_attr()
239 _json_attr = _json_attr()
240 for k, val in _json_attr.iteritems():
240 for k, val in _json_attr.iteritems():
241 d[k] = val
241 d[k] = val
242 return d
242 return d
243
243
244 def get_appstruct(self):
244 def get_appstruct(self):
245 """return list with keys and values tuples corresponding
245 """return list with keys and values tuples corresponding
246 to this model data """
246 to this model data """
247
247
248 lst = []
248 lst = []
249 for k in self._get_keys():
249 for k in self._get_keys():
250 lst.append((k, getattr(self, k),))
250 lst.append((k, getattr(self, k),))
251 return lst
251 return lst
252
252
253 def populate_obj(self, populate_dict):
253 def populate_obj(self, populate_dict):
254 """populate model with data from given populate_dict"""
254 """populate model with data from given populate_dict"""
255
255
256 for k in self._get_keys():
256 for k in self._get_keys():
257 if k in populate_dict:
257 if k in populate_dict:
258 setattr(self, k, populate_dict[k])
258 setattr(self, k, populate_dict[k])
259
259
260 @classmethod
260 @classmethod
261 def query(cls):
261 def query(cls):
262 return Session().query(cls)
262 return Session().query(cls)
263
263
264 @classmethod
264 @classmethod
265 def get(cls, id_):
265 def get(cls, id_):
266 if id_:
266 if id_:
267 return cls.query().get(id_)
267 return cls.query().get(id_)
268
268
269 @classmethod
269 @classmethod
270 def get_or_404(cls, id_):
270 def get_or_404(cls, id_):
271 from pyramid.httpexceptions import HTTPNotFound
271 from pyramid.httpexceptions import HTTPNotFound
272
272
273 try:
273 try:
274 id_ = int(id_)
274 id_ = int(id_)
275 except (TypeError, ValueError):
275 except (TypeError, ValueError):
276 raise HTTPNotFound()
276 raise HTTPNotFound()
277
277
278 res = cls.query().get(id_)
278 res = cls.query().get(id_)
279 if not res:
279 if not res:
280 raise HTTPNotFound()
280 raise HTTPNotFound()
281 return res
281 return res
282
282
283 @classmethod
283 @classmethod
284 def getAll(cls):
284 def getAll(cls):
285 # deprecated and left for backward compatibility
285 # deprecated and left for backward compatibility
286 return cls.get_all()
286 return cls.get_all()
287
287
288 @classmethod
288 @classmethod
289 def get_all(cls):
289 def get_all(cls):
290 return cls.query().all()
290 return cls.query().all()
291
291
292 @classmethod
292 @classmethod
293 def delete(cls, id_):
293 def delete(cls, id_):
294 obj = cls.query().get(id_)
294 obj = cls.query().get(id_)
295 Session().delete(obj)
295 Session().delete(obj)
296
296
297 @classmethod
297 @classmethod
298 def identity_cache(cls, session, attr_name, value):
298 def identity_cache(cls, session, attr_name, value):
299 exist_in_session = []
299 exist_in_session = []
300 for (item_cls, pkey), instance in session.identity_map.items():
300 for (item_cls, pkey), instance in session.identity_map.items():
301 if cls == item_cls and getattr(instance, attr_name) == value:
301 if cls == item_cls and getattr(instance, attr_name) == value:
302 exist_in_session.append(instance)
302 exist_in_session.append(instance)
303 if exist_in_session:
303 if exist_in_session:
304 if len(exist_in_session) == 1:
304 if len(exist_in_session) == 1:
305 return exist_in_session[0]
305 return exist_in_session[0]
306 log.exception(
306 log.exception(
307 'multiple objects with attr %s and '
307 'multiple objects with attr %s and '
308 'value %s found with same name: %r',
308 'value %s found with same name: %r',
309 attr_name, value, exist_in_session)
309 attr_name, value, exist_in_session)
310
310
311 def __repr__(self):
311 def __repr__(self):
312 if hasattr(self, '__unicode__'):
312 if hasattr(self, '__unicode__'):
313 # python repr needs to return str
313 # python repr needs to return str
314 try:
314 try:
315 return safe_str(self.__unicode__())
315 return safe_str(self.__unicode__())
316 except UnicodeDecodeError:
316 except UnicodeDecodeError:
317 pass
317 pass
318 return '<DB:%s>' % (self.__class__.__name__)
318 return '<DB:%s>' % (self.__class__.__name__)
319
319
320
320
321 class RhodeCodeSetting(Base, BaseModel):
321 class RhodeCodeSetting(Base, BaseModel):
322 __tablename__ = 'rhodecode_settings'
322 __tablename__ = 'rhodecode_settings'
323 __table_args__ = (
323 __table_args__ = (
324 UniqueConstraint('app_settings_name'),
324 UniqueConstraint('app_settings_name'),
325 base_table_args
325 base_table_args
326 )
326 )
327
327
328 SETTINGS_TYPES = {
328 SETTINGS_TYPES = {
329 'str': safe_str,
329 'str': safe_str,
330 'int': safe_int,
330 'int': safe_int,
331 'unicode': safe_unicode,
331 'unicode': safe_unicode,
332 'bool': str2bool,
332 'bool': str2bool,
333 'list': functools.partial(aslist, sep=',')
333 'list': functools.partial(aslist, sep=',')
334 }
334 }
335 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
335 DEFAULT_UPDATE_URL = 'https://rhodecode.com/api/v1/info/versions'
336 GLOBAL_CONF_KEY = 'app_settings'
336 GLOBAL_CONF_KEY = 'app_settings'
337
337
338 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
338 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
339 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
339 app_settings_name = Column("app_settings_name", String(255), nullable=True, unique=None, default=None)
340 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
340 _app_settings_value = Column("app_settings_value", String(4096), nullable=True, unique=None, default=None)
341 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
341 _app_settings_type = Column("app_settings_type", String(255), nullable=True, unique=None, default=None)
342
342
343 def __init__(self, key='', val='', type='unicode'):
343 def __init__(self, key='', val='', type='unicode'):
344 self.app_settings_name = key
344 self.app_settings_name = key
345 self.app_settings_type = type
345 self.app_settings_type = type
346 self.app_settings_value = val
346 self.app_settings_value = val
347
347
348 @validates('_app_settings_value')
348 @validates('_app_settings_value')
349 def validate_settings_value(self, key, val):
349 def validate_settings_value(self, key, val):
350 assert type(val) == unicode
350 assert type(val) == unicode
351 return val
351 return val
352
352
353 @hybrid_property
353 @hybrid_property
354 def app_settings_value(self):
354 def app_settings_value(self):
355 v = self._app_settings_value
355 v = self._app_settings_value
356 _type = self.app_settings_type
356 _type = self.app_settings_type
357 if _type:
357 if _type:
358 _type = self.app_settings_type.split('.')[0]
358 _type = self.app_settings_type.split('.')[0]
359 # decode the encrypted value
359 # decode the encrypted value
360 if 'encrypted' in self.app_settings_type:
360 if 'encrypted' in self.app_settings_type:
361 cipher = EncryptedTextValue()
361 cipher = EncryptedTextValue()
362 v = safe_unicode(cipher.process_result_value(v, None))
362 v = safe_unicode(cipher.process_result_value(v, None))
363
363
364 converter = self.SETTINGS_TYPES.get(_type) or \
364 converter = self.SETTINGS_TYPES.get(_type) or \
365 self.SETTINGS_TYPES['unicode']
365 self.SETTINGS_TYPES['unicode']
366 return converter(v)
366 return converter(v)
367
367
368 @app_settings_value.setter
368 @app_settings_value.setter
369 def app_settings_value(self, val):
369 def app_settings_value(self, val):
370 """
370 """
371 Setter that will always make sure we use unicode in app_settings_value
371 Setter that will always make sure we use unicode in app_settings_value
372
372
373 :param val:
373 :param val:
374 """
374 """
375 val = safe_unicode(val)
375 val = safe_unicode(val)
376 # encode the encrypted value
376 # encode the encrypted value
377 if 'encrypted' in self.app_settings_type:
377 if 'encrypted' in self.app_settings_type:
378 cipher = EncryptedTextValue()
378 cipher = EncryptedTextValue()
379 val = safe_unicode(cipher.process_bind_param(val, None))
379 val = safe_unicode(cipher.process_bind_param(val, None))
380 self._app_settings_value = val
380 self._app_settings_value = val
381
381
382 @hybrid_property
382 @hybrid_property
383 def app_settings_type(self):
383 def app_settings_type(self):
384 return self._app_settings_type
384 return self._app_settings_type
385
385
386 @app_settings_type.setter
386 @app_settings_type.setter
387 def app_settings_type(self, val):
387 def app_settings_type(self, val):
388 if val.split('.')[0] not in self.SETTINGS_TYPES:
388 if val.split('.')[0] not in self.SETTINGS_TYPES:
389 raise Exception('type must be one of %s got %s'
389 raise Exception('type must be one of %s got %s'
390 % (self.SETTINGS_TYPES.keys(), val))
390 % (self.SETTINGS_TYPES.keys(), val))
391 self._app_settings_type = val
391 self._app_settings_type = val
392
392
393 @classmethod
393 @classmethod
394 def get_by_prefix(cls, prefix):
394 def get_by_prefix(cls, prefix):
395 return RhodeCodeSetting.query()\
395 return RhodeCodeSetting.query()\
396 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
396 .filter(RhodeCodeSetting.app_settings_name.startswith(prefix))\
397 .all()
397 .all()
398
398
399 def __unicode__(self):
399 def __unicode__(self):
400 return u"<%s('%s:%s[%s]')>" % (
400 return u"<%s('%s:%s[%s]')>" % (
401 self.__class__.__name__,
401 self.__class__.__name__,
402 self.app_settings_name, self.app_settings_value,
402 self.app_settings_name, self.app_settings_value,
403 self.app_settings_type
403 self.app_settings_type
404 )
404 )
405
405
406
406
407 class RhodeCodeUi(Base, BaseModel):
407 class RhodeCodeUi(Base, BaseModel):
408 __tablename__ = 'rhodecode_ui'
408 __tablename__ = 'rhodecode_ui'
409 __table_args__ = (
409 __table_args__ = (
410 UniqueConstraint('ui_key'),
410 UniqueConstraint('ui_key'),
411 base_table_args
411 base_table_args
412 )
412 )
413
413
414 HOOK_REPO_SIZE = 'changegroup.repo_size'
414 HOOK_REPO_SIZE = 'changegroup.repo_size'
415 # HG
415 # HG
416 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
416 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
417 HOOK_PULL = 'outgoing.pull_logger'
417 HOOK_PULL = 'outgoing.pull_logger'
418 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
418 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
419 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
419 HOOK_PRETX_PUSH = 'pretxnchangegroup.pre_push'
420 HOOK_PUSH = 'changegroup.push_logger'
420 HOOK_PUSH = 'changegroup.push_logger'
421 HOOK_PUSH_KEY = 'pushkey.key_push'
421 HOOK_PUSH_KEY = 'pushkey.key_push'
422
422
423 HOOKS_BUILTIN = [
423 HOOKS_BUILTIN = [
424 HOOK_PRE_PULL,
424 HOOK_PRE_PULL,
425 HOOK_PULL,
425 HOOK_PULL,
426 HOOK_PRE_PUSH,
426 HOOK_PRE_PUSH,
427 HOOK_PRETX_PUSH,
427 HOOK_PRETX_PUSH,
428 HOOK_PUSH,
428 HOOK_PUSH,
429 HOOK_PUSH_KEY,
429 HOOK_PUSH_KEY,
430 ]
430 ]
431
431
432 # TODO: johbo: Unify way how hooks are configured for git and hg,
432 # TODO: johbo: Unify way how hooks are configured for git and hg,
433 # git part is currently hardcoded.
433 # git part is currently hardcoded.
434
434
435 # SVN PATTERNS
435 # SVN PATTERNS
436 SVN_BRANCH_ID = 'vcs_svn_branch'
436 SVN_BRANCH_ID = 'vcs_svn_branch'
437 SVN_TAG_ID = 'vcs_svn_tag'
437 SVN_TAG_ID = 'vcs_svn_tag'
438
438
439 ui_id = Column(
439 ui_id = Column(
440 "ui_id", Integer(), nullable=False, unique=True, default=None,
440 "ui_id", Integer(), nullable=False, unique=True, default=None,
441 primary_key=True)
441 primary_key=True)
442 ui_section = Column(
442 ui_section = Column(
443 "ui_section", String(255), nullable=True, unique=None, default=None)
443 "ui_section", String(255), nullable=True, unique=None, default=None)
444 ui_key = Column(
444 ui_key = Column(
445 "ui_key", String(255), nullable=True, unique=None, default=None)
445 "ui_key", String(255), nullable=True, unique=None, default=None)
446 ui_value = Column(
446 ui_value = Column(
447 "ui_value", String(255), nullable=True, unique=None, default=None)
447 "ui_value", String(255), nullable=True, unique=None, default=None)
448 ui_active = Column(
448 ui_active = Column(
449 "ui_active", Boolean(), nullable=True, unique=None, default=True)
449 "ui_active", Boolean(), nullable=True, unique=None, default=True)
450
450
451 def __repr__(self):
451 def __repr__(self):
452 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
452 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.ui_section,
453 self.ui_key, self.ui_value)
453 self.ui_key, self.ui_value)
454
454
455
455
456 class RepoRhodeCodeSetting(Base, BaseModel):
456 class RepoRhodeCodeSetting(Base, BaseModel):
457 __tablename__ = 'repo_rhodecode_settings'
457 __tablename__ = 'repo_rhodecode_settings'
458 __table_args__ = (
458 __table_args__ = (
459 UniqueConstraint(
459 UniqueConstraint(
460 'app_settings_name', 'repository_id',
460 'app_settings_name', 'repository_id',
461 name='uq_repo_rhodecode_setting_name_repo_id'),
461 name='uq_repo_rhodecode_setting_name_repo_id'),
462 base_table_args
462 base_table_args
463 )
463 )
464
464
465 repository_id = Column(
465 repository_id = Column(
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
466 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
467 nullable=False)
467 nullable=False)
468 app_settings_id = Column(
468 app_settings_id = Column(
469 "app_settings_id", Integer(), nullable=False, unique=True,
469 "app_settings_id", Integer(), nullable=False, unique=True,
470 default=None, primary_key=True)
470 default=None, primary_key=True)
471 app_settings_name = Column(
471 app_settings_name = Column(
472 "app_settings_name", String(255), nullable=True, unique=None,
472 "app_settings_name", String(255), nullable=True, unique=None,
473 default=None)
473 default=None)
474 _app_settings_value = Column(
474 _app_settings_value = Column(
475 "app_settings_value", String(4096), nullable=True, unique=None,
475 "app_settings_value", String(4096), nullable=True, unique=None,
476 default=None)
476 default=None)
477 _app_settings_type = Column(
477 _app_settings_type = Column(
478 "app_settings_type", String(255), nullable=True, unique=None,
478 "app_settings_type", String(255), nullable=True, unique=None,
479 default=None)
479 default=None)
480
480
481 repository = relationship('Repository')
481 repository = relationship('Repository')
482
482
483 def __init__(self, repository_id, key='', val='', type='unicode'):
483 def __init__(self, repository_id, key='', val='', type='unicode'):
484 self.repository_id = repository_id
484 self.repository_id = repository_id
485 self.app_settings_name = key
485 self.app_settings_name = key
486 self.app_settings_type = type
486 self.app_settings_type = type
487 self.app_settings_value = val
487 self.app_settings_value = val
488
488
489 @validates('_app_settings_value')
489 @validates('_app_settings_value')
490 def validate_settings_value(self, key, val):
490 def validate_settings_value(self, key, val):
491 assert type(val) == unicode
491 assert type(val) == unicode
492 return val
492 return val
493
493
494 @hybrid_property
494 @hybrid_property
495 def app_settings_value(self):
495 def app_settings_value(self):
496 v = self._app_settings_value
496 v = self._app_settings_value
497 type_ = self.app_settings_type
497 type_ = self.app_settings_type
498 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
498 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
499 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
499 converter = SETTINGS_TYPES.get(type_) or SETTINGS_TYPES['unicode']
500 return converter(v)
500 return converter(v)
501
501
502 @app_settings_value.setter
502 @app_settings_value.setter
503 def app_settings_value(self, val):
503 def app_settings_value(self, val):
504 """
504 """
505 Setter that will always make sure we use unicode in app_settings_value
505 Setter that will always make sure we use unicode in app_settings_value
506
506
507 :param val:
507 :param val:
508 """
508 """
509 self._app_settings_value = safe_unicode(val)
509 self._app_settings_value = safe_unicode(val)
510
510
511 @hybrid_property
511 @hybrid_property
512 def app_settings_type(self):
512 def app_settings_type(self):
513 return self._app_settings_type
513 return self._app_settings_type
514
514
515 @app_settings_type.setter
515 @app_settings_type.setter
516 def app_settings_type(self, val):
516 def app_settings_type(self, val):
517 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
517 SETTINGS_TYPES = RhodeCodeSetting.SETTINGS_TYPES
518 if val not in SETTINGS_TYPES:
518 if val not in SETTINGS_TYPES:
519 raise Exception('type must be one of %s got %s'
519 raise Exception('type must be one of %s got %s'
520 % (SETTINGS_TYPES.keys(), val))
520 % (SETTINGS_TYPES.keys(), val))
521 self._app_settings_type = val
521 self._app_settings_type = val
522
522
523 def __unicode__(self):
523 def __unicode__(self):
524 return u"<%s('%s:%s:%s[%s]')>" % (
524 return u"<%s('%s:%s:%s[%s]')>" % (
525 self.__class__.__name__, self.repository.repo_name,
525 self.__class__.__name__, self.repository.repo_name,
526 self.app_settings_name, self.app_settings_value,
526 self.app_settings_name, self.app_settings_value,
527 self.app_settings_type
527 self.app_settings_type
528 )
528 )
529
529
530
530
531 class RepoRhodeCodeUi(Base, BaseModel):
531 class RepoRhodeCodeUi(Base, BaseModel):
532 __tablename__ = 'repo_rhodecode_ui'
532 __tablename__ = 'repo_rhodecode_ui'
533 __table_args__ = (
533 __table_args__ = (
534 UniqueConstraint(
534 UniqueConstraint(
535 'repository_id', 'ui_section', 'ui_key',
535 'repository_id', 'ui_section', 'ui_key',
536 name='uq_repo_rhodecode_ui_repository_id_section_key'),
536 name='uq_repo_rhodecode_ui_repository_id_section_key'),
537 base_table_args
537 base_table_args
538 )
538 )
539
539
540 repository_id = Column(
540 repository_id = Column(
541 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
541 "repository_id", Integer(), ForeignKey('repositories.repo_id'),
542 nullable=False)
542 nullable=False)
543 ui_id = Column(
543 ui_id = Column(
544 "ui_id", Integer(), nullable=False, unique=True, default=None,
544 "ui_id", Integer(), nullable=False, unique=True, default=None,
545 primary_key=True)
545 primary_key=True)
546 ui_section = Column(
546 ui_section = Column(
547 "ui_section", String(255), nullable=True, unique=None, default=None)
547 "ui_section", String(255), nullable=True, unique=None, default=None)
548 ui_key = Column(
548 ui_key = Column(
549 "ui_key", String(255), nullable=True, unique=None, default=None)
549 "ui_key", String(255), nullable=True, unique=None, default=None)
550 ui_value = Column(
550 ui_value = Column(
551 "ui_value", String(255), nullable=True, unique=None, default=None)
551 "ui_value", String(255), nullable=True, unique=None, default=None)
552 ui_active = Column(
552 ui_active = Column(
553 "ui_active", Boolean(), nullable=True, unique=None, default=True)
553 "ui_active", Boolean(), nullable=True, unique=None, default=True)
554
554
555 repository = relationship('Repository')
555 repository = relationship('Repository')
556
556
557 def __repr__(self):
557 def __repr__(self):
558 return '<%s[%s:%s]%s=>%s]>' % (
558 return '<%s[%s:%s]%s=>%s]>' % (
559 self.__class__.__name__, self.repository.repo_name,
559 self.__class__.__name__, self.repository.repo_name,
560 self.ui_section, self.ui_key, self.ui_value)
560 self.ui_section, self.ui_key, self.ui_value)
561
561
562
562
563 class User(Base, BaseModel):
563 class User(Base, BaseModel):
564 __tablename__ = 'users'
564 __tablename__ = 'users'
565 __table_args__ = (
565 __table_args__ = (
566 UniqueConstraint('username'), UniqueConstraint('email'),
566 UniqueConstraint('username'), UniqueConstraint('email'),
567 Index('u_username_idx', 'username'),
567 Index('u_username_idx', 'username'),
568 Index('u_email_idx', 'email'),
568 Index('u_email_idx', 'email'),
569 base_table_args
569 base_table_args
570 )
570 )
571
571
572 DEFAULT_USER = 'default'
572 DEFAULT_USER = 'default'
573 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
573 DEFAULT_USER_EMAIL = 'anonymous@rhodecode.org'
574 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
574 DEFAULT_GRAVATAR_URL = 'https://secure.gravatar.com/avatar/{md5email}?d=identicon&s={size}'
575
575
576 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
576 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
577 username = Column("username", String(255), nullable=True, unique=None, default=None)
577 username = Column("username", String(255), nullable=True, unique=None, default=None)
578 password = Column("password", String(255), nullable=True, unique=None, default=None)
578 password = Column("password", String(255), nullable=True, unique=None, default=None)
579 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
579 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
580 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
580 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
581 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
581 name = Column("firstname", String(255), nullable=True, unique=None, default=None)
582 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
582 lastname = Column("lastname", String(255), nullable=True, unique=None, default=None)
583 _email = Column("email", String(255), nullable=True, unique=None, default=None)
583 _email = Column("email", String(255), nullable=True, unique=None, default=None)
584 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
584 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
585 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
585 last_activity = Column('last_activity', DateTime(timezone=False), nullable=True, unique=None, default=None)
586 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
586 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
587
587
588 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
588 extern_type = Column("extern_type", String(255), nullable=True, unique=None, default=None)
589 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
589 extern_name = Column("extern_name", String(255), nullable=True, unique=None, default=None)
590 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
590 _api_key = Column("api_key", String(255), nullable=True, unique=None, default=None)
591 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
591 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
592 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
592 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
593 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
593 _user_data = Column("user_data", LargeBinary(), nullable=True) # JSON data
594
594
595 user_log = relationship('UserLog')
595 user_log = relationship('UserLog')
596 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
596 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all, delete-orphan')
597
597
598 repositories = relationship('Repository')
598 repositories = relationship('Repository')
599 repository_groups = relationship('RepoGroup')
599 repository_groups = relationship('RepoGroup')
600 user_groups = relationship('UserGroup')
600 user_groups = relationship('UserGroup')
601
601
602 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
602 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
603 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
603 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
604
604
605 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
605 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all, delete-orphan')
606 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
606 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
607 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
607 user_group_to_perm = relationship('UserUserGroupToPerm', primaryjoin='UserUserGroupToPerm.user_id==User.user_id', cascade='all, delete-orphan')
608
608
609 group_member = relationship('UserGroupMember', cascade='all')
609 group_member = relationship('UserGroupMember', cascade='all')
610
610
611 notifications = relationship('UserNotification', cascade='all')
611 notifications = relationship('UserNotification', cascade='all')
612 # notifications assigned to this user
612 # notifications assigned to this user
613 user_created_notifications = relationship('Notification', cascade='all')
613 user_created_notifications = relationship('Notification', cascade='all')
614 # comments created by this user
614 # comments created by this user
615 user_comments = relationship('ChangesetComment', cascade='all')
615 user_comments = relationship('ChangesetComment', cascade='all')
616 # user profile extra info
616 # user profile extra info
617 user_emails = relationship('UserEmailMap', cascade='all')
617 user_emails = relationship('UserEmailMap', cascade='all')
618 user_ip_map = relationship('UserIpMap', cascade='all')
618 user_ip_map = relationship('UserIpMap', cascade='all')
619 user_auth_tokens = relationship('UserApiKeys', cascade='all')
619 user_auth_tokens = relationship('UserApiKeys', cascade='all')
620 user_ssh_keys = relationship('UserSshKeys', cascade='all')
620 user_ssh_keys = relationship('UserSshKeys', cascade='all')
621
621
622 # gists
622 # gists
623 user_gists = relationship('Gist', cascade='all')
623 user_gists = relationship('Gist', cascade='all')
624 # user pull requests
624 # user pull requests
625 user_pull_requests = relationship('PullRequest', cascade='all')
625 user_pull_requests = relationship('PullRequest', cascade='all')
626
626
627 # external identities
627 # external identities
628 external_identities = relationship(
628 external_identities = relationship(
629 'ExternalIdentity',
629 'ExternalIdentity',
630 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
630 primaryjoin="User.user_id==ExternalIdentity.local_user_id",
631 cascade='all')
631 cascade='all')
632 # review rules
632 # review rules
633 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
633 user_review_rules = relationship('RepoReviewRuleUser', cascade='all')
634
634
635 # artifacts owned
635 # artifacts owned
636 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
636 artifacts = relationship('FileStore', primaryjoin='FileStore.user_id==User.user_id')
637
637
638 # no cascade, set NULL
638 # no cascade, set NULL
639 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
639 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_user_id==User.user_id')
640
640
641 def __unicode__(self):
641 def __unicode__(self):
642 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
642 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
643 self.user_id, self.username)
643 self.user_id, self.username)
644
644
645 @hybrid_property
645 @hybrid_property
646 def email(self):
646 def email(self):
647 return self._email
647 return self._email
648
648
649 @email.setter
649 @email.setter
650 def email(self, val):
650 def email(self, val):
651 self._email = val.lower() if val else None
651 self._email = val.lower() if val else None
652
652
653 @hybrid_property
653 @hybrid_property
654 def first_name(self):
654 def first_name(self):
655 from rhodecode.lib import helpers as h
655 from rhodecode.lib import helpers as h
656 if self.name:
656 if self.name:
657 return h.escape(self.name)
657 return h.escape(self.name)
658 return self.name
658 return self.name
659
659
660 @hybrid_property
660 @hybrid_property
661 def last_name(self):
661 def last_name(self):
662 from rhodecode.lib import helpers as h
662 from rhodecode.lib import helpers as h
663 if self.lastname:
663 if self.lastname:
664 return h.escape(self.lastname)
664 return h.escape(self.lastname)
665 return self.lastname
665 return self.lastname
666
666
667 @hybrid_property
667 @hybrid_property
668 def api_key(self):
668 def api_key(self):
669 """
669 """
670 Fetch if exist an auth-token with role ALL connected to this user
670 Fetch if exist an auth-token with role ALL connected to this user
671 """
671 """
672 user_auth_token = UserApiKeys.query()\
672 user_auth_token = UserApiKeys.query()\
673 .filter(UserApiKeys.user_id == self.user_id)\
673 .filter(UserApiKeys.user_id == self.user_id)\
674 .filter(or_(UserApiKeys.expires == -1,
674 .filter(or_(UserApiKeys.expires == -1,
675 UserApiKeys.expires >= time.time()))\
675 UserApiKeys.expires >= time.time()))\
676 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
676 .filter(UserApiKeys.role == UserApiKeys.ROLE_ALL).first()
677 if user_auth_token:
677 if user_auth_token:
678 user_auth_token = user_auth_token.api_key
678 user_auth_token = user_auth_token.api_key
679
679
680 return user_auth_token
680 return user_auth_token
681
681
682 @api_key.setter
682 @api_key.setter
683 def api_key(self, val):
683 def api_key(self, val):
684 # don't allow to set API key this is deprecated for now
684 # don't allow to set API key this is deprecated for now
685 self._api_key = None
685 self._api_key = None
686
686
687 @property
687 @property
688 def reviewer_pull_requests(self):
688 def reviewer_pull_requests(self):
689 return PullRequestReviewers.query() \
689 return PullRequestReviewers.query() \
690 .options(joinedload(PullRequestReviewers.pull_request)) \
690 .options(joinedload(PullRequestReviewers.pull_request)) \
691 .filter(PullRequestReviewers.user_id == self.user_id) \
691 .filter(PullRequestReviewers.user_id == self.user_id) \
692 .all()
692 .all()
693
693
694 @property
694 @property
695 def firstname(self):
695 def firstname(self):
696 # alias for future
696 # alias for future
697 return self.name
697 return self.name
698
698
699 @property
699 @property
700 def emails(self):
700 def emails(self):
701 other = UserEmailMap.query()\
701 other = UserEmailMap.query()\
702 .filter(UserEmailMap.user == self) \
702 .filter(UserEmailMap.user == self) \
703 .order_by(UserEmailMap.email_id.asc()) \
703 .order_by(UserEmailMap.email_id.asc()) \
704 .all()
704 .all()
705 return [self.email] + [x.email for x in other]
705 return [self.email] + [x.email for x in other]
706
706
707 def emails_cached(self):
707 def emails_cached(self):
708 emails = UserEmailMap.query()\
708 emails = UserEmailMap.query()\
709 .filter(UserEmailMap.user == self) \
709 .filter(UserEmailMap.user == self) \
710 .order_by(UserEmailMap.email_id.asc())
710 .order_by(UserEmailMap.email_id.asc())
711
711
712 emails = emails.options(
712 emails = emails.options(
713 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
713 FromCache("sql_cache_short", "get_user_{}_emails".format(self.user_id))
714 )
714 )
715
715
716 return [self.email] + [x.email for x in emails]
716 return [self.email] + [x.email for x in emails]
717
717
718 @property
718 @property
719 def auth_tokens(self):
719 def auth_tokens(self):
720 auth_tokens = self.get_auth_tokens()
720 auth_tokens = self.get_auth_tokens()
721 return [x.api_key for x in auth_tokens]
721 return [x.api_key for x in auth_tokens]
722
722
723 def get_auth_tokens(self):
723 def get_auth_tokens(self):
724 return UserApiKeys.query()\
724 return UserApiKeys.query()\
725 .filter(UserApiKeys.user == self)\
725 .filter(UserApiKeys.user == self)\
726 .order_by(UserApiKeys.user_api_key_id.asc())\
726 .order_by(UserApiKeys.user_api_key_id.asc())\
727 .all()
727 .all()
728
728
729 @LazyProperty
729 @LazyProperty
730 def feed_token(self):
730 def feed_token(self):
731 return self.get_feed_token()
731 return self.get_feed_token()
732
732
733 def get_feed_token(self, cache=True):
733 def get_feed_token(self, cache=True):
734 feed_tokens = UserApiKeys.query()\
734 feed_tokens = UserApiKeys.query()\
735 .filter(UserApiKeys.user == self)\
735 .filter(UserApiKeys.user == self)\
736 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
736 .filter(UserApiKeys.role == UserApiKeys.ROLE_FEED)
737 if cache:
737 if cache:
738 feed_tokens = feed_tokens.options(
738 feed_tokens = feed_tokens.options(
739 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
739 FromCache("sql_cache_short", "get_user_feed_token_%s" % self.user_id))
740
740
741 feed_tokens = feed_tokens.all()
741 feed_tokens = feed_tokens.all()
742 if feed_tokens:
742 if feed_tokens:
743 return feed_tokens[0].api_key
743 return feed_tokens[0].api_key
744 return 'NO_FEED_TOKEN_AVAILABLE'
744 return 'NO_FEED_TOKEN_AVAILABLE'
745
745
746 @LazyProperty
746 @LazyProperty
747 def artifact_token(self):
747 def artifact_token(self):
748 return self.get_artifact_token()
748 return self.get_artifact_token()
749
749
750 def get_artifact_token(self, cache=True):
750 def get_artifact_token(self, cache=True):
751 artifacts_tokens = UserApiKeys.query()\
751 artifacts_tokens = UserApiKeys.query()\
752 .filter(UserApiKeys.user == self)\
752 .filter(UserApiKeys.user == self)\
753 .filter(or_(UserApiKeys.expires == -1,
754 UserApiKeys.expires >= time.time())) \
753 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
755 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
756
754 if cache:
757 if cache:
755 artifacts_tokens = artifacts_tokens.options(
758 artifacts_tokens = artifacts_tokens.options(
756 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
759 FromCache("sql_cache_short", "get_user_artifact_token_%s" % self.user_id))
757
760
758 artifacts_tokens = artifacts_tokens.all()
761 artifacts_tokens = artifacts_tokens.all()
759 if artifacts_tokens:
762 if artifacts_tokens:
760 return artifacts_tokens[0].api_key
763 return artifacts_tokens[0].api_key
761 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
764 return 'NO_ARTIFACT_TOKEN_AVAILABLE'
762
765
766 def get_or_create_artifact_token(self):
767 artifacts_tokens = UserApiKeys.query()\
768 .filter(UserApiKeys.user == self) \
769 .filter(or_(UserApiKeys.expires == -1,
770 UserApiKeys.expires >= time.time())) \
771 .filter(UserApiKeys.role == UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
772
773 artifacts_tokens = artifacts_tokens.all()
774 if artifacts_tokens:
775 return artifacts_tokens[0].api_key
776 else:
777 from rhodecode.model.auth_token import AuthTokenModel
778 artifact_token = AuthTokenModel().create(
779 self, 'auto-generated-artifact-token',
780 lifetime=-1, role=UserApiKeys.ROLE_ARTIFACT_DOWNLOAD)
781 Session.commit()
782 return artifact_token.api_key
783
763 @classmethod
784 @classmethod
764 def get(cls, user_id, cache=False):
785 def get(cls, user_id, cache=False):
765 if not user_id:
786 if not user_id:
766 return
787 return
767
788
768 user = cls.query()
789 user = cls.query()
769 if cache:
790 if cache:
770 user = user.options(
791 user = user.options(
771 FromCache("sql_cache_short", "get_users_%s" % user_id))
792 FromCache("sql_cache_short", "get_users_%s" % user_id))
772 return user.get(user_id)
793 return user.get(user_id)
773
794
774 @classmethod
795 @classmethod
775 def extra_valid_auth_tokens(cls, user, role=None):
796 def extra_valid_auth_tokens(cls, user, role=None):
776 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
797 tokens = UserApiKeys.query().filter(UserApiKeys.user == user)\
777 .filter(or_(UserApiKeys.expires == -1,
798 .filter(or_(UserApiKeys.expires == -1,
778 UserApiKeys.expires >= time.time()))
799 UserApiKeys.expires >= time.time()))
779 if role:
800 if role:
780 tokens = tokens.filter(or_(UserApiKeys.role == role,
801 tokens = tokens.filter(or_(UserApiKeys.role == role,
781 UserApiKeys.role == UserApiKeys.ROLE_ALL))
802 UserApiKeys.role == UserApiKeys.ROLE_ALL))
782 return tokens.all()
803 return tokens.all()
783
804
784 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
805 def authenticate_by_token(self, auth_token, roles=None, scope_repo_id=None):
785 from rhodecode.lib import auth
806 from rhodecode.lib import auth
786
807
787 log.debug('Trying to authenticate user: %s via auth-token, '
808 log.debug('Trying to authenticate user: %s via auth-token, '
788 'and roles: %s', self, roles)
809 'and roles: %s', self, roles)
789
810
790 if not auth_token:
811 if not auth_token:
791 return False
812 return False
792
813
793 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
814 roles = (roles or []) + [UserApiKeys.ROLE_ALL]
794 tokens_q = UserApiKeys.query()\
815 tokens_q = UserApiKeys.query()\
795 .filter(UserApiKeys.user_id == self.user_id)\
816 .filter(UserApiKeys.user_id == self.user_id)\
796 .filter(or_(UserApiKeys.expires == -1,
817 .filter(or_(UserApiKeys.expires == -1,
797 UserApiKeys.expires >= time.time()))
818 UserApiKeys.expires >= time.time()))
798
819
799 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
820 tokens_q = tokens_q.filter(UserApiKeys.role.in_(roles))
800
821
801 crypto_backend = auth.crypto_backend()
822 crypto_backend = auth.crypto_backend()
802 enc_token_map = {}
823 enc_token_map = {}
803 plain_token_map = {}
824 plain_token_map = {}
804 for token in tokens_q:
825 for token in tokens_q:
805 if token.api_key.startswith(crypto_backend.ENC_PREF):
826 if token.api_key.startswith(crypto_backend.ENC_PREF):
806 enc_token_map[token.api_key] = token
827 enc_token_map[token.api_key] = token
807 else:
828 else:
808 plain_token_map[token.api_key] = token
829 plain_token_map[token.api_key] = token
809 log.debug(
830 log.debug(
810 'Found %s plain and %s encrypted tokens to check for authentication for this user',
831 'Found %s plain and %s encrypted tokens to check for authentication for this user',
811 len(plain_token_map), len(enc_token_map))
832 len(plain_token_map), len(enc_token_map))
812
833
813 # plain token match comes first
834 # plain token match comes first
814 match = plain_token_map.get(auth_token)
835 match = plain_token_map.get(auth_token)
815
836
816 # check encrypted tokens now
837 # check encrypted tokens now
817 if not match:
838 if not match:
818 for token_hash, token in enc_token_map.items():
839 for token_hash, token in enc_token_map.items():
819 # NOTE(marcink): this is expensive to calculate, but most secure
840 # NOTE(marcink): this is expensive to calculate, but most secure
820 if crypto_backend.hash_check(auth_token, token_hash):
841 if crypto_backend.hash_check(auth_token, token_hash):
821 match = token
842 match = token
822 break
843 break
823
844
824 if match:
845 if match:
825 log.debug('Found matching token %s', match)
846 log.debug('Found matching token %s', match)
826 if match.repo_id:
847 if match.repo_id:
827 log.debug('Found scope, checking for scope match of token %s', match)
848 log.debug('Found scope, checking for scope match of token %s', match)
828 if match.repo_id == scope_repo_id:
849 if match.repo_id == scope_repo_id:
829 return True
850 return True
830 else:
851 else:
831 log.debug(
852 log.debug(
832 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
853 'AUTH_TOKEN: scope mismatch, token has a set repo scope: %s, '
833 'and calling scope is:%s, skipping further checks',
854 'and calling scope is:%s, skipping further checks',
834 match.repo, scope_repo_id)
855 match.repo, scope_repo_id)
835 return False
856 return False
836 else:
857 else:
837 return True
858 return True
838
859
839 return False
860 return False
840
861
841 @property
862 @property
842 def ip_addresses(self):
863 def ip_addresses(self):
843 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
864 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
844 return [x.ip_addr for x in ret]
865 return [x.ip_addr for x in ret]
845
866
846 @property
867 @property
847 def username_and_name(self):
868 def username_and_name(self):
848 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
869 return '%s (%s %s)' % (self.username, self.first_name, self.last_name)
849
870
850 @property
871 @property
851 def username_or_name_or_email(self):
872 def username_or_name_or_email(self):
852 full_name = self.full_name if self.full_name is not ' ' else None
873 full_name = self.full_name if self.full_name is not ' ' else None
853 return self.username or full_name or self.email
874 return self.username or full_name or self.email
854
875
855 @property
876 @property
856 def full_name(self):
877 def full_name(self):
857 return '%s %s' % (self.first_name, self.last_name)
878 return '%s %s' % (self.first_name, self.last_name)
858
879
859 @property
880 @property
860 def full_name_or_username(self):
881 def full_name_or_username(self):
861 return ('%s %s' % (self.first_name, self.last_name)
882 return ('%s %s' % (self.first_name, self.last_name)
862 if (self.first_name and self.last_name) else self.username)
883 if (self.first_name and self.last_name) else self.username)
863
884
864 @property
885 @property
865 def full_contact(self):
886 def full_contact(self):
866 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
887 return '%s %s <%s>' % (self.first_name, self.last_name, self.email)
867
888
868 @property
889 @property
869 def short_contact(self):
890 def short_contact(self):
870 return '%s %s' % (self.first_name, self.last_name)
891 return '%s %s' % (self.first_name, self.last_name)
871
892
872 @property
893 @property
873 def is_admin(self):
894 def is_admin(self):
874 return self.admin
895 return self.admin
875
896
876 @property
897 @property
877 def language(self):
898 def language(self):
878 return self.user_data.get('language')
899 return self.user_data.get('language')
879
900
880 def AuthUser(self, **kwargs):
901 def AuthUser(self, **kwargs):
881 """
902 """
882 Returns instance of AuthUser for this user
903 Returns instance of AuthUser for this user
883 """
904 """
884 from rhodecode.lib.auth import AuthUser
905 from rhodecode.lib.auth import AuthUser
885 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
906 return AuthUser(user_id=self.user_id, username=self.username, **kwargs)
886
907
887 @hybrid_property
908 @hybrid_property
888 def user_data(self):
909 def user_data(self):
889 if not self._user_data:
910 if not self._user_data:
890 return {}
911 return {}
891
912
892 try:
913 try:
893 return json.loads(self._user_data)
914 return json.loads(self._user_data)
894 except TypeError:
915 except TypeError:
895 return {}
916 return {}
896
917
897 @user_data.setter
918 @user_data.setter
898 def user_data(self, val):
919 def user_data(self, val):
899 if not isinstance(val, dict):
920 if not isinstance(val, dict):
900 raise Exception('user_data must be dict, got %s' % type(val))
921 raise Exception('user_data must be dict, got %s' % type(val))
901 try:
922 try:
902 self._user_data = json.dumps(val)
923 self._user_data = json.dumps(val)
903 except Exception:
924 except Exception:
904 log.error(traceback.format_exc())
925 log.error(traceback.format_exc())
905
926
906 @classmethod
927 @classmethod
907 def get_by_username(cls, username, case_insensitive=False,
928 def get_by_username(cls, username, case_insensitive=False,
908 cache=False, identity_cache=False):
929 cache=False, identity_cache=False):
909 session = Session()
930 session = Session()
910
931
911 if case_insensitive:
932 if case_insensitive:
912 q = cls.query().filter(
933 q = cls.query().filter(
913 func.lower(cls.username) == func.lower(username))
934 func.lower(cls.username) == func.lower(username))
914 else:
935 else:
915 q = cls.query().filter(cls.username == username)
936 q = cls.query().filter(cls.username == username)
916
937
917 if cache:
938 if cache:
918 if identity_cache:
939 if identity_cache:
919 val = cls.identity_cache(session, 'username', username)
940 val = cls.identity_cache(session, 'username', username)
920 if val:
941 if val:
921 return val
942 return val
922 else:
943 else:
923 cache_key = "get_user_by_name_%s" % _hash_key(username)
944 cache_key = "get_user_by_name_%s" % _hash_key(username)
924 q = q.options(
945 q = q.options(
925 FromCache("sql_cache_short", cache_key))
946 FromCache("sql_cache_short", cache_key))
926
947
927 return q.scalar()
948 return q.scalar()
928
949
929 @classmethod
950 @classmethod
930 def get_by_auth_token(cls, auth_token, cache=False):
951 def get_by_auth_token(cls, auth_token, cache=False):
931 q = UserApiKeys.query()\
952 q = UserApiKeys.query()\
932 .filter(UserApiKeys.api_key == auth_token)\
953 .filter(UserApiKeys.api_key == auth_token)\
933 .filter(or_(UserApiKeys.expires == -1,
954 .filter(or_(UserApiKeys.expires == -1,
934 UserApiKeys.expires >= time.time()))
955 UserApiKeys.expires >= time.time()))
935 if cache:
956 if cache:
936 q = q.options(
957 q = q.options(
937 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
958 FromCache("sql_cache_short", "get_auth_token_%s" % auth_token))
938
959
939 match = q.first()
960 match = q.first()
940 if match:
961 if match:
941 return match.user
962 return match.user
942
963
943 @classmethod
964 @classmethod
944 def get_by_email(cls, email, case_insensitive=False, cache=False):
965 def get_by_email(cls, email, case_insensitive=False, cache=False):
945
966
946 if case_insensitive:
967 if case_insensitive:
947 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
968 q = cls.query().filter(func.lower(cls.email) == func.lower(email))
948
969
949 else:
970 else:
950 q = cls.query().filter(cls.email == email)
971 q = cls.query().filter(cls.email == email)
951
972
952 email_key = _hash_key(email)
973 email_key = _hash_key(email)
953 if cache:
974 if cache:
954 q = q.options(
975 q = q.options(
955 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
976 FromCache("sql_cache_short", "get_email_key_%s" % email_key))
956
977
957 ret = q.scalar()
978 ret = q.scalar()
958 if ret is None:
979 if ret is None:
959 q = UserEmailMap.query()
980 q = UserEmailMap.query()
960 # try fetching in alternate email map
981 # try fetching in alternate email map
961 if case_insensitive:
982 if case_insensitive:
962 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
983 q = q.filter(func.lower(UserEmailMap.email) == func.lower(email))
963 else:
984 else:
964 q = q.filter(UserEmailMap.email == email)
985 q = q.filter(UserEmailMap.email == email)
965 q = q.options(joinedload(UserEmailMap.user))
986 q = q.options(joinedload(UserEmailMap.user))
966 if cache:
987 if cache:
967 q = q.options(
988 q = q.options(
968 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
989 FromCache("sql_cache_short", "get_email_map_key_%s" % email_key))
969 ret = getattr(q.scalar(), 'user', None)
990 ret = getattr(q.scalar(), 'user', None)
970
991
971 return ret
992 return ret
972
993
973 @classmethod
994 @classmethod
974 def get_from_cs_author(cls, author):
995 def get_from_cs_author(cls, author):
975 """
996 """
976 Tries to get User objects out of commit author string
997 Tries to get User objects out of commit author string
977
998
978 :param author:
999 :param author:
979 """
1000 """
980 from rhodecode.lib.helpers import email, author_name
1001 from rhodecode.lib.helpers import email, author_name
981 # Valid email in the attribute passed, see if they're in the system
1002 # Valid email in the attribute passed, see if they're in the system
982 _email = email(author)
1003 _email = email(author)
983 if _email:
1004 if _email:
984 user = cls.get_by_email(_email, case_insensitive=True)
1005 user = cls.get_by_email(_email, case_insensitive=True)
985 if user:
1006 if user:
986 return user
1007 return user
987 # Maybe we can match by username?
1008 # Maybe we can match by username?
988 _author = author_name(author)
1009 _author = author_name(author)
989 user = cls.get_by_username(_author, case_insensitive=True)
1010 user = cls.get_by_username(_author, case_insensitive=True)
990 if user:
1011 if user:
991 return user
1012 return user
992
1013
993 def update_userdata(self, **kwargs):
1014 def update_userdata(self, **kwargs):
994 usr = self
1015 usr = self
995 old = usr.user_data
1016 old = usr.user_data
996 old.update(**kwargs)
1017 old.update(**kwargs)
997 usr.user_data = old
1018 usr.user_data = old
998 Session().add(usr)
1019 Session().add(usr)
999 log.debug('updated userdata with %s', kwargs)
1020 log.debug('updated userdata with %s', kwargs)
1000
1021
1001 def update_lastlogin(self):
1022 def update_lastlogin(self):
1002 """Update user lastlogin"""
1023 """Update user lastlogin"""
1003 self.last_login = datetime.datetime.now()
1024 self.last_login = datetime.datetime.now()
1004 Session().add(self)
1025 Session().add(self)
1005 log.debug('updated user %s lastlogin', self.username)
1026 log.debug('updated user %s lastlogin', self.username)
1006
1027
1007 def update_password(self, new_password):
1028 def update_password(self, new_password):
1008 from rhodecode.lib.auth import get_crypt_password
1029 from rhodecode.lib.auth import get_crypt_password
1009
1030
1010 self.password = get_crypt_password(new_password)
1031 self.password = get_crypt_password(new_password)
1011 Session().add(self)
1032 Session().add(self)
1012
1033
1013 @classmethod
1034 @classmethod
1014 def get_first_super_admin(cls):
1035 def get_first_super_admin(cls):
1015 user = User.query()\
1036 user = User.query()\
1016 .filter(User.admin == true()) \
1037 .filter(User.admin == true()) \
1017 .order_by(User.user_id.asc()) \
1038 .order_by(User.user_id.asc()) \
1018 .first()
1039 .first()
1019
1040
1020 if user is None:
1041 if user is None:
1021 raise Exception('FATAL: Missing administrative account!')
1042 raise Exception('FATAL: Missing administrative account!')
1022 return user
1043 return user
1023
1044
1024 @classmethod
1045 @classmethod
1025 def get_all_super_admins(cls, only_active=False):
1046 def get_all_super_admins(cls, only_active=False):
1026 """
1047 """
1027 Returns all admin accounts sorted by username
1048 Returns all admin accounts sorted by username
1028 """
1049 """
1029 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1050 qry = User.query().filter(User.admin == true()).order_by(User.username.asc())
1030 if only_active:
1051 if only_active:
1031 qry = qry.filter(User.active == true())
1052 qry = qry.filter(User.active == true())
1032 return qry.all()
1053 return qry.all()
1033
1054
1034 @classmethod
1055 @classmethod
1035 def get_all_user_ids(cls, only_active=True):
1056 def get_all_user_ids(cls, only_active=True):
1036 """
1057 """
1037 Returns all users IDs
1058 Returns all users IDs
1038 """
1059 """
1039 qry = Session().query(User.user_id)
1060 qry = Session().query(User.user_id)
1040
1061
1041 if only_active:
1062 if only_active:
1042 qry = qry.filter(User.active == true())
1063 qry = qry.filter(User.active == true())
1043 return [x.user_id for x in qry]
1064 return [x.user_id for x in qry]
1044
1065
1045 @classmethod
1066 @classmethod
1046 def get_default_user(cls, cache=False, refresh=False):
1067 def get_default_user(cls, cache=False, refresh=False):
1047 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1068 user = User.get_by_username(User.DEFAULT_USER, cache=cache)
1048 if user is None:
1069 if user is None:
1049 raise Exception('FATAL: Missing default account!')
1070 raise Exception('FATAL: Missing default account!')
1050 if refresh:
1071 if refresh:
1051 # The default user might be based on outdated state which
1072 # The default user might be based on outdated state which
1052 # has been loaded from the cache.
1073 # has been loaded from the cache.
1053 # A call to refresh() ensures that the
1074 # A call to refresh() ensures that the
1054 # latest state from the database is used.
1075 # latest state from the database is used.
1055 Session().refresh(user)
1076 Session().refresh(user)
1056 return user
1077 return user
1057
1078
1058 @classmethod
1079 @classmethod
1059 def get_default_user_id(cls):
1080 def get_default_user_id(cls):
1060 import rhodecode
1081 import rhodecode
1061 return rhodecode.CONFIG['default_user_id']
1082 return rhodecode.CONFIG['default_user_id']
1062
1083
1063 def _get_default_perms(self, user, suffix=''):
1084 def _get_default_perms(self, user, suffix=''):
1064 from rhodecode.model.permission import PermissionModel
1085 from rhodecode.model.permission import PermissionModel
1065 return PermissionModel().get_default_perms(user.user_perms, suffix)
1086 return PermissionModel().get_default_perms(user.user_perms, suffix)
1066
1087
1067 def get_default_perms(self, suffix=''):
1088 def get_default_perms(self, suffix=''):
1068 return self._get_default_perms(self, suffix)
1089 return self._get_default_perms(self, suffix)
1069
1090
1070 def get_api_data(self, include_secrets=False, details='full'):
1091 def get_api_data(self, include_secrets=False, details='full'):
1071 """
1092 """
1072 Common function for generating user related data for API
1093 Common function for generating user related data for API
1073
1094
1074 :param include_secrets: By default secrets in the API data will be replaced
1095 :param include_secrets: By default secrets in the API data will be replaced
1075 by a placeholder value to prevent exposing this data by accident. In case
1096 by a placeholder value to prevent exposing this data by accident. In case
1076 this data shall be exposed, set this flag to ``True``.
1097 this data shall be exposed, set this flag to ``True``.
1077
1098
1078 :param details: details can be 'basic|full' basic gives only a subset of
1099 :param details: details can be 'basic|full' basic gives only a subset of
1079 the available user information that includes user_id, name and emails.
1100 the available user information that includes user_id, name and emails.
1080 """
1101 """
1081 user = self
1102 user = self
1082 user_data = self.user_data
1103 user_data = self.user_data
1083 data = {
1104 data = {
1084 'user_id': user.user_id,
1105 'user_id': user.user_id,
1085 'username': user.username,
1106 'username': user.username,
1086 'firstname': user.name,
1107 'firstname': user.name,
1087 'lastname': user.lastname,
1108 'lastname': user.lastname,
1088 'description': user.description,
1109 'description': user.description,
1089 'email': user.email,
1110 'email': user.email,
1090 'emails': user.emails,
1111 'emails': user.emails,
1091 }
1112 }
1092 if details == 'basic':
1113 if details == 'basic':
1093 return data
1114 return data
1094
1115
1095 auth_token_length = 40
1116 auth_token_length = 40
1096 auth_token_replacement = '*' * auth_token_length
1117 auth_token_replacement = '*' * auth_token_length
1097
1118
1098 extras = {
1119 extras = {
1099 'auth_tokens': [auth_token_replacement],
1120 'auth_tokens': [auth_token_replacement],
1100 'active': user.active,
1121 'active': user.active,
1101 'admin': user.admin,
1122 'admin': user.admin,
1102 'extern_type': user.extern_type,
1123 'extern_type': user.extern_type,
1103 'extern_name': user.extern_name,
1124 'extern_name': user.extern_name,
1104 'last_login': user.last_login,
1125 'last_login': user.last_login,
1105 'last_activity': user.last_activity,
1126 'last_activity': user.last_activity,
1106 'ip_addresses': user.ip_addresses,
1127 'ip_addresses': user.ip_addresses,
1107 'language': user_data.get('language')
1128 'language': user_data.get('language')
1108 }
1129 }
1109 data.update(extras)
1130 data.update(extras)
1110
1131
1111 if include_secrets:
1132 if include_secrets:
1112 data['auth_tokens'] = user.auth_tokens
1133 data['auth_tokens'] = user.auth_tokens
1113 return data
1134 return data
1114
1135
1115 def __json__(self):
1136 def __json__(self):
1116 data = {
1137 data = {
1117 'full_name': self.full_name,
1138 'full_name': self.full_name,
1118 'full_name_or_username': self.full_name_or_username,
1139 'full_name_or_username': self.full_name_or_username,
1119 'short_contact': self.short_contact,
1140 'short_contact': self.short_contact,
1120 'full_contact': self.full_contact,
1141 'full_contact': self.full_contact,
1121 }
1142 }
1122 data.update(self.get_api_data())
1143 data.update(self.get_api_data())
1123 return data
1144 return data
1124
1145
1125
1146
1126 class UserApiKeys(Base, BaseModel):
1147 class UserApiKeys(Base, BaseModel):
1127 __tablename__ = 'user_api_keys'
1148 __tablename__ = 'user_api_keys'
1128 __table_args__ = (
1149 __table_args__ = (
1129 Index('uak_api_key_idx', 'api_key'),
1150 Index('uak_api_key_idx', 'api_key'),
1130 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1151 Index('uak_api_key_expires_idx', 'api_key', 'expires'),
1131 base_table_args
1152 base_table_args
1132 )
1153 )
1133 __mapper_args__ = {}
1154 __mapper_args__ = {}
1134
1155
1135 # ApiKey role
1156 # ApiKey role
1136 ROLE_ALL = 'token_role_all'
1157 ROLE_ALL = 'token_role_all'
1137 ROLE_VCS = 'token_role_vcs'
1158 ROLE_VCS = 'token_role_vcs'
1138 ROLE_API = 'token_role_api'
1159 ROLE_API = 'token_role_api'
1139 ROLE_HTTP = 'token_role_http'
1160 ROLE_HTTP = 'token_role_http'
1140 ROLE_FEED = 'token_role_feed'
1161 ROLE_FEED = 'token_role_feed'
1141 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1162 ROLE_ARTIFACT_DOWNLOAD = 'role_artifact_download'
1142 # The last one is ignored in the list as we only
1163 # The last one is ignored in the list as we only
1143 # use it for one action, and cannot be created by users
1164 # use it for one action, and cannot be created by users
1144 ROLE_PASSWORD_RESET = 'token_password_reset'
1165 ROLE_PASSWORD_RESET = 'token_password_reset'
1145
1166
1146 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1167 ROLES = [ROLE_ALL, ROLE_VCS, ROLE_API, ROLE_HTTP, ROLE_FEED, ROLE_ARTIFACT_DOWNLOAD]
1147
1168
1148 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1169 user_api_key_id = Column("user_api_key_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1149 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1170 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1150 api_key = Column("api_key", String(255), nullable=False, unique=True)
1171 api_key = Column("api_key", String(255), nullable=False, unique=True)
1151 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1172 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1152 expires = Column('expires', Float(53), nullable=False)
1173 expires = Column('expires', Float(53), nullable=False)
1153 role = Column('role', String(255), nullable=True)
1174 role = Column('role', String(255), nullable=True)
1154 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1175 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1155
1176
1156 # scope columns
1177 # scope columns
1157 repo_id = Column(
1178 repo_id = Column(
1158 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1179 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
1159 nullable=True, unique=None, default=None)
1180 nullable=True, unique=None, default=None)
1160 repo = relationship('Repository', lazy='joined')
1181 repo = relationship('Repository', lazy='joined')
1161
1182
1162 repo_group_id = Column(
1183 repo_group_id = Column(
1163 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1184 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
1164 nullable=True, unique=None, default=None)
1185 nullable=True, unique=None, default=None)
1165 repo_group = relationship('RepoGroup', lazy='joined')
1186 repo_group = relationship('RepoGroup', lazy='joined')
1166
1187
1167 user = relationship('User', lazy='joined')
1188 user = relationship('User', lazy='joined')
1168
1189
1169 def __unicode__(self):
1190 def __unicode__(self):
1170 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1191 return u"<%s('%s')>" % (self.__class__.__name__, self.role)
1171
1192
1172 def __json__(self):
1193 def __json__(self):
1173 data = {
1194 data = {
1174 'auth_token': self.api_key,
1195 'auth_token': self.api_key,
1175 'role': self.role,
1196 'role': self.role,
1176 'scope': self.scope_humanized,
1197 'scope': self.scope_humanized,
1177 'expired': self.expired
1198 'expired': self.expired
1178 }
1199 }
1179 return data
1200 return data
1180
1201
1181 def get_api_data(self, include_secrets=False):
1202 def get_api_data(self, include_secrets=False):
1182 data = self.__json__()
1203 data = self.__json__()
1183 if include_secrets:
1204 if include_secrets:
1184 return data
1205 return data
1185 else:
1206 else:
1186 data['auth_token'] = self.token_obfuscated
1207 data['auth_token'] = self.token_obfuscated
1187 return data
1208 return data
1188
1209
1189 @hybrid_property
1210 @hybrid_property
1190 def description_safe(self):
1211 def description_safe(self):
1191 from rhodecode.lib import helpers as h
1212 from rhodecode.lib import helpers as h
1192 return h.escape(self.description)
1213 return h.escape(self.description)
1193
1214
1194 @property
1215 @property
1195 def expired(self):
1216 def expired(self):
1196 if self.expires == -1:
1217 if self.expires == -1:
1197 return False
1218 return False
1198 return time.time() > self.expires
1219 return time.time() > self.expires
1199
1220
1200 @classmethod
1221 @classmethod
1201 def _get_role_name(cls, role):
1222 def _get_role_name(cls, role):
1202 return {
1223 return {
1203 cls.ROLE_ALL: _('all'),
1224 cls.ROLE_ALL: _('all'),
1204 cls.ROLE_HTTP: _('http/web interface'),
1225 cls.ROLE_HTTP: _('http/web interface'),
1205 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1226 cls.ROLE_VCS: _('vcs (git/hg/svn protocol)'),
1206 cls.ROLE_API: _('api calls'),
1227 cls.ROLE_API: _('api calls'),
1207 cls.ROLE_FEED: _('feed access'),
1228 cls.ROLE_FEED: _('feed access'),
1208 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1229 cls.ROLE_ARTIFACT_DOWNLOAD: _('artifacts downloads'),
1209 }.get(role, role)
1230 }.get(role, role)
1210
1231
1211 @classmethod
1232 @classmethod
1212 def _get_role_description(cls, role):
1233 def _get_role_description(cls, role):
1213 return {
1234 return {
1214 cls.ROLE_ALL: _('Token for all actions.'),
1235 cls.ROLE_ALL: _('Token for all actions.'),
1215 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1236 cls.ROLE_HTTP: _('Token to access RhodeCode pages via web interface without '
1216 'login using `api_access_controllers_whitelist` functionality.'),
1237 'login using `api_access_controllers_whitelist` functionality.'),
1217 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1238 cls.ROLE_VCS: _('Token to interact over git/hg/svn protocols. '
1218 'Requires auth_token authentication plugin to be active. <br/>'
1239 'Requires auth_token authentication plugin to be active. <br/>'
1219 'Such Token should be used then instead of a password to '
1240 'Such Token should be used then instead of a password to '
1220 'interact with a repository, and additionally can be '
1241 'interact with a repository, and additionally can be '
1221 'limited to single repository using repo scope.'),
1242 'limited to single repository using repo scope.'),
1222 cls.ROLE_API: _('Token limited to api calls.'),
1243 cls.ROLE_API: _('Token limited to api calls.'),
1223 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1244 cls.ROLE_FEED: _('Token to read RSS/ATOM feed.'),
1224 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1245 cls.ROLE_ARTIFACT_DOWNLOAD: _('Token for artifacts downloads.'),
1225 }.get(role, role)
1246 }.get(role, role)
1226
1247
1227 @property
1248 @property
1228 def role_humanized(self):
1249 def role_humanized(self):
1229 return self._get_role_name(self.role)
1250 return self._get_role_name(self.role)
1230
1251
1231 def _get_scope(self):
1252 def _get_scope(self):
1232 if self.repo:
1253 if self.repo:
1233 return 'Repository: {}'.format(self.repo.repo_name)
1254 return 'Repository: {}'.format(self.repo.repo_name)
1234 if self.repo_group:
1255 if self.repo_group:
1235 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1256 return 'RepositoryGroup: {} (recursive)'.format(self.repo_group.group_name)
1236 return 'Global'
1257 return 'Global'
1237
1258
1238 @property
1259 @property
1239 def scope_humanized(self):
1260 def scope_humanized(self):
1240 return self._get_scope()
1261 return self._get_scope()
1241
1262
1242 @property
1263 @property
1243 def token_obfuscated(self):
1264 def token_obfuscated(self):
1244 if self.api_key:
1265 if self.api_key:
1245 return self.api_key[:4] + "****"
1266 return self.api_key[:4] + "****"
1246
1267
1247
1268
1248 class UserEmailMap(Base, BaseModel):
1269 class UserEmailMap(Base, BaseModel):
1249 __tablename__ = 'user_email_map'
1270 __tablename__ = 'user_email_map'
1250 __table_args__ = (
1271 __table_args__ = (
1251 Index('uem_email_idx', 'email'),
1272 Index('uem_email_idx', 'email'),
1252 UniqueConstraint('email'),
1273 UniqueConstraint('email'),
1253 base_table_args
1274 base_table_args
1254 )
1275 )
1255 __mapper_args__ = {}
1276 __mapper_args__ = {}
1256
1277
1257 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1278 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1258 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1279 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1259 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1280 _email = Column("email", String(255), nullable=True, unique=False, default=None)
1260 user = relationship('User', lazy='joined')
1281 user = relationship('User', lazy='joined')
1261
1282
1262 @validates('_email')
1283 @validates('_email')
1263 def validate_email(self, key, email):
1284 def validate_email(self, key, email):
1264 # check if this email is not main one
1285 # check if this email is not main one
1265 main_email = Session().query(User).filter(User.email == email).scalar()
1286 main_email = Session().query(User).filter(User.email == email).scalar()
1266 if main_email is not None:
1287 if main_email is not None:
1267 raise AttributeError('email %s is present is user table' % email)
1288 raise AttributeError('email %s is present is user table' % email)
1268 return email
1289 return email
1269
1290
1270 @hybrid_property
1291 @hybrid_property
1271 def email(self):
1292 def email(self):
1272 return self._email
1293 return self._email
1273
1294
1274 @email.setter
1295 @email.setter
1275 def email(self, val):
1296 def email(self, val):
1276 self._email = val.lower() if val else None
1297 self._email = val.lower() if val else None
1277
1298
1278
1299
1279 class UserIpMap(Base, BaseModel):
1300 class UserIpMap(Base, BaseModel):
1280 __tablename__ = 'user_ip_map'
1301 __tablename__ = 'user_ip_map'
1281 __table_args__ = (
1302 __table_args__ = (
1282 UniqueConstraint('user_id', 'ip_addr'),
1303 UniqueConstraint('user_id', 'ip_addr'),
1283 base_table_args
1304 base_table_args
1284 )
1305 )
1285 __mapper_args__ = {}
1306 __mapper_args__ = {}
1286
1307
1287 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1308 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1288 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1309 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1289 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1310 ip_addr = Column("ip_addr", String(255), nullable=True, unique=False, default=None)
1290 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1311 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
1291 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1312 description = Column("description", String(10000), nullable=True, unique=None, default=None)
1292 user = relationship('User', lazy='joined')
1313 user = relationship('User', lazy='joined')
1293
1314
1294 @hybrid_property
1315 @hybrid_property
1295 def description_safe(self):
1316 def description_safe(self):
1296 from rhodecode.lib import helpers as h
1317 from rhodecode.lib import helpers as h
1297 return h.escape(self.description)
1318 return h.escape(self.description)
1298
1319
1299 @classmethod
1320 @classmethod
1300 def _get_ip_range(cls, ip_addr):
1321 def _get_ip_range(cls, ip_addr):
1301 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1322 net = ipaddress.ip_network(safe_unicode(ip_addr), strict=False)
1302 return [str(net.network_address), str(net.broadcast_address)]
1323 return [str(net.network_address), str(net.broadcast_address)]
1303
1324
1304 def __json__(self):
1325 def __json__(self):
1305 return {
1326 return {
1306 'ip_addr': self.ip_addr,
1327 'ip_addr': self.ip_addr,
1307 'ip_range': self._get_ip_range(self.ip_addr),
1328 'ip_range': self._get_ip_range(self.ip_addr),
1308 }
1329 }
1309
1330
1310 def __unicode__(self):
1331 def __unicode__(self):
1311 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1332 return u"<%s('user_id:%s=>%s')>" % (self.__class__.__name__,
1312 self.user_id, self.ip_addr)
1333 self.user_id, self.ip_addr)
1313
1334
1314
1335
1315 class UserSshKeys(Base, BaseModel):
1336 class UserSshKeys(Base, BaseModel):
1316 __tablename__ = 'user_ssh_keys'
1337 __tablename__ = 'user_ssh_keys'
1317 __table_args__ = (
1338 __table_args__ = (
1318 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1339 Index('usk_ssh_key_fingerprint_idx', 'ssh_key_fingerprint'),
1319
1340
1320 UniqueConstraint('ssh_key_fingerprint'),
1341 UniqueConstraint('ssh_key_fingerprint'),
1321
1342
1322 base_table_args
1343 base_table_args
1323 )
1344 )
1324 __mapper_args__ = {}
1345 __mapper_args__ = {}
1325
1346
1326 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1347 ssh_key_id = Column('ssh_key_id', Integer(), nullable=False, unique=True, default=None, primary_key=True)
1327 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1348 ssh_key_data = Column('ssh_key_data', String(10240), nullable=False, unique=None, default=None)
1328 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1349 ssh_key_fingerprint = Column('ssh_key_fingerprint', String(255), nullable=False, unique=None, default=None)
1329
1350
1330 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1351 description = Column('description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
1331
1352
1332 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1353 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1333 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1354 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True, default=None)
1334 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1355 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1335
1356
1336 user = relationship('User', lazy='joined')
1357 user = relationship('User', lazy='joined')
1337
1358
1338 def __json__(self):
1359 def __json__(self):
1339 data = {
1360 data = {
1340 'ssh_fingerprint': self.ssh_key_fingerprint,
1361 'ssh_fingerprint': self.ssh_key_fingerprint,
1341 'description': self.description,
1362 'description': self.description,
1342 'created_on': self.created_on
1363 'created_on': self.created_on
1343 }
1364 }
1344 return data
1365 return data
1345
1366
1346 def get_api_data(self):
1367 def get_api_data(self):
1347 data = self.__json__()
1368 data = self.__json__()
1348 return data
1369 return data
1349
1370
1350
1371
1351 class UserLog(Base, BaseModel):
1372 class UserLog(Base, BaseModel):
1352 __tablename__ = 'user_logs'
1373 __tablename__ = 'user_logs'
1353 __table_args__ = (
1374 __table_args__ = (
1354 base_table_args,
1375 base_table_args,
1355 )
1376 )
1356
1377
1357 VERSION_1 = 'v1'
1378 VERSION_1 = 'v1'
1358 VERSION_2 = 'v2'
1379 VERSION_2 = 'v2'
1359 VERSIONS = [VERSION_1, VERSION_2]
1380 VERSIONS = [VERSION_1, VERSION_2]
1360
1381
1361 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1382 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1362 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1383 user_id = Column("user_id", Integer(), ForeignKey('users.user_id',ondelete='SET NULL'), nullable=True, unique=None, default=None)
1363 username = Column("username", String(255), nullable=True, unique=None, default=None)
1384 username = Column("username", String(255), nullable=True, unique=None, default=None)
1364 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1385 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id', ondelete='SET NULL'), nullable=True, unique=None, default=None)
1365 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1386 repository_name = Column("repository_name", String(255), nullable=True, unique=None, default=None)
1366 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1387 user_ip = Column("user_ip", String(255), nullable=True, unique=None, default=None)
1367 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1388 action = Column("action", Text().with_variant(Text(1200000), 'mysql'), nullable=True, unique=None, default=None)
1368 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1389 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
1369
1390
1370 version = Column("version", String(255), nullable=True, default=VERSION_1)
1391 version = Column("version", String(255), nullable=True, default=VERSION_1)
1371 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1392 user_data = Column('user_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1372 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1393 action_data = Column('action_data_json', MutationObj.as_mutable(JsonType(dialect_map=dict(mysql=LONGTEXT()))))
1373
1394
1374 def __unicode__(self):
1395 def __unicode__(self):
1375 return u"<%s('id:%s:%s')>" % (
1396 return u"<%s('id:%s:%s')>" % (
1376 self.__class__.__name__, self.repository_name, self.action)
1397 self.__class__.__name__, self.repository_name, self.action)
1377
1398
1378 def __json__(self):
1399 def __json__(self):
1379 return {
1400 return {
1380 'user_id': self.user_id,
1401 'user_id': self.user_id,
1381 'username': self.username,
1402 'username': self.username,
1382 'repository_id': self.repository_id,
1403 'repository_id': self.repository_id,
1383 'repository_name': self.repository_name,
1404 'repository_name': self.repository_name,
1384 'user_ip': self.user_ip,
1405 'user_ip': self.user_ip,
1385 'action_date': self.action_date,
1406 'action_date': self.action_date,
1386 'action': self.action,
1407 'action': self.action,
1387 }
1408 }
1388
1409
1389 @hybrid_property
1410 @hybrid_property
1390 def entry_id(self):
1411 def entry_id(self):
1391 return self.user_log_id
1412 return self.user_log_id
1392
1413
1393 @property
1414 @property
1394 def action_as_day(self):
1415 def action_as_day(self):
1395 return datetime.date(*self.action_date.timetuple()[:3])
1416 return datetime.date(*self.action_date.timetuple()[:3])
1396
1417
1397 user = relationship('User')
1418 user = relationship('User')
1398 repository = relationship('Repository', cascade='')
1419 repository = relationship('Repository', cascade='')
1399
1420
1400
1421
1401 class UserGroup(Base, BaseModel):
1422 class UserGroup(Base, BaseModel):
1402 __tablename__ = 'users_groups'
1423 __tablename__ = 'users_groups'
1403 __table_args__ = (
1424 __table_args__ = (
1404 base_table_args,
1425 base_table_args,
1405 )
1426 )
1406
1427
1407 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1428 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1408 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1429 users_group_name = Column("users_group_name", String(255), nullable=False, unique=True, default=None)
1409 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1430 user_group_description = Column("user_group_description", String(10000), nullable=True, unique=None, default=None)
1410 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1431 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
1411 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1432 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
1412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1433 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
1413 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1434 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1414 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1435 _group_data = Column("group_data", LargeBinary(), nullable=True) # JSON data
1415
1436
1416 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1437 members = relationship('UserGroupMember', cascade="all, delete-orphan", lazy="joined")
1417 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1438 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
1418 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1439 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1419 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1440 users_group_repo_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1420 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1441 user_user_group_to_perm = relationship('UserUserGroupToPerm', cascade='all')
1421 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1442 user_group_user_group_to_perm = relationship('UserGroupUserGroupToPerm ', primaryjoin="UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id", cascade='all')
1422
1443
1423 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1444 user_group_review_rules = relationship('RepoReviewRuleUserGroup', cascade='all')
1424 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1445 user = relationship('User', primaryjoin="User.user_id==UserGroup.user_id")
1425
1446
1426 @classmethod
1447 @classmethod
1427 def _load_group_data(cls, column):
1448 def _load_group_data(cls, column):
1428 if not column:
1449 if not column:
1429 return {}
1450 return {}
1430
1451
1431 try:
1452 try:
1432 return json.loads(column) or {}
1453 return json.loads(column) or {}
1433 except TypeError:
1454 except TypeError:
1434 return {}
1455 return {}
1435
1456
1436 @hybrid_property
1457 @hybrid_property
1437 def description_safe(self):
1458 def description_safe(self):
1438 from rhodecode.lib import helpers as h
1459 from rhodecode.lib import helpers as h
1439 return h.escape(self.user_group_description)
1460 return h.escape(self.user_group_description)
1440
1461
1441 @hybrid_property
1462 @hybrid_property
1442 def group_data(self):
1463 def group_data(self):
1443 return self._load_group_data(self._group_data)
1464 return self._load_group_data(self._group_data)
1444
1465
1445 @group_data.expression
1466 @group_data.expression
1446 def group_data(self, **kwargs):
1467 def group_data(self, **kwargs):
1447 return self._group_data
1468 return self._group_data
1448
1469
1449 @group_data.setter
1470 @group_data.setter
1450 def group_data(self, val):
1471 def group_data(self, val):
1451 try:
1472 try:
1452 self._group_data = json.dumps(val)
1473 self._group_data = json.dumps(val)
1453 except Exception:
1474 except Exception:
1454 log.error(traceback.format_exc())
1475 log.error(traceback.format_exc())
1455
1476
1456 @classmethod
1477 @classmethod
1457 def _load_sync(cls, group_data):
1478 def _load_sync(cls, group_data):
1458 if group_data:
1479 if group_data:
1459 return group_data.get('extern_type')
1480 return group_data.get('extern_type')
1460
1481
1461 @property
1482 @property
1462 def sync(self):
1483 def sync(self):
1463 return self._load_sync(self.group_data)
1484 return self._load_sync(self.group_data)
1464
1485
1465 def __unicode__(self):
1486 def __unicode__(self):
1466 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1487 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
1467 self.users_group_id,
1488 self.users_group_id,
1468 self.users_group_name)
1489 self.users_group_name)
1469
1490
1470 @classmethod
1491 @classmethod
1471 def get_by_group_name(cls, group_name, cache=False,
1492 def get_by_group_name(cls, group_name, cache=False,
1472 case_insensitive=False):
1493 case_insensitive=False):
1473 if case_insensitive:
1494 if case_insensitive:
1474 q = cls.query().filter(func.lower(cls.users_group_name) ==
1495 q = cls.query().filter(func.lower(cls.users_group_name) ==
1475 func.lower(group_name))
1496 func.lower(group_name))
1476
1497
1477 else:
1498 else:
1478 q = cls.query().filter(cls.users_group_name == group_name)
1499 q = cls.query().filter(cls.users_group_name == group_name)
1479 if cache:
1500 if cache:
1480 q = q.options(
1501 q = q.options(
1481 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1502 FromCache("sql_cache_short", "get_group_%s" % _hash_key(group_name)))
1482 return q.scalar()
1503 return q.scalar()
1483
1504
1484 @classmethod
1505 @classmethod
1485 def get(cls, user_group_id, cache=False):
1506 def get(cls, user_group_id, cache=False):
1486 if not user_group_id:
1507 if not user_group_id:
1487 return
1508 return
1488
1509
1489 user_group = cls.query()
1510 user_group = cls.query()
1490 if cache:
1511 if cache:
1491 user_group = user_group.options(
1512 user_group = user_group.options(
1492 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1513 FromCache("sql_cache_short", "get_users_group_%s" % user_group_id))
1493 return user_group.get(user_group_id)
1514 return user_group.get(user_group_id)
1494
1515
1495 def permissions(self, with_admins=True, with_owner=True,
1516 def permissions(self, with_admins=True, with_owner=True,
1496 expand_from_user_groups=False):
1517 expand_from_user_groups=False):
1497 """
1518 """
1498 Permissions for user groups
1519 Permissions for user groups
1499 """
1520 """
1500 _admin_perm = 'usergroup.admin'
1521 _admin_perm = 'usergroup.admin'
1501
1522
1502 owner_row = []
1523 owner_row = []
1503 if with_owner:
1524 if with_owner:
1504 usr = AttributeDict(self.user.get_dict())
1525 usr = AttributeDict(self.user.get_dict())
1505 usr.owner_row = True
1526 usr.owner_row = True
1506 usr.permission = _admin_perm
1527 usr.permission = _admin_perm
1507 owner_row.append(usr)
1528 owner_row.append(usr)
1508
1529
1509 super_admin_ids = []
1530 super_admin_ids = []
1510 super_admin_rows = []
1531 super_admin_rows = []
1511 if with_admins:
1532 if with_admins:
1512 for usr in User.get_all_super_admins():
1533 for usr in User.get_all_super_admins():
1513 super_admin_ids.append(usr.user_id)
1534 super_admin_ids.append(usr.user_id)
1514 # if this admin is also owner, don't double the record
1535 # if this admin is also owner, don't double the record
1515 if usr.user_id == owner_row[0].user_id:
1536 if usr.user_id == owner_row[0].user_id:
1516 owner_row[0].admin_row = True
1537 owner_row[0].admin_row = True
1517 else:
1538 else:
1518 usr = AttributeDict(usr.get_dict())
1539 usr = AttributeDict(usr.get_dict())
1519 usr.admin_row = True
1540 usr.admin_row = True
1520 usr.permission = _admin_perm
1541 usr.permission = _admin_perm
1521 super_admin_rows.append(usr)
1542 super_admin_rows.append(usr)
1522
1543
1523 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1544 q = UserUserGroupToPerm.query().filter(UserUserGroupToPerm.user_group == self)
1524 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1545 q = q.options(joinedload(UserUserGroupToPerm.user_group),
1525 joinedload(UserUserGroupToPerm.user),
1546 joinedload(UserUserGroupToPerm.user),
1526 joinedload(UserUserGroupToPerm.permission),)
1547 joinedload(UserUserGroupToPerm.permission),)
1527
1548
1528 # get owners and admins and permissions. We do a trick of re-writing
1549 # get owners and admins and permissions. We do a trick of re-writing
1529 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1550 # objects from sqlalchemy to named-tuples due to sqlalchemy session
1530 # has a global reference and changing one object propagates to all
1551 # has a global reference and changing one object propagates to all
1531 # others. This means if admin is also an owner admin_row that change
1552 # others. This means if admin is also an owner admin_row that change
1532 # would propagate to both objects
1553 # would propagate to both objects
1533 perm_rows = []
1554 perm_rows = []
1534 for _usr in q.all():
1555 for _usr in q.all():
1535 usr = AttributeDict(_usr.user.get_dict())
1556 usr = AttributeDict(_usr.user.get_dict())
1536 # if this user is also owner/admin, mark as duplicate record
1557 # if this user is also owner/admin, mark as duplicate record
1537 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1558 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
1538 usr.duplicate_perm = True
1559 usr.duplicate_perm = True
1539 usr.permission = _usr.permission.permission_name
1560 usr.permission = _usr.permission.permission_name
1540 perm_rows.append(usr)
1561 perm_rows.append(usr)
1541
1562
1542 # filter the perm rows by 'default' first and then sort them by
1563 # filter the perm rows by 'default' first and then sort them by
1543 # admin,write,read,none permissions sorted again alphabetically in
1564 # admin,write,read,none permissions sorted again alphabetically in
1544 # each group
1565 # each group
1545 perm_rows = sorted(perm_rows, key=display_user_sort)
1566 perm_rows = sorted(perm_rows, key=display_user_sort)
1546
1567
1547 user_groups_rows = []
1568 user_groups_rows = []
1548 if expand_from_user_groups:
1569 if expand_from_user_groups:
1549 for ug in self.permission_user_groups(with_members=True):
1570 for ug in self.permission_user_groups(with_members=True):
1550 for user_data in ug.members:
1571 for user_data in ug.members:
1551 user_groups_rows.append(user_data)
1572 user_groups_rows.append(user_data)
1552
1573
1553 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1574 return super_admin_rows + owner_row + perm_rows + user_groups_rows
1554
1575
1555 def permission_user_groups(self, with_members=False):
1576 def permission_user_groups(self, with_members=False):
1556 q = UserGroupUserGroupToPerm.query()\
1577 q = UserGroupUserGroupToPerm.query()\
1557 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1578 .filter(UserGroupUserGroupToPerm.target_user_group == self)
1558 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1579 q = q.options(joinedload(UserGroupUserGroupToPerm.user_group),
1559 joinedload(UserGroupUserGroupToPerm.target_user_group),
1580 joinedload(UserGroupUserGroupToPerm.target_user_group),
1560 joinedload(UserGroupUserGroupToPerm.permission),)
1581 joinedload(UserGroupUserGroupToPerm.permission),)
1561
1582
1562 perm_rows = []
1583 perm_rows = []
1563 for _user_group in q.all():
1584 for _user_group in q.all():
1564 entry = AttributeDict(_user_group.user_group.get_dict())
1585 entry = AttributeDict(_user_group.user_group.get_dict())
1565 entry.permission = _user_group.permission.permission_name
1586 entry.permission = _user_group.permission.permission_name
1566 if with_members:
1587 if with_members:
1567 entry.members = [x.user.get_dict()
1588 entry.members = [x.user.get_dict()
1568 for x in _user_group.user_group.members]
1589 for x in _user_group.user_group.members]
1569 perm_rows.append(entry)
1590 perm_rows.append(entry)
1570
1591
1571 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1592 perm_rows = sorted(perm_rows, key=display_user_group_sort)
1572 return perm_rows
1593 return perm_rows
1573
1594
1574 def _get_default_perms(self, user_group, suffix=''):
1595 def _get_default_perms(self, user_group, suffix=''):
1575 from rhodecode.model.permission import PermissionModel
1596 from rhodecode.model.permission import PermissionModel
1576 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1597 return PermissionModel().get_default_perms(user_group.users_group_to_perm, suffix)
1577
1598
1578 def get_default_perms(self, suffix=''):
1599 def get_default_perms(self, suffix=''):
1579 return self._get_default_perms(self, suffix)
1600 return self._get_default_perms(self, suffix)
1580
1601
1581 def get_api_data(self, with_group_members=True, include_secrets=False):
1602 def get_api_data(self, with_group_members=True, include_secrets=False):
1582 """
1603 """
1583 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1604 :param include_secrets: See :meth:`User.get_api_data`, this parameter is
1584 basically forwarded.
1605 basically forwarded.
1585
1606
1586 """
1607 """
1587 user_group = self
1608 user_group = self
1588 data = {
1609 data = {
1589 'users_group_id': user_group.users_group_id,
1610 'users_group_id': user_group.users_group_id,
1590 'group_name': user_group.users_group_name,
1611 'group_name': user_group.users_group_name,
1591 'group_description': user_group.user_group_description,
1612 'group_description': user_group.user_group_description,
1592 'active': user_group.users_group_active,
1613 'active': user_group.users_group_active,
1593 'owner': user_group.user.username,
1614 'owner': user_group.user.username,
1594 'sync': user_group.sync,
1615 'sync': user_group.sync,
1595 'owner_email': user_group.user.email,
1616 'owner_email': user_group.user.email,
1596 }
1617 }
1597
1618
1598 if with_group_members:
1619 if with_group_members:
1599 users = []
1620 users = []
1600 for user in user_group.members:
1621 for user in user_group.members:
1601 user = user.user
1622 user = user.user
1602 users.append(user.get_api_data(include_secrets=include_secrets))
1623 users.append(user.get_api_data(include_secrets=include_secrets))
1603 data['users'] = users
1624 data['users'] = users
1604
1625
1605 return data
1626 return data
1606
1627
1607
1628
1608 class UserGroupMember(Base, BaseModel):
1629 class UserGroupMember(Base, BaseModel):
1609 __tablename__ = 'users_groups_members'
1630 __tablename__ = 'users_groups_members'
1610 __table_args__ = (
1631 __table_args__ = (
1611 base_table_args,
1632 base_table_args,
1612 )
1633 )
1613
1634
1614 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1615 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1636 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1616 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1637 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1617
1638
1618 user = relationship('User', lazy='joined')
1639 user = relationship('User', lazy='joined')
1619 users_group = relationship('UserGroup')
1640 users_group = relationship('UserGroup')
1620
1641
1621 def __init__(self, gr_id='', u_id=''):
1642 def __init__(self, gr_id='', u_id=''):
1622 self.users_group_id = gr_id
1643 self.users_group_id = gr_id
1623 self.user_id = u_id
1644 self.user_id = u_id
1624
1645
1625
1646
1626 class RepositoryField(Base, BaseModel):
1647 class RepositoryField(Base, BaseModel):
1627 __tablename__ = 'repositories_fields'
1648 __tablename__ = 'repositories_fields'
1628 __table_args__ = (
1649 __table_args__ = (
1629 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1650 UniqueConstraint('repository_id', 'field_key'), # no-multi field
1630 base_table_args,
1651 base_table_args,
1631 )
1652 )
1632
1653
1633 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1654 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
1634
1655
1635 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1656 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1636 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1657 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1637 field_key = Column("field_key", String(250))
1658 field_key = Column("field_key", String(250))
1638 field_label = Column("field_label", String(1024), nullable=False)
1659 field_label = Column("field_label", String(1024), nullable=False)
1639 field_value = Column("field_value", String(10000), nullable=False)
1660 field_value = Column("field_value", String(10000), nullable=False)
1640 field_desc = Column("field_desc", String(1024), nullable=False)
1661 field_desc = Column("field_desc", String(1024), nullable=False)
1641 field_type = Column("field_type", String(255), nullable=False, unique=None)
1662 field_type = Column("field_type", String(255), nullable=False, unique=None)
1642 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1663 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1643
1664
1644 repository = relationship('Repository')
1665 repository = relationship('Repository')
1645
1666
1646 @property
1667 @property
1647 def field_key_prefixed(self):
1668 def field_key_prefixed(self):
1648 return 'ex_%s' % self.field_key
1669 return 'ex_%s' % self.field_key
1649
1670
1650 @classmethod
1671 @classmethod
1651 def un_prefix_key(cls, key):
1672 def un_prefix_key(cls, key):
1652 if key.startswith(cls.PREFIX):
1673 if key.startswith(cls.PREFIX):
1653 return key[len(cls.PREFIX):]
1674 return key[len(cls.PREFIX):]
1654 return key
1675 return key
1655
1676
1656 @classmethod
1677 @classmethod
1657 def get_by_key_name(cls, key, repo):
1678 def get_by_key_name(cls, key, repo):
1658 row = cls.query()\
1679 row = cls.query()\
1659 .filter(cls.repository == repo)\
1680 .filter(cls.repository == repo)\
1660 .filter(cls.field_key == key).scalar()
1681 .filter(cls.field_key == key).scalar()
1661 return row
1682 return row
1662
1683
1663
1684
1664 class Repository(Base, BaseModel):
1685 class Repository(Base, BaseModel):
1665 __tablename__ = 'repositories'
1686 __tablename__ = 'repositories'
1666 __table_args__ = (
1687 __table_args__ = (
1667 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1688 Index('r_repo_name_idx', 'repo_name', mysql_length=255),
1668 base_table_args,
1689 base_table_args,
1669 )
1690 )
1670 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1691 DEFAULT_CLONE_URI = '{scheme}://{user}@{netloc}/{repo}'
1671 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1692 DEFAULT_CLONE_URI_ID = '{scheme}://{user}@{netloc}/_{repoid}'
1672 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1693 DEFAULT_CLONE_URI_SSH = 'ssh://{sys_user}@{hostname}/{repo}'
1673
1694
1674 STATE_CREATED = 'repo_state_created'
1695 STATE_CREATED = 'repo_state_created'
1675 STATE_PENDING = 'repo_state_pending'
1696 STATE_PENDING = 'repo_state_pending'
1676 STATE_ERROR = 'repo_state_error'
1697 STATE_ERROR = 'repo_state_error'
1677
1698
1678 LOCK_AUTOMATIC = 'lock_auto'
1699 LOCK_AUTOMATIC = 'lock_auto'
1679 LOCK_API = 'lock_api'
1700 LOCK_API = 'lock_api'
1680 LOCK_WEB = 'lock_web'
1701 LOCK_WEB = 'lock_web'
1681 LOCK_PULL = 'lock_pull'
1702 LOCK_PULL = 'lock_pull'
1682
1703
1683 NAME_SEP = URL_SEP
1704 NAME_SEP = URL_SEP
1684
1705
1685 repo_id = Column(
1706 repo_id = Column(
1686 "repo_id", Integer(), nullable=False, unique=True, default=None,
1707 "repo_id", Integer(), nullable=False, unique=True, default=None,
1687 primary_key=True)
1708 primary_key=True)
1688 _repo_name = Column(
1709 _repo_name = Column(
1689 "repo_name", Text(), nullable=False, default=None)
1710 "repo_name", Text(), nullable=False, default=None)
1690 repo_name_hash = Column(
1711 repo_name_hash = Column(
1691 "repo_name_hash", String(255), nullable=False, unique=True)
1712 "repo_name_hash", String(255), nullable=False, unique=True)
1692 repo_state = Column("repo_state", String(255), nullable=True)
1713 repo_state = Column("repo_state", String(255), nullable=True)
1693
1714
1694 clone_uri = Column(
1715 clone_uri = Column(
1695 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1716 "clone_uri", EncryptedTextValue(), nullable=True, unique=False,
1696 default=None)
1717 default=None)
1697 push_uri = Column(
1718 push_uri = Column(
1698 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1719 "push_uri", EncryptedTextValue(), nullable=True, unique=False,
1699 default=None)
1720 default=None)
1700 repo_type = Column(
1721 repo_type = Column(
1701 "repo_type", String(255), nullable=False, unique=False, default=None)
1722 "repo_type", String(255), nullable=False, unique=False, default=None)
1702 user_id = Column(
1723 user_id = Column(
1703 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1724 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
1704 unique=False, default=None)
1725 unique=False, default=None)
1705 private = Column(
1726 private = Column(
1706 "private", Boolean(), nullable=True, unique=None, default=None)
1727 "private", Boolean(), nullable=True, unique=None, default=None)
1707 archived = Column(
1728 archived = Column(
1708 "archived", Boolean(), nullable=True, unique=None, default=None)
1729 "archived", Boolean(), nullable=True, unique=None, default=None)
1709 enable_statistics = Column(
1730 enable_statistics = Column(
1710 "statistics", Boolean(), nullable=True, unique=None, default=True)
1731 "statistics", Boolean(), nullable=True, unique=None, default=True)
1711 enable_downloads = Column(
1732 enable_downloads = Column(
1712 "downloads", Boolean(), nullable=True, unique=None, default=True)
1733 "downloads", Boolean(), nullable=True, unique=None, default=True)
1713 description = Column(
1734 description = Column(
1714 "description", String(10000), nullable=True, unique=None, default=None)
1735 "description", String(10000), nullable=True, unique=None, default=None)
1715 created_on = Column(
1736 created_on = Column(
1716 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1737 'created_on', DateTime(timezone=False), nullable=True, unique=None,
1717 default=datetime.datetime.now)
1738 default=datetime.datetime.now)
1718 updated_on = Column(
1739 updated_on = Column(
1719 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1740 'updated_on', DateTime(timezone=False), nullable=True, unique=None,
1720 default=datetime.datetime.now)
1741 default=datetime.datetime.now)
1721 _landing_revision = Column(
1742 _landing_revision = Column(
1722 "landing_revision", String(255), nullable=False, unique=False,
1743 "landing_revision", String(255), nullable=False, unique=False,
1723 default=None)
1744 default=None)
1724 enable_locking = Column(
1745 enable_locking = Column(
1725 "enable_locking", Boolean(), nullable=False, unique=None,
1746 "enable_locking", Boolean(), nullable=False, unique=None,
1726 default=False)
1747 default=False)
1727 _locked = Column(
1748 _locked = Column(
1728 "locked", String(255), nullable=True, unique=False, default=None)
1749 "locked", String(255), nullable=True, unique=False, default=None)
1729 _changeset_cache = Column(
1750 _changeset_cache = Column(
1730 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1751 "changeset_cache", LargeBinary(), nullable=True) # JSON data
1731
1752
1732 fork_id = Column(
1753 fork_id = Column(
1733 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1754 "fork_id", Integer(), ForeignKey('repositories.repo_id'),
1734 nullable=True, unique=False, default=None)
1755 nullable=True, unique=False, default=None)
1735 group_id = Column(
1756 group_id = Column(
1736 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1757 "group_id", Integer(), ForeignKey('groups.group_id'), nullable=True,
1737 unique=False, default=None)
1758 unique=False, default=None)
1738
1759
1739 user = relationship('User', lazy='joined')
1760 user = relationship('User', lazy='joined')
1740 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1761 fork = relationship('Repository', remote_side=repo_id, lazy='joined')
1741 group = relationship('RepoGroup', lazy='joined')
1762 group = relationship('RepoGroup', lazy='joined')
1742 repo_to_perm = relationship(
1763 repo_to_perm = relationship(
1743 'UserRepoToPerm', cascade='all',
1764 'UserRepoToPerm', cascade='all',
1744 order_by='UserRepoToPerm.repo_to_perm_id')
1765 order_by='UserRepoToPerm.repo_to_perm_id')
1745 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1766 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
1746 stats = relationship('Statistics', cascade='all', uselist=False)
1767 stats = relationship('Statistics', cascade='all', uselist=False)
1747
1768
1748 followers = relationship(
1769 followers = relationship(
1749 'UserFollowing',
1770 'UserFollowing',
1750 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1771 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
1751 cascade='all')
1772 cascade='all')
1752 extra_fields = relationship(
1773 extra_fields = relationship(
1753 'RepositoryField', cascade="all, delete-orphan")
1774 'RepositoryField', cascade="all, delete-orphan")
1754 logs = relationship('UserLog')
1775 logs = relationship('UserLog')
1755 comments = relationship(
1776 comments = relationship(
1756 'ChangesetComment', cascade="all, delete-orphan")
1777 'ChangesetComment', cascade="all, delete-orphan")
1757 pull_requests_source = relationship(
1778 pull_requests_source = relationship(
1758 'PullRequest',
1779 'PullRequest',
1759 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1780 primaryjoin='PullRequest.source_repo_id==Repository.repo_id',
1760 cascade="all, delete-orphan")
1781 cascade="all, delete-orphan")
1761 pull_requests_target = relationship(
1782 pull_requests_target = relationship(
1762 'PullRequest',
1783 'PullRequest',
1763 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1784 primaryjoin='PullRequest.target_repo_id==Repository.repo_id',
1764 cascade="all, delete-orphan")
1785 cascade="all, delete-orphan")
1765 ui = relationship('RepoRhodeCodeUi', cascade="all")
1786 ui = relationship('RepoRhodeCodeUi', cascade="all")
1766 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1787 settings = relationship('RepoRhodeCodeSetting', cascade="all")
1767 integrations = relationship('Integration', cascade="all, delete-orphan")
1788 integrations = relationship('Integration', cascade="all, delete-orphan")
1768
1789
1769 scoped_tokens = relationship('UserApiKeys', cascade="all")
1790 scoped_tokens = relationship('UserApiKeys', cascade="all")
1770
1791
1771 # no cascade, set NULL
1792 # no cascade, set NULL
1772 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1793 artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_id==Repository.repo_id')
1773
1794
1774 def __unicode__(self):
1795 def __unicode__(self):
1775 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1796 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
1776 safe_unicode(self.repo_name))
1797 safe_unicode(self.repo_name))
1777
1798
1778 @hybrid_property
1799 @hybrid_property
1779 def description_safe(self):
1800 def description_safe(self):
1780 from rhodecode.lib import helpers as h
1801 from rhodecode.lib import helpers as h
1781 return h.escape(self.description)
1802 return h.escape(self.description)
1782
1803
1783 @hybrid_property
1804 @hybrid_property
1784 def landing_rev(self):
1805 def landing_rev(self):
1785 # always should return [rev_type, rev], e.g ['branch', 'master']
1806 # always should return [rev_type, rev], e.g ['branch', 'master']
1786 if self._landing_revision:
1807 if self._landing_revision:
1787 _rev_info = self._landing_revision.split(':')
1808 _rev_info = self._landing_revision.split(':')
1788 if len(_rev_info) < 2:
1809 if len(_rev_info) < 2:
1789 _rev_info.insert(0, 'rev')
1810 _rev_info.insert(0, 'rev')
1790 return [_rev_info[0], _rev_info[1]]
1811 return [_rev_info[0], _rev_info[1]]
1791 return [None, None]
1812 return [None, None]
1792
1813
1793 @property
1814 @property
1794 def landing_ref_type(self):
1815 def landing_ref_type(self):
1795 return self.landing_rev[0]
1816 return self.landing_rev[0]
1796
1817
1797 @property
1818 @property
1798 def landing_ref_name(self):
1819 def landing_ref_name(self):
1799 return self.landing_rev[1]
1820 return self.landing_rev[1]
1800
1821
1801 @landing_rev.setter
1822 @landing_rev.setter
1802 def landing_rev(self, val):
1823 def landing_rev(self, val):
1803 if ':' not in val:
1824 if ':' not in val:
1804 raise ValueError('value must be delimited with `:` and consist '
1825 raise ValueError('value must be delimited with `:` and consist '
1805 'of <rev_type>:<rev>, got %s instead' % val)
1826 'of <rev_type>:<rev>, got %s instead' % val)
1806 self._landing_revision = val
1827 self._landing_revision = val
1807
1828
1808 @hybrid_property
1829 @hybrid_property
1809 def locked(self):
1830 def locked(self):
1810 if self._locked:
1831 if self._locked:
1811 user_id, timelocked, reason = self._locked.split(':')
1832 user_id, timelocked, reason = self._locked.split(':')
1812 lock_values = int(user_id), timelocked, reason
1833 lock_values = int(user_id), timelocked, reason
1813 else:
1834 else:
1814 lock_values = [None, None, None]
1835 lock_values = [None, None, None]
1815 return lock_values
1836 return lock_values
1816
1837
1817 @locked.setter
1838 @locked.setter
1818 def locked(self, val):
1839 def locked(self, val):
1819 if val and isinstance(val, (list, tuple)):
1840 if val and isinstance(val, (list, tuple)):
1820 self._locked = ':'.join(map(str, val))
1841 self._locked = ':'.join(map(str, val))
1821 else:
1842 else:
1822 self._locked = None
1843 self._locked = None
1823
1844
1824 @classmethod
1845 @classmethod
1825 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1846 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
1826 from rhodecode.lib.vcs.backends.base import EmptyCommit
1847 from rhodecode.lib.vcs.backends.base import EmptyCommit
1827 dummy = EmptyCommit().__json__()
1848 dummy = EmptyCommit().__json__()
1828 if not changeset_cache_raw:
1849 if not changeset_cache_raw:
1829 dummy['source_repo_id'] = repo_id
1850 dummy['source_repo_id'] = repo_id
1830 return json.loads(json.dumps(dummy))
1851 return json.loads(json.dumps(dummy))
1831
1852
1832 try:
1853 try:
1833 return json.loads(changeset_cache_raw)
1854 return json.loads(changeset_cache_raw)
1834 except TypeError:
1855 except TypeError:
1835 return dummy
1856 return dummy
1836 except Exception:
1857 except Exception:
1837 log.error(traceback.format_exc())
1858 log.error(traceback.format_exc())
1838 return dummy
1859 return dummy
1839
1860
1840 @hybrid_property
1861 @hybrid_property
1841 def changeset_cache(self):
1862 def changeset_cache(self):
1842 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1863 return self._load_changeset_cache(self.repo_id, self._changeset_cache)
1843
1864
1844 @changeset_cache.setter
1865 @changeset_cache.setter
1845 def changeset_cache(self, val):
1866 def changeset_cache(self, val):
1846 try:
1867 try:
1847 self._changeset_cache = json.dumps(val)
1868 self._changeset_cache = json.dumps(val)
1848 except Exception:
1869 except Exception:
1849 log.error(traceback.format_exc())
1870 log.error(traceback.format_exc())
1850
1871
1851 @hybrid_property
1872 @hybrid_property
1852 def repo_name(self):
1873 def repo_name(self):
1853 return self._repo_name
1874 return self._repo_name
1854
1875
1855 @repo_name.setter
1876 @repo_name.setter
1856 def repo_name(self, value):
1877 def repo_name(self, value):
1857 self._repo_name = value
1878 self._repo_name = value
1858 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1879 self.repo_name_hash = hashlib.sha1(safe_str(value)).hexdigest()
1859
1880
1860 @classmethod
1881 @classmethod
1861 def normalize_repo_name(cls, repo_name):
1882 def normalize_repo_name(cls, repo_name):
1862 """
1883 """
1863 Normalizes os specific repo_name to the format internally stored inside
1884 Normalizes os specific repo_name to the format internally stored inside
1864 database using URL_SEP
1885 database using URL_SEP
1865
1886
1866 :param cls:
1887 :param cls:
1867 :param repo_name:
1888 :param repo_name:
1868 """
1889 """
1869 return cls.NAME_SEP.join(repo_name.split(os.sep))
1890 return cls.NAME_SEP.join(repo_name.split(os.sep))
1870
1891
1871 @classmethod
1892 @classmethod
1872 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1893 def get_by_repo_name(cls, repo_name, cache=False, identity_cache=False):
1873 session = Session()
1894 session = Session()
1874 q = session.query(cls).filter(cls.repo_name == repo_name)
1895 q = session.query(cls).filter(cls.repo_name == repo_name)
1875
1896
1876 if cache:
1897 if cache:
1877 if identity_cache:
1898 if identity_cache:
1878 val = cls.identity_cache(session, 'repo_name', repo_name)
1899 val = cls.identity_cache(session, 'repo_name', repo_name)
1879 if val:
1900 if val:
1880 return val
1901 return val
1881 else:
1902 else:
1882 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1903 cache_key = "get_repo_by_name_%s" % _hash_key(repo_name)
1883 q = q.options(
1904 q = q.options(
1884 FromCache("sql_cache_short", cache_key))
1905 FromCache("sql_cache_short", cache_key))
1885
1906
1886 return q.scalar()
1907 return q.scalar()
1887
1908
1888 @classmethod
1909 @classmethod
1889 def get_by_id_or_repo_name(cls, repoid):
1910 def get_by_id_or_repo_name(cls, repoid):
1890 if isinstance(repoid, (int, long)):
1911 if isinstance(repoid, (int, long)):
1891 try:
1912 try:
1892 repo = cls.get(repoid)
1913 repo = cls.get(repoid)
1893 except ValueError:
1914 except ValueError:
1894 repo = None
1915 repo = None
1895 else:
1916 else:
1896 repo = cls.get_by_repo_name(repoid)
1917 repo = cls.get_by_repo_name(repoid)
1897 return repo
1918 return repo
1898
1919
1899 @classmethod
1920 @classmethod
1900 def get_by_full_path(cls, repo_full_path):
1921 def get_by_full_path(cls, repo_full_path):
1901 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1922 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
1902 repo_name = cls.normalize_repo_name(repo_name)
1923 repo_name = cls.normalize_repo_name(repo_name)
1903 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1924 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
1904
1925
1905 @classmethod
1926 @classmethod
1906 def get_repo_forks(cls, repo_id):
1927 def get_repo_forks(cls, repo_id):
1907 return cls.query().filter(Repository.fork_id == repo_id)
1928 return cls.query().filter(Repository.fork_id == repo_id)
1908
1929
1909 @classmethod
1930 @classmethod
1910 def base_path(cls):
1931 def base_path(cls):
1911 """
1932 """
1912 Returns base path when all repos are stored
1933 Returns base path when all repos are stored
1913
1934
1914 :param cls:
1935 :param cls:
1915 """
1936 """
1916 q = Session().query(RhodeCodeUi)\
1937 q = Session().query(RhodeCodeUi)\
1917 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1938 .filter(RhodeCodeUi.ui_key == cls.NAME_SEP)
1918 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1939 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1919 return q.one().ui_value
1940 return q.one().ui_value
1920
1941
1921 @classmethod
1942 @classmethod
1922 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1943 def get_all_repos(cls, user_id=Optional(None), group_id=Optional(None),
1923 case_insensitive=True, archived=False):
1944 case_insensitive=True, archived=False):
1924 q = Repository.query()
1945 q = Repository.query()
1925
1946
1926 if not archived:
1947 if not archived:
1927 q = q.filter(Repository.archived.isnot(true()))
1948 q = q.filter(Repository.archived.isnot(true()))
1928
1949
1929 if not isinstance(user_id, Optional):
1950 if not isinstance(user_id, Optional):
1930 q = q.filter(Repository.user_id == user_id)
1951 q = q.filter(Repository.user_id == user_id)
1931
1952
1932 if not isinstance(group_id, Optional):
1953 if not isinstance(group_id, Optional):
1933 q = q.filter(Repository.group_id == group_id)
1954 q = q.filter(Repository.group_id == group_id)
1934
1955
1935 if case_insensitive:
1956 if case_insensitive:
1936 q = q.order_by(func.lower(Repository.repo_name))
1957 q = q.order_by(func.lower(Repository.repo_name))
1937 else:
1958 else:
1938 q = q.order_by(Repository.repo_name)
1959 q = q.order_by(Repository.repo_name)
1939
1960
1940 return q.all()
1961 return q.all()
1941
1962
1942 @property
1963 @property
1943 def repo_uid(self):
1964 def repo_uid(self):
1944 return '_{}'.format(self.repo_id)
1965 return '_{}'.format(self.repo_id)
1945
1966
1946 @property
1967 @property
1947 def forks(self):
1968 def forks(self):
1948 """
1969 """
1949 Return forks of this repo
1970 Return forks of this repo
1950 """
1971 """
1951 return Repository.get_repo_forks(self.repo_id)
1972 return Repository.get_repo_forks(self.repo_id)
1952
1973
1953 @property
1974 @property
1954 def parent(self):
1975 def parent(self):
1955 """
1976 """
1956 Returns fork parent
1977 Returns fork parent
1957 """
1978 """
1958 return self.fork
1979 return self.fork
1959
1980
1960 @property
1981 @property
1961 def just_name(self):
1982 def just_name(self):
1962 return self.repo_name.split(self.NAME_SEP)[-1]
1983 return self.repo_name.split(self.NAME_SEP)[-1]
1963
1984
1964 @property
1985 @property
1965 def groups_with_parents(self):
1986 def groups_with_parents(self):
1966 groups = []
1987 groups = []
1967 if self.group is None:
1988 if self.group is None:
1968 return groups
1989 return groups
1969
1990
1970 cur_gr = self.group
1991 cur_gr = self.group
1971 groups.insert(0, cur_gr)
1992 groups.insert(0, cur_gr)
1972 while 1:
1993 while 1:
1973 gr = getattr(cur_gr, 'parent_group', None)
1994 gr = getattr(cur_gr, 'parent_group', None)
1974 cur_gr = cur_gr.parent_group
1995 cur_gr = cur_gr.parent_group
1975 if gr is None:
1996 if gr is None:
1976 break
1997 break
1977 groups.insert(0, gr)
1998 groups.insert(0, gr)
1978
1999
1979 return groups
2000 return groups
1980
2001
1981 @property
2002 @property
1982 def groups_and_repo(self):
2003 def groups_and_repo(self):
1983 return self.groups_with_parents, self
2004 return self.groups_with_parents, self
1984
2005
1985 @LazyProperty
2006 @LazyProperty
1986 def repo_path(self):
2007 def repo_path(self):
1987 """
2008 """
1988 Returns base full path for that repository means where it actually
2009 Returns base full path for that repository means where it actually
1989 exists on a filesystem
2010 exists on a filesystem
1990 """
2011 """
1991 q = Session().query(RhodeCodeUi).filter(
2012 q = Session().query(RhodeCodeUi).filter(
1992 RhodeCodeUi.ui_key == self.NAME_SEP)
2013 RhodeCodeUi.ui_key == self.NAME_SEP)
1993 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
2014 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
1994 return q.one().ui_value
2015 return q.one().ui_value
1995
2016
1996 @property
2017 @property
1997 def repo_full_path(self):
2018 def repo_full_path(self):
1998 p = [self.repo_path]
2019 p = [self.repo_path]
1999 # we need to split the name by / since this is how we store the
2020 # we need to split the name by / since this is how we store the
2000 # names in the database, but that eventually needs to be converted
2021 # names in the database, but that eventually needs to be converted
2001 # into a valid system path
2022 # into a valid system path
2002 p += self.repo_name.split(self.NAME_SEP)
2023 p += self.repo_name.split(self.NAME_SEP)
2003 return os.path.join(*map(safe_unicode, p))
2024 return os.path.join(*map(safe_unicode, p))
2004
2025
2005 @property
2026 @property
2006 def cache_keys(self):
2027 def cache_keys(self):
2007 """
2028 """
2008 Returns associated cache keys for that repo
2029 Returns associated cache keys for that repo
2009 """
2030 """
2010 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2031 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2011 repo_id=self.repo_id)
2032 repo_id=self.repo_id)
2012 return CacheKey.query()\
2033 return CacheKey.query()\
2013 .filter(CacheKey.cache_args == invalidation_namespace)\
2034 .filter(CacheKey.cache_args == invalidation_namespace)\
2014 .order_by(CacheKey.cache_key)\
2035 .order_by(CacheKey.cache_key)\
2015 .all()
2036 .all()
2016
2037
2017 @property
2038 @property
2018 def cached_diffs_relative_dir(self):
2039 def cached_diffs_relative_dir(self):
2019 """
2040 """
2020 Return a relative to the repository store path of cached diffs
2041 Return a relative to the repository store path of cached diffs
2021 used for safe display for users, who shouldn't know the absolute store
2042 used for safe display for users, who shouldn't know the absolute store
2022 path
2043 path
2023 """
2044 """
2024 return os.path.join(
2045 return os.path.join(
2025 os.path.dirname(self.repo_name),
2046 os.path.dirname(self.repo_name),
2026 self.cached_diffs_dir.split(os.path.sep)[-1])
2047 self.cached_diffs_dir.split(os.path.sep)[-1])
2027
2048
2028 @property
2049 @property
2029 def cached_diffs_dir(self):
2050 def cached_diffs_dir(self):
2030 path = self.repo_full_path
2051 path = self.repo_full_path
2031 return os.path.join(
2052 return os.path.join(
2032 os.path.dirname(path),
2053 os.path.dirname(path),
2033 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2054 '.__shadow_diff_cache_repo_{}'.format(self.repo_id))
2034
2055
2035 def cached_diffs(self):
2056 def cached_diffs(self):
2036 diff_cache_dir = self.cached_diffs_dir
2057 diff_cache_dir = self.cached_diffs_dir
2037 if os.path.isdir(diff_cache_dir):
2058 if os.path.isdir(diff_cache_dir):
2038 return os.listdir(diff_cache_dir)
2059 return os.listdir(diff_cache_dir)
2039 return []
2060 return []
2040
2061
2041 def shadow_repos(self):
2062 def shadow_repos(self):
2042 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2063 shadow_repos_pattern = '.__shadow_repo_{}'.format(self.repo_id)
2043 return [
2064 return [
2044 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2065 x for x in os.listdir(os.path.dirname(self.repo_full_path))
2045 if x.startswith(shadow_repos_pattern)]
2066 if x.startswith(shadow_repos_pattern)]
2046
2067
2047 def get_new_name(self, repo_name):
2068 def get_new_name(self, repo_name):
2048 """
2069 """
2049 returns new full repository name based on assigned group and new new
2070 returns new full repository name based on assigned group and new new
2050
2071
2051 :param group_name:
2072 :param group_name:
2052 """
2073 """
2053 path_prefix = self.group.full_path_splitted if self.group else []
2074 path_prefix = self.group.full_path_splitted if self.group else []
2054 return self.NAME_SEP.join(path_prefix + [repo_name])
2075 return self.NAME_SEP.join(path_prefix + [repo_name])
2055
2076
2056 @property
2077 @property
2057 def _config(self):
2078 def _config(self):
2058 """
2079 """
2059 Returns db based config object.
2080 Returns db based config object.
2060 """
2081 """
2061 from rhodecode.lib.utils import make_db_config
2082 from rhodecode.lib.utils import make_db_config
2062 return make_db_config(clear_session=False, repo=self)
2083 return make_db_config(clear_session=False, repo=self)
2063
2084
2064 def permissions(self, with_admins=True, with_owner=True,
2085 def permissions(self, with_admins=True, with_owner=True,
2065 expand_from_user_groups=False):
2086 expand_from_user_groups=False):
2066 """
2087 """
2067 Permissions for repositories
2088 Permissions for repositories
2068 """
2089 """
2069 _admin_perm = 'repository.admin'
2090 _admin_perm = 'repository.admin'
2070
2091
2071 owner_row = []
2092 owner_row = []
2072 if with_owner:
2093 if with_owner:
2073 usr = AttributeDict(self.user.get_dict())
2094 usr = AttributeDict(self.user.get_dict())
2074 usr.owner_row = True
2095 usr.owner_row = True
2075 usr.permission = _admin_perm
2096 usr.permission = _admin_perm
2076 usr.permission_id = None
2097 usr.permission_id = None
2077 owner_row.append(usr)
2098 owner_row.append(usr)
2078
2099
2079 super_admin_ids = []
2100 super_admin_ids = []
2080 super_admin_rows = []
2101 super_admin_rows = []
2081 if with_admins:
2102 if with_admins:
2082 for usr in User.get_all_super_admins():
2103 for usr in User.get_all_super_admins():
2083 super_admin_ids.append(usr.user_id)
2104 super_admin_ids.append(usr.user_id)
2084 # if this admin is also owner, don't double the record
2105 # if this admin is also owner, don't double the record
2085 if usr.user_id == owner_row[0].user_id:
2106 if usr.user_id == owner_row[0].user_id:
2086 owner_row[0].admin_row = True
2107 owner_row[0].admin_row = True
2087 else:
2108 else:
2088 usr = AttributeDict(usr.get_dict())
2109 usr = AttributeDict(usr.get_dict())
2089 usr.admin_row = True
2110 usr.admin_row = True
2090 usr.permission = _admin_perm
2111 usr.permission = _admin_perm
2091 usr.permission_id = None
2112 usr.permission_id = None
2092 super_admin_rows.append(usr)
2113 super_admin_rows.append(usr)
2093
2114
2094 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2115 q = UserRepoToPerm.query().filter(UserRepoToPerm.repository == self)
2095 q = q.options(joinedload(UserRepoToPerm.repository),
2116 q = q.options(joinedload(UserRepoToPerm.repository),
2096 joinedload(UserRepoToPerm.user),
2117 joinedload(UserRepoToPerm.user),
2097 joinedload(UserRepoToPerm.permission),)
2118 joinedload(UserRepoToPerm.permission),)
2098
2119
2099 # get owners and admins and permissions. We do a trick of re-writing
2120 # get owners and admins and permissions. We do a trick of re-writing
2100 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2121 # objects from sqlalchemy to named-tuples due to sqlalchemy session
2101 # has a global reference and changing one object propagates to all
2122 # has a global reference and changing one object propagates to all
2102 # others. This means if admin is also an owner admin_row that change
2123 # others. This means if admin is also an owner admin_row that change
2103 # would propagate to both objects
2124 # would propagate to both objects
2104 perm_rows = []
2125 perm_rows = []
2105 for _usr in q.all():
2126 for _usr in q.all():
2106 usr = AttributeDict(_usr.user.get_dict())
2127 usr = AttributeDict(_usr.user.get_dict())
2107 # if this user is also owner/admin, mark as duplicate record
2128 # if this user is also owner/admin, mark as duplicate record
2108 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2129 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
2109 usr.duplicate_perm = True
2130 usr.duplicate_perm = True
2110 # also check if this permission is maybe used by branch_permissions
2131 # also check if this permission is maybe used by branch_permissions
2111 if _usr.branch_perm_entry:
2132 if _usr.branch_perm_entry:
2112 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2133 usr.branch_rules = [x.branch_rule_id for x in _usr.branch_perm_entry]
2113
2134
2114 usr.permission = _usr.permission.permission_name
2135 usr.permission = _usr.permission.permission_name
2115 usr.permission_id = _usr.repo_to_perm_id
2136 usr.permission_id = _usr.repo_to_perm_id
2116 perm_rows.append(usr)
2137 perm_rows.append(usr)
2117
2138
2118 # filter the perm rows by 'default' first and then sort them by
2139 # filter the perm rows by 'default' first and then sort them by
2119 # admin,write,read,none permissions sorted again alphabetically in
2140 # admin,write,read,none permissions sorted again alphabetically in
2120 # each group
2141 # each group
2121 perm_rows = sorted(perm_rows, key=display_user_sort)
2142 perm_rows = sorted(perm_rows, key=display_user_sort)
2122
2143
2123 user_groups_rows = []
2144 user_groups_rows = []
2124 if expand_from_user_groups:
2145 if expand_from_user_groups:
2125 for ug in self.permission_user_groups(with_members=True):
2146 for ug in self.permission_user_groups(with_members=True):
2126 for user_data in ug.members:
2147 for user_data in ug.members:
2127 user_groups_rows.append(user_data)
2148 user_groups_rows.append(user_data)
2128
2149
2129 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2150 return super_admin_rows + owner_row + perm_rows + user_groups_rows
2130
2151
2131 def permission_user_groups(self, with_members=True):
2152 def permission_user_groups(self, with_members=True):
2132 q = UserGroupRepoToPerm.query()\
2153 q = UserGroupRepoToPerm.query()\
2133 .filter(UserGroupRepoToPerm.repository == self)
2154 .filter(UserGroupRepoToPerm.repository == self)
2134 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2155 q = q.options(joinedload(UserGroupRepoToPerm.repository),
2135 joinedload(UserGroupRepoToPerm.users_group),
2156 joinedload(UserGroupRepoToPerm.users_group),
2136 joinedload(UserGroupRepoToPerm.permission),)
2157 joinedload(UserGroupRepoToPerm.permission),)
2137
2158
2138 perm_rows = []
2159 perm_rows = []
2139 for _user_group in q.all():
2160 for _user_group in q.all():
2140 entry = AttributeDict(_user_group.users_group.get_dict())
2161 entry = AttributeDict(_user_group.users_group.get_dict())
2141 entry.permission = _user_group.permission.permission_name
2162 entry.permission = _user_group.permission.permission_name
2142 if with_members:
2163 if with_members:
2143 entry.members = [x.user.get_dict()
2164 entry.members = [x.user.get_dict()
2144 for x in _user_group.users_group.members]
2165 for x in _user_group.users_group.members]
2145 perm_rows.append(entry)
2166 perm_rows.append(entry)
2146
2167
2147 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2168 perm_rows = sorted(perm_rows, key=display_user_group_sort)
2148 return perm_rows
2169 return perm_rows
2149
2170
2150 def get_api_data(self, include_secrets=False):
2171 def get_api_data(self, include_secrets=False):
2151 """
2172 """
2152 Common function for generating repo api data
2173 Common function for generating repo api data
2153
2174
2154 :param include_secrets: See :meth:`User.get_api_data`.
2175 :param include_secrets: See :meth:`User.get_api_data`.
2155
2176
2156 """
2177 """
2157 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2178 # TODO: mikhail: Here there is an anti-pattern, we probably need to
2158 # move this methods on models level.
2179 # move this methods on models level.
2159 from rhodecode.model.settings import SettingsModel
2180 from rhodecode.model.settings import SettingsModel
2160 from rhodecode.model.repo import RepoModel
2181 from rhodecode.model.repo import RepoModel
2161
2182
2162 repo = self
2183 repo = self
2163 _user_id, _time, _reason = self.locked
2184 _user_id, _time, _reason = self.locked
2164
2185
2165 data = {
2186 data = {
2166 'repo_id': repo.repo_id,
2187 'repo_id': repo.repo_id,
2167 'repo_name': repo.repo_name,
2188 'repo_name': repo.repo_name,
2168 'repo_type': repo.repo_type,
2189 'repo_type': repo.repo_type,
2169 'clone_uri': repo.clone_uri or '',
2190 'clone_uri': repo.clone_uri or '',
2170 'push_uri': repo.push_uri or '',
2191 'push_uri': repo.push_uri or '',
2171 'url': RepoModel().get_url(self),
2192 'url': RepoModel().get_url(self),
2172 'private': repo.private,
2193 'private': repo.private,
2173 'created_on': repo.created_on,
2194 'created_on': repo.created_on,
2174 'description': repo.description_safe,
2195 'description': repo.description_safe,
2175 'landing_rev': repo.landing_rev,
2196 'landing_rev': repo.landing_rev,
2176 'owner': repo.user.username,
2197 'owner': repo.user.username,
2177 'fork_of': repo.fork.repo_name if repo.fork else None,
2198 'fork_of': repo.fork.repo_name if repo.fork else None,
2178 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2199 'fork_of_id': repo.fork.repo_id if repo.fork else None,
2179 'enable_statistics': repo.enable_statistics,
2200 'enable_statistics': repo.enable_statistics,
2180 'enable_locking': repo.enable_locking,
2201 'enable_locking': repo.enable_locking,
2181 'enable_downloads': repo.enable_downloads,
2202 'enable_downloads': repo.enable_downloads,
2182 'last_changeset': repo.changeset_cache,
2203 'last_changeset': repo.changeset_cache,
2183 'locked_by': User.get(_user_id).get_api_data(
2204 'locked_by': User.get(_user_id).get_api_data(
2184 include_secrets=include_secrets) if _user_id else None,
2205 include_secrets=include_secrets) if _user_id else None,
2185 'locked_date': time_to_datetime(_time) if _time else None,
2206 'locked_date': time_to_datetime(_time) if _time else None,
2186 'lock_reason': _reason if _reason else None,
2207 'lock_reason': _reason if _reason else None,
2187 }
2208 }
2188
2209
2189 # TODO: mikhail: should be per-repo settings here
2210 # TODO: mikhail: should be per-repo settings here
2190 rc_config = SettingsModel().get_all_settings()
2211 rc_config = SettingsModel().get_all_settings()
2191 repository_fields = str2bool(
2212 repository_fields = str2bool(
2192 rc_config.get('rhodecode_repository_fields'))
2213 rc_config.get('rhodecode_repository_fields'))
2193 if repository_fields:
2214 if repository_fields:
2194 for f in self.extra_fields:
2215 for f in self.extra_fields:
2195 data[f.field_key_prefixed] = f.field_value
2216 data[f.field_key_prefixed] = f.field_value
2196
2217
2197 return data
2218 return data
2198
2219
2199 @classmethod
2220 @classmethod
2200 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2221 def lock(cls, repo, user_id, lock_time=None, lock_reason=None):
2201 if not lock_time:
2222 if not lock_time:
2202 lock_time = time.time()
2223 lock_time = time.time()
2203 if not lock_reason:
2224 if not lock_reason:
2204 lock_reason = cls.LOCK_AUTOMATIC
2225 lock_reason = cls.LOCK_AUTOMATIC
2205 repo.locked = [user_id, lock_time, lock_reason]
2226 repo.locked = [user_id, lock_time, lock_reason]
2206 Session().add(repo)
2227 Session().add(repo)
2207 Session().commit()
2228 Session().commit()
2208
2229
2209 @classmethod
2230 @classmethod
2210 def unlock(cls, repo):
2231 def unlock(cls, repo):
2211 repo.locked = None
2232 repo.locked = None
2212 Session().add(repo)
2233 Session().add(repo)
2213 Session().commit()
2234 Session().commit()
2214
2235
2215 @classmethod
2236 @classmethod
2216 def getlock(cls, repo):
2237 def getlock(cls, repo):
2217 return repo.locked
2238 return repo.locked
2218
2239
2219 def is_user_lock(self, user_id):
2240 def is_user_lock(self, user_id):
2220 if self.lock[0]:
2241 if self.lock[0]:
2221 lock_user_id = safe_int(self.lock[0])
2242 lock_user_id = safe_int(self.lock[0])
2222 user_id = safe_int(user_id)
2243 user_id = safe_int(user_id)
2223 # both are ints, and they are equal
2244 # both are ints, and they are equal
2224 return all([lock_user_id, user_id]) and lock_user_id == user_id
2245 return all([lock_user_id, user_id]) and lock_user_id == user_id
2225
2246
2226 return False
2247 return False
2227
2248
2228 def get_locking_state(self, action, user_id, only_when_enabled=True):
2249 def get_locking_state(self, action, user_id, only_when_enabled=True):
2229 """
2250 """
2230 Checks locking on this repository, if locking is enabled and lock is
2251 Checks locking on this repository, if locking is enabled and lock is
2231 present returns a tuple of make_lock, locked, locked_by.
2252 present returns a tuple of make_lock, locked, locked_by.
2232 make_lock can have 3 states None (do nothing) True, make lock
2253 make_lock can have 3 states None (do nothing) True, make lock
2233 False release lock, This value is later propagated to hooks, which
2254 False release lock, This value is later propagated to hooks, which
2234 do the locking. Think about this as signals passed to hooks what to do.
2255 do the locking. Think about this as signals passed to hooks what to do.
2235
2256
2236 """
2257 """
2237 # TODO: johbo: This is part of the business logic and should be moved
2258 # TODO: johbo: This is part of the business logic and should be moved
2238 # into the RepositoryModel.
2259 # into the RepositoryModel.
2239
2260
2240 if action not in ('push', 'pull'):
2261 if action not in ('push', 'pull'):
2241 raise ValueError("Invalid action value: %s" % repr(action))
2262 raise ValueError("Invalid action value: %s" % repr(action))
2242
2263
2243 # defines if locked error should be thrown to user
2264 # defines if locked error should be thrown to user
2244 currently_locked = False
2265 currently_locked = False
2245 # defines if new lock should be made, tri-state
2266 # defines if new lock should be made, tri-state
2246 make_lock = None
2267 make_lock = None
2247 repo = self
2268 repo = self
2248 user = User.get(user_id)
2269 user = User.get(user_id)
2249
2270
2250 lock_info = repo.locked
2271 lock_info = repo.locked
2251
2272
2252 if repo and (repo.enable_locking or not only_when_enabled):
2273 if repo and (repo.enable_locking or not only_when_enabled):
2253 if action == 'push':
2274 if action == 'push':
2254 # check if it's already locked !, if it is compare users
2275 # check if it's already locked !, if it is compare users
2255 locked_by_user_id = lock_info[0]
2276 locked_by_user_id = lock_info[0]
2256 if user.user_id == locked_by_user_id:
2277 if user.user_id == locked_by_user_id:
2257 log.debug(
2278 log.debug(
2258 'Got `push` action from user %s, now unlocking', user)
2279 'Got `push` action from user %s, now unlocking', user)
2259 # unlock if we have push from user who locked
2280 # unlock if we have push from user who locked
2260 make_lock = False
2281 make_lock = False
2261 else:
2282 else:
2262 # we're not the same user who locked, ban with
2283 # we're not the same user who locked, ban with
2263 # code defined in settings (default is 423 HTTP Locked) !
2284 # code defined in settings (default is 423 HTTP Locked) !
2264 log.debug('Repo %s is currently locked by %s', repo, user)
2285 log.debug('Repo %s is currently locked by %s', repo, user)
2265 currently_locked = True
2286 currently_locked = True
2266 elif action == 'pull':
2287 elif action == 'pull':
2267 # [0] user [1] date
2288 # [0] user [1] date
2268 if lock_info[0] and lock_info[1]:
2289 if lock_info[0] and lock_info[1]:
2269 log.debug('Repo %s is currently locked by %s', repo, user)
2290 log.debug('Repo %s is currently locked by %s', repo, user)
2270 currently_locked = True
2291 currently_locked = True
2271 else:
2292 else:
2272 log.debug('Setting lock on repo %s by %s', repo, user)
2293 log.debug('Setting lock on repo %s by %s', repo, user)
2273 make_lock = True
2294 make_lock = True
2274
2295
2275 else:
2296 else:
2276 log.debug('Repository %s do not have locking enabled', repo)
2297 log.debug('Repository %s do not have locking enabled', repo)
2277
2298
2278 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2299 log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s',
2279 make_lock, currently_locked, lock_info)
2300 make_lock, currently_locked, lock_info)
2280
2301
2281 from rhodecode.lib.auth import HasRepoPermissionAny
2302 from rhodecode.lib.auth import HasRepoPermissionAny
2282 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2303 perm_check = HasRepoPermissionAny('repository.write', 'repository.admin')
2283 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2304 if make_lock and not perm_check(repo_name=repo.repo_name, user=user):
2284 # if we don't have at least write permission we cannot make a lock
2305 # if we don't have at least write permission we cannot make a lock
2285 log.debug('lock state reset back to FALSE due to lack '
2306 log.debug('lock state reset back to FALSE due to lack '
2286 'of at least read permission')
2307 'of at least read permission')
2287 make_lock = False
2308 make_lock = False
2288
2309
2289 return make_lock, currently_locked, lock_info
2310 return make_lock, currently_locked, lock_info
2290
2311
2291 @property
2312 @property
2292 def last_commit_cache_update_diff(self):
2313 def last_commit_cache_update_diff(self):
2293 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2314 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2294
2315
2295 @classmethod
2316 @classmethod
2296 def _load_commit_change(cls, last_commit_cache):
2317 def _load_commit_change(cls, last_commit_cache):
2297 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2318 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2298 empty_date = datetime.datetime.fromtimestamp(0)
2319 empty_date = datetime.datetime.fromtimestamp(0)
2299 date_latest = last_commit_cache.get('date', empty_date)
2320 date_latest = last_commit_cache.get('date', empty_date)
2300 try:
2321 try:
2301 return parse_datetime(date_latest)
2322 return parse_datetime(date_latest)
2302 except Exception:
2323 except Exception:
2303 return empty_date
2324 return empty_date
2304
2325
2305 @property
2326 @property
2306 def last_commit_change(self):
2327 def last_commit_change(self):
2307 return self._load_commit_change(self.changeset_cache)
2328 return self._load_commit_change(self.changeset_cache)
2308
2329
2309 @property
2330 @property
2310 def last_db_change(self):
2331 def last_db_change(self):
2311 return self.updated_on
2332 return self.updated_on
2312
2333
2313 @property
2334 @property
2314 def clone_uri_hidden(self):
2335 def clone_uri_hidden(self):
2315 clone_uri = self.clone_uri
2336 clone_uri = self.clone_uri
2316 if clone_uri:
2337 if clone_uri:
2317 import urlobject
2338 import urlobject
2318 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2339 url_obj = urlobject.URLObject(cleaned_uri(clone_uri))
2319 if url_obj.password:
2340 if url_obj.password:
2320 clone_uri = url_obj.with_password('*****')
2341 clone_uri = url_obj.with_password('*****')
2321 return clone_uri
2342 return clone_uri
2322
2343
2323 @property
2344 @property
2324 def push_uri_hidden(self):
2345 def push_uri_hidden(self):
2325 push_uri = self.push_uri
2346 push_uri = self.push_uri
2326 if push_uri:
2347 if push_uri:
2327 import urlobject
2348 import urlobject
2328 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2349 url_obj = urlobject.URLObject(cleaned_uri(push_uri))
2329 if url_obj.password:
2350 if url_obj.password:
2330 push_uri = url_obj.with_password('*****')
2351 push_uri = url_obj.with_password('*****')
2331 return push_uri
2352 return push_uri
2332
2353
2333 def clone_url(self, **override):
2354 def clone_url(self, **override):
2334 from rhodecode.model.settings import SettingsModel
2355 from rhodecode.model.settings import SettingsModel
2335
2356
2336 uri_tmpl = None
2357 uri_tmpl = None
2337 if 'with_id' in override:
2358 if 'with_id' in override:
2338 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2359 uri_tmpl = self.DEFAULT_CLONE_URI_ID
2339 del override['with_id']
2360 del override['with_id']
2340
2361
2341 if 'uri_tmpl' in override:
2362 if 'uri_tmpl' in override:
2342 uri_tmpl = override['uri_tmpl']
2363 uri_tmpl = override['uri_tmpl']
2343 del override['uri_tmpl']
2364 del override['uri_tmpl']
2344
2365
2345 ssh = False
2366 ssh = False
2346 if 'ssh' in override:
2367 if 'ssh' in override:
2347 ssh = True
2368 ssh = True
2348 del override['ssh']
2369 del override['ssh']
2349
2370
2350 # we didn't override our tmpl from **overrides
2371 # we didn't override our tmpl from **overrides
2351 request = get_current_request()
2372 request = get_current_request()
2352 if not uri_tmpl:
2373 if not uri_tmpl:
2353 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2374 if hasattr(request, 'call_context') and hasattr(request.call_context, 'rc_config'):
2354 rc_config = request.call_context.rc_config
2375 rc_config = request.call_context.rc_config
2355 else:
2376 else:
2356 rc_config = SettingsModel().get_all_settings(cache=True)
2377 rc_config = SettingsModel().get_all_settings(cache=True)
2357
2378
2358 if ssh:
2379 if ssh:
2359 uri_tmpl = rc_config.get(
2380 uri_tmpl = rc_config.get(
2360 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2381 'rhodecode_clone_uri_ssh_tmpl') or self.DEFAULT_CLONE_URI_SSH
2361
2382
2362 else:
2383 else:
2363 uri_tmpl = rc_config.get(
2384 uri_tmpl = rc_config.get(
2364 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2385 'rhodecode_clone_uri_tmpl') or self.DEFAULT_CLONE_URI
2365
2386
2366 return get_clone_url(request=request,
2387 return get_clone_url(request=request,
2367 uri_tmpl=uri_tmpl,
2388 uri_tmpl=uri_tmpl,
2368 repo_name=self.repo_name,
2389 repo_name=self.repo_name,
2369 repo_id=self.repo_id,
2390 repo_id=self.repo_id,
2370 repo_type=self.repo_type,
2391 repo_type=self.repo_type,
2371 **override)
2392 **override)
2372
2393
2373 def set_state(self, state):
2394 def set_state(self, state):
2374 self.repo_state = state
2395 self.repo_state = state
2375 Session().add(self)
2396 Session().add(self)
2376 #==========================================================================
2397 #==========================================================================
2377 # SCM PROPERTIES
2398 # SCM PROPERTIES
2378 #==========================================================================
2399 #==========================================================================
2379
2400
2380 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2401 def get_commit(self, commit_id=None, commit_idx=None, pre_load=None, maybe_unreachable=False):
2381 return get_commit_safe(
2402 return get_commit_safe(
2382 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2403 self.scm_instance(), commit_id, commit_idx, pre_load=pre_load,
2383 maybe_unreachable=maybe_unreachable)
2404 maybe_unreachable=maybe_unreachable)
2384
2405
2385 def get_changeset(self, rev=None, pre_load=None):
2406 def get_changeset(self, rev=None, pre_load=None):
2386 warnings.warn("Use get_commit", DeprecationWarning)
2407 warnings.warn("Use get_commit", DeprecationWarning)
2387 commit_id = None
2408 commit_id = None
2388 commit_idx = None
2409 commit_idx = None
2389 if isinstance(rev, compat.string_types):
2410 if isinstance(rev, compat.string_types):
2390 commit_id = rev
2411 commit_id = rev
2391 else:
2412 else:
2392 commit_idx = rev
2413 commit_idx = rev
2393 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2414 return self.get_commit(commit_id=commit_id, commit_idx=commit_idx,
2394 pre_load=pre_load)
2415 pre_load=pre_load)
2395
2416
2396 def get_landing_commit(self):
2417 def get_landing_commit(self):
2397 """
2418 """
2398 Returns landing commit, or if that doesn't exist returns the tip
2419 Returns landing commit, or if that doesn't exist returns the tip
2399 """
2420 """
2400 _rev_type, _rev = self.landing_rev
2421 _rev_type, _rev = self.landing_rev
2401 commit = self.get_commit(_rev)
2422 commit = self.get_commit(_rev)
2402 if isinstance(commit, EmptyCommit):
2423 if isinstance(commit, EmptyCommit):
2403 return self.get_commit()
2424 return self.get_commit()
2404 return commit
2425 return commit
2405
2426
2406 def flush_commit_cache(self):
2427 def flush_commit_cache(self):
2407 self.update_commit_cache(cs_cache={'raw_id':'0'})
2428 self.update_commit_cache(cs_cache={'raw_id':'0'})
2408 self.update_commit_cache()
2429 self.update_commit_cache()
2409
2430
2410 def update_commit_cache(self, cs_cache=None, config=None):
2431 def update_commit_cache(self, cs_cache=None, config=None):
2411 """
2432 """
2412 Update cache of last commit for repository
2433 Update cache of last commit for repository
2413 cache_keys should be::
2434 cache_keys should be::
2414
2435
2415 source_repo_id
2436 source_repo_id
2416 short_id
2437 short_id
2417 raw_id
2438 raw_id
2418 revision
2439 revision
2419 parents
2440 parents
2420 message
2441 message
2421 date
2442 date
2422 author
2443 author
2423 updated_on
2444 updated_on
2424
2445
2425 """
2446 """
2426 from rhodecode.lib.vcs.backends.base import BaseChangeset
2447 from rhodecode.lib.vcs.backends.base import BaseChangeset
2427 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2448 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2428 empty_date = datetime.datetime.fromtimestamp(0)
2449 empty_date = datetime.datetime.fromtimestamp(0)
2429
2450
2430 if cs_cache is None:
2451 if cs_cache is None:
2431 # use no-cache version here
2452 # use no-cache version here
2432 try:
2453 try:
2433 scm_repo = self.scm_instance(cache=False, config=config)
2454 scm_repo = self.scm_instance(cache=False, config=config)
2434 except VCSError:
2455 except VCSError:
2435 scm_repo = None
2456 scm_repo = None
2436 empty = scm_repo is None or scm_repo.is_empty()
2457 empty = scm_repo is None or scm_repo.is_empty()
2437
2458
2438 if not empty:
2459 if not empty:
2439 cs_cache = scm_repo.get_commit(
2460 cs_cache = scm_repo.get_commit(
2440 pre_load=["author", "date", "message", "parents", "branch"])
2461 pre_load=["author", "date", "message", "parents", "branch"])
2441 else:
2462 else:
2442 cs_cache = EmptyCommit()
2463 cs_cache = EmptyCommit()
2443
2464
2444 if isinstance(cs_cache, BaseChangeset):
2465 if isinstance(cs_cache, BaseChangeset):
2445 cs_cache = cs_cache.__json__()
2466 cs_cache = cs_cache.__json__()
2446
2467
2447 def is_outdated(new_cs_cache):
2468 def is_outdated(new_cs_cache):
2448 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2469 if (new_cs_cache['raw_id'] != self.changeset_cache['raw_id'] or
2449 new_cs_cache['revision'] != self.changeset_cache['revision']):
2470 new_cs_cache['revision'] != self.changeset_cache['revision']):
2450 return True
2471 return True
2451 return False
2472 return False
2452
2473
2453 # check if we have maybe already latest cached revision
2474 # check if we have maybe already latest cached revision
2454 if is_outdated(cs_cache) or not self.changeset_cache:
2475 if is_outdated(cs_cache) or not self.changeset_cache:
2455 _current_datetime = datetime.datetime.utcnow()
2476 _current_datetime = datetime.datetime.utcnow()
2456 last_change = cs_cache.get('date') or _current_datetime
2477 last_change = cs_cache.get('date') or _current_datetime
2457 # we check if last update is newer than the new value
2478 # we check if last update is newer than the new value
2458 # if yes, we use the current timestamp instead. Imagine you get
2479 # if yes, we use the current timestamp instead. Imagine you get
2459 # old commit pushed 1y ago, we'd set last update 1y to ago.
2480 # old commit pushed 1y ago, we'd set last update 1y to ago.
2460 last_change_timestamp = datetime_to_time(last_change)
2481 last_change_timestamp = datetime_to_time(last_change)
2461 current_timestamp = datetime_to_time(last_change)
2482 current_timestamp = datetime_to_time(last_change)
2462 if last_change_timestamp > current_timestamp and not empty:
2483 if last_change_timestamp > current_timestamp and not empty:
2463 cs_cache['date'] = _current_datetime
2484 cs_cache['date'] = _current_datetime
2464
2485
2465 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2486 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2466 cs_cache['updated_on'] = time.time()
2487 cs_cache['updated_on'] = time.time()
2467 self.changeset_cache = cs_cache
2488 self.changeset_cache = cs_cache
2468 self.updated_on = last_change
2489 self.updated_on = last_change
2469 Session().add(self)
2490 Session().add(self)
2470 Session().commit()
2491 Session().commit()
2471
2492
2472 else:
2493 else:
2473 if empty:
2494 if empty:
2474 cs_cache = EmptyCommit().__json__()
2495 cs_cache = EmptyCommit().__json__()
2475 else:
2496 else:
2476 cs_cache = self.changeset_cache
2497 cs_cache = self.changeset_cache
2477
2498
2478 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2499 _date_latest = parse_datetime(cs_cache.get('date') or empty_date)
2479
2500
2480 cs_cache['updated_on'] = time.time()
2501 cs_cache['updated_on'] = time.time()
2481 self.changeset_cache = cs_cache
2502 self.changeset_cache = cs_cache
2482 self.updated_on = _date_latest
2503 self.updated_on = _date_latest
2483 Session().add(self)
2504 Session().add(self)
2484 Session().commit()
2505 Session().commit()
2485
2506
2486 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2507 log.debug('updated repo `%s` with new commit cache %s, and last update_date: %s',
2487 self.repo_name, cs_cache, _date_latest)
2508 self.repo_name, cs_cache, _date_latest)
2488
2509
2489 @property
2510 @property
2490 def tip(self):
2511 def tip(self):
2491 return self.get_commit('tip')
2512 return self.get_commit('tip')
2492
2513
2493 @property
2514 @property
2494 def author(self):
2515 def author(self):
2495 return self.tip.author
2516 return self.tip.author
2496
2517
2497 @property
2518 @property
2498 def last_change(self):
2519 def last_change(self):
2499 return self.scm_instance().last_change
2520 return self.scm_instance().last_change
2500
2521
2501 def get_comments(self, revisions=None):
2522 def get_comments(self, revisions=None):
2502 """
2523 """
2503 Returns comments for this repository grouped by revisions
2524 Returns comments for this repository grouped by revisions
2504
2525
2505 :param revisions: filter query by revisions only
2526 :param revisions: filter query by revisions only
2506 """
2527 """
2507 cmts = ChangesetComment.query()\
2528 cmts = ChangesetComment.query()\
2508 .filter(ChangesetComment.repo == self)
2529 .filter(ChangesetComment.repo == self)
2509 if revisions:
2530 if revisions:
2510 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2531 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
2511 grouped = collections.defaultdict(list)
2532 grouped = collections.defaultdict(list)
2512 for cmt in cmts.all():
2533 for cmt in cmts.all():
2513 grouped[cmt.revision].append(cmt)
2534 grouped[cmt.revision].append(cmt)
2514 return grouped
2535 return grouped
2515
2536
2516 def statuses(self, revisions=None):
2537 def statuses(self, revisions=None):
2517 """
2538 """
2518 Returns statuses for this repository
2539 Returns statuses for this repository
2519
2540
2520 :param revisions: list of revisions to get statuses for
2541 :param revisions: list of revisions to get statuses for
2521 """
2542 """
2522 statuses = ChangesetStatus.query()\
2543 statuses = ChangesetStatus.query()\
2523 .filter(ChangesetStatus.repo == self)\
2544 .filter(ChangesetStatus.repo == self)\
2524 .filter(ChangesetStatus.version == 0)
2545 .filter(ChangesetStatus.version == 0)
2525
2546
2526 if revisions:
2547 if revisions:
2527 # Try doing the filtering in chunks to avoid hitting limits
2548 # Try doing the filtering in chunks to avoid hitting limits
2528 size = 500
2549 size = 500
2529 status_results = []
2550 status_results = []
2530 for chunk in xrange(0, len(revisions), size):
2551 for chunk in xrange(0, len(revisions), size):
2531 status_results += statuses.filter(
2552 status_results += statuses.filter(
2532 ChangesetStatus.revision.in_(
2553 ChangesetStatus.revision.in_(
2533 revisions[chunk: chunk+size])
2554 revisions[chunk: chunk+size])
2534 ).all()
2555 ).all()
2535 else:
2556 else:
2536 status_results = statuses.all()
2557 status_results = statuses.all()
2537
2558
2538 grouped = {}
2559 grouped = {}
2539
2560
2540 # maybe we have open new pullrequest without a status?
2561 # maybe we have open new pullrequest without a status?
2541 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2562 stat = ChangesetStatus.STATUS_UNDER_REVIEW
2542 status_lbl = ChangesetStatus.get_status_lbl(stat)
2563 status_lbl = ChangesetStatus.get_status_lbl(stat)
2543 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2564 for pr in PullRequest.query().filter(PullRequest.source_repo == self).all():
2544 for rev in pr.revisions:
2565 for rev in pr.revisions:
2545 pr_id = pr.pull_request_id
2566 pr_id = pr.pull_request_id
2546 pr_repo = pr.target_repo.repo_name
2567 pr_repo = pr.target_repo.repo_name
2547 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2568 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
2548
2569
2549 for stat in status_results:
2570 for stat in status_results:
2550 pr_id = pr_repo = None
2571 pr_id = pr_repo = None
2551 if stat.pull_request:
2572 if stat.pull_request:
2552 pr_id = stat.pull_request.pull_request_id
2573 pr_id = stat.pull_request.pull_request_id
2553 pr_repo = stat.pull_request.target_repo.repo_name
2574 pr_repo = stat.pull_request.target_repo.repo_name
2554 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2575 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
2555 pr_id, pr_repo]
2576 pr_id, pr_repo]
2556 return grouped
2577 return grouped
2557
2578
2558 # ==========================================================================
2579 # ==========================================================================
2559 # SCM CACHE INSTANCE
2580 # SCM CACHE INSTANCE
2560 # ==========================================================================
2581 # ==========================================================================
2561
2582
2562 def scm_instance(self, **kwargs):
2583 def scm_instance(self, **kwargs):
2563 import rhodecode
2584 import rhodecode
2564
2585
2565 # Passing a config will not hit the cache currently only used
2586 # Passing a config will not hit the cache currently only used
2566 # for repo2dbmapper
2587 # for repo2dbmapper
2567 config = kwargs.pop('config', None)
2588 config = kwargs.pop('config', None)
2568 cache = kwargs.pop('cache', None)
2589 cache = kwargs.pop('cache', None)
2569 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2590 vcs_full_cache = kwargs.pop('vcs_full_cache', None)
2570 if vcs_full_cache is not None:
2591 if vcs_full_cache is not None:
2571 # allows override global config
2592 # allows override global config
2572 full_cache = vcs_full_cache
2593 full_cache = vcs_full_cache
2573 else:
2594 else:
2574 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2595 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
2575 # if cache is NOT defined use default global, else we have a full
2596 # if cache is NOT defined use default global, else we have a full
2576 # control over cache behaviour
2597 # control over cache behaviour
2577 if cache is None and full_cache and not config:
2598 if cache is None and full_cache and not config:
2578 log.debug('Initializing pure cached instance for %s', self.repo_path)
2599 log.debug('Initializing pure cached instance for %s', self.repo_path)
2579 return self._get_instance_cached()
2600 return self._get_instance_cached()
2580
2601
2581 # cache here is sent to the "vcs server"
2602 # cache here is sent to the "vcs server"
2582 return self._get_instance(cache=bool(cache), config=config)
2603 return self._get_instance(cache=bool(cache), config=config)
2583
2604
2584 def _get_instance_cached(self):
2605 def _get_instance_cached(self):
2585 from rhodecode.lib import rc_cache
2606 from rhodecode.lib import rc_cache
2586
2607
2587 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2608 cache_namespace_uid = 'cache_repo_instance.{}'.format(self.repo_id)
2588 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2609 invalidation_namespace = CacheKey.REPO_INVALIDATION_NAMESPACE.format(
2589 repo_id=self.repo_id)
2610 repo_id=self.repo_id)
2590 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2611 region = rc_cache.get_or_create_region('cache_repo_longterm', cache_namespace_uid)
2591
2612
2592 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2613 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid)
2593 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2614 def get_instance_cached(repo_id, context_id, _cache_state_uid):
2594 return self._get_instance(repo_state_uid=_cache_state_uid)
2615 return self._get_instance(repo_state_uid=_cache_state_uid)
2595
2616
2596 # we must use thread scoped cache here,
2617 # we must use thread scoped cache here,
2597 # because each thread of gevent needs it's own not shared connection and cache
2618 # because each thread of gevent needs it's own not shared connection and cache
2598 # we also alter `args` so the cache key is individual for every green thread.
2619 # we also alter `args` so the cache key is individual for every green thread.
2599 inv_context_manager = rc_cache.InvalidationContext(
2620 inv_context_manager = rc_cache.InvalidationContext(
2600 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2621 uid=cache_namespace_uid, invalidation_namespace=invalidation_namespace,
2601 thread_scoped=True)
2622 thread_scoped=True)
2602 with inv_context_manager as invalidation_context:
2623 with inv_context_manager as invalidation_context:
2603 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2624 cache_state_uid = invalidation_context.cache_data['cache_state_uid']
2604 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2625 args = (self.repo_id, inv_context_manager.cache_key, cache_state_uid)
2605
2626
2606 # re-compute and store cache if we get invalidate signal
2627 # re-compute and store cache if we get invalidate signal
2607 if invalidation_context.should_invalidate():
2628 if invalidation_context.should_invalidate():
2608 instance = get_instance_cached.refresh(*args)
2629 instance = get_instance_cached.refresh(*args)
2609 else:
2630 else:
2610 instance = get_instance_cached(*args)
2631 instance = get_instance_cached(*args)
2611
2632
2612 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2633 log.debug('Repo instance fetched in %.4fs', inv_context_manager.compute_time)
2613 return instance
2634 return instance
2614
2635
2615 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2636 def _get_instance(self, cache=True, config=None, repo_state_uid=None):
2616 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2637 log.debug('Initializing %s instance `%s` with cache flag set to: %s',
2617 self.repo_type, self.repo_path, cache)
2638 self.repo_type, self.repo_path, cache)
2618 config = config or self._config
2639 config = config or self._config
2619 custom_wire = {
2640 custom_wire = {
2620 'cache': cache, # controls the vcs.remote cache
2641 'cache': cache, # controls the vcs.remote cache
2621 'repo_state_uid': repo_state_uid
2642 'repo_state_uid': repo_state_uid
2622 }
2643 }
2623 repo = get_vcs_instance(
2644 repo = get_vcs_instance(
2624 repo_path=safe_str(self.repo_full_path),
2645 repo_path=safe_str(self.repo_full_path),
2625 config=config,
2646 config=config,
2626 with_wire=custom_wire,
2647 with_wire=custom_wire,
2627 create=False,
2648 create=False,
2628 _vcs_alias=self.repo_type)
2649 _vcs_alias=self.repo_type)
2629 if repo is not None:
2650 if repo is not None:
2630 repo.count() # cache rebuild
2651 repo.count() # cache rebuild
2631 return repo
2652 return repo
2632
2653
2633 def get_shadow_repository_path(self, workspace_id):
2654 def get_shadow_repository_path(self, workspace_id):
2634 from rhodecode.lib.vcs.backends.base import BaseRepository
2655 from rhodecode.lib.vcs.backends.base import BaseRepository
2635 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2656 shadow_repo_path = BaseRepository._get_shadow_repository_path(
2636 self.repo_full_path, self.repo_id, workspace_id)
2657 self.repo_full_path, self.repo_id, workspace_id)
2637 return shadow_repo_path
2658 return shadow_repo_path
2638
2659
2639 def __json__(self):
2660 def __json__(self):
2640 return {'landing_rev': self.landing_rev}
2661 return {'landing_rev': self.landing_rev}
2641
2662
2642 def get_dict(self):
2663 def get_dict(self):
2643
2664
2644 # Since we transformed `repo_name` to a hybrid property, we need to
2665 # Since we transformed `repo_name` to a hybrid property, we need to
2645 # keep compatibility with the code which uses `repo_name` field.
2666 # keep compatibility with the code which uses `repo_name` field.
2646
2667
2647 result = super(Repository, self).get_dict()
2668 result = super(Repository, self).get_dict()
2648 result['repo_name'] = result.pop('_repo_name', None)
2669 result['repo_name'] = result.pop('_repo_name', None)
2649 return result
2670 return result
2650
2671
2651
2672
2652 class RepoGroup(Base, BaseModel):
2673 class RepoGroup(Base, BaseModel):
2653 __tablename__ = 'groups'
2674 __tablename__ = 'groups'
2654 __table_args__ = (
2675 __table_args__ = (
2655 UniqueConstraint('group_name', 'group_parent_id'),
2676 UniqueConstraint('group_name', 'group_parent_id'),
2656 base_table_args,
2677 base_table_args,
2657 )
2678 )
2658 __mapper_args__ = {'order_by': 'group_name'}
2679 __mapper_args__ = {'order_by': 'group_name'}
2659
2680
2660 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2681 CHOICES_SEPARATOR = '/' # used to generate select2 choices for nested groups
2661
2682
2662 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2683 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
2663 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2684 _group_name = Column("group_name", String(255), nullable=False, unique=True, default=None)
2664 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2685 group_name_hash = Column("repo_group_name_hash", String(1024), nullable=False, unique=False)
2665 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2686 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
2666 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2687 group_description = Column("group_description", String(10000), nullable=True, unique=None, default=None)
2667 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2688 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
2668 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2689 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
2669 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2690 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
2670 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2691 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
2671 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2692 personal = Column('personal', Boolean(), nullable=True, unique=None, default=None)
2672 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2693 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) # JSON data
2673
2694
2674 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2695 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
2675 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2696 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
2676 parent_group = relationship('RepoGroup', remote_side=group_id)
2697 parent_group = relationship('RepoGroup', remote_side=group_id)
2677 user = relationship('User')
2698 user = relationship('User')
2678 integrations = relationship('Integration', cascade="all, delete-orphan")
2699 integrations = relationship('Integration', cascade="all, delete-orphan")
2679
2700
2680 # no cascade, set NULL
2701 # no cascade, set NULL
2681 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2702 scope_artifacts = relationship('FileStore', primaryjoin='FileStore.scope_repo_group_id==RepoGroup.group_id')
2682
2703
2683 def __init__(self, group_name='', parent_group=None):
2704 def __init__(self, group_name='', parent_group=None):
2684 self.group_name = group_name
2705 self.group_name = group_name
2685 self.parent_group = parent_group
2706 self.parent_group = parent_group
2686
2707
2687 def __unicode__(self):
2708 def __unicode__(self):
2688 return u"<%s('id:%s:%s')>" % (
2709 return u"<%s('id:%s:%s')>" % (
2689 self.__class__.__name__, self.group_id, self.group_name)
2710 self.__class__.__name__, self.group_id, self.group_name)
2690
2711
2691 @hybrid_property
2712 @hybrid_property
2692 def group_name(self):
2713 def group_name(self):
2693 return self._group_name
2714 return self._group_name
2694
2715
2695 @group_name.setter
2716 @group_name.setter
2696 def group_name(self, value):
2717 def group_name(self, value):
2697 self._group_name = value
2718 self._group_name = value
2698 self.group_name_hash = self.hash_repo_group_name(value)
2719 self.group_name_hash = self.hash_repo_group_name(value)
2699
2720
2700 @classmethod
2721 @classmethod
2701 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2722 def _load_changeset_cache(cls, repo_id, changeset_cache_raw):
2702 from rhodecode.lib.vcs.backends.base import EmptyCommit
2723 from rhodecode.lib.vcs.backends.base import EmptyCommit
2703 dummy = EmptyCommit().__json__()
2724 dummy = EmptyCommit().__json__()
2704 if not changeset_cache_raw:
2725 if not changeset_cache_raw:
2705 dummy['source_repo_id'] = repo_id
2726 dummy['source_repo_id'] = repo_id
2706 return json.loads(json.dumps(dummy))
2727 return json.loads(json.dumps(dummy))
2707
2728
2708 try:
2729 try:
2709 return json.loads(changeset_cache_raw)
2730 return json.loads(changeset_cache_raw)
2710 except TypeError:
2731 except TypeError:
2711 return dummy
2732 return dummy
2712 except Exception:
2733 except Exception:
2713 log.error(traceback.format_exc())
2734 log.error(traceback.format_exc())
2714 return dummy
2735 return dummy
2715
2736
2716 @hybrid_property
2737 @hybrid_property
2717 def changeset_cache(self):
2738 def changeset_cache(self):
2718 return self._load_changeset_cache('', self._changeset_cache)
2739 return self._load_changeset_cache('', self._changeset_cache)
2719
2740
2720 @changeset_cache.setter
2741 @changeset_cache.setter
2721 def changeset_cache(self, val):
2742 def changeset_cache(self, val):
2722 try:
2743 try:
2723 self._changeset_cache = json.dumps(val)
2744 self._changeset_cache = json.dumps(val)
2724 except Exception:
2745 except Exception:
2725 log.error(traceback.format_exc())
2746 log.error(traceback.format_exc())
2726
2747
2727 @validates('group_parent_id')
2748 @validates('group_parent_id')
2728 def validate_group_parent_id(self, key, val):
2749 def validate_group_parent_id(self, key, val):
2729 """
2750 """
2730 Check cycle references for a parent group to self
2751 Check cycle references for a parent group to self
2731 """
2752 """
2732 if self.group_id and val:
2753 if self.group_id and val:
2733 assert val != self.group_id
2754 assert val != self.group_id
2734
2755
2735 return val
2756 return val
2736
2757
2737 @hybrid_property
2758 @hybrid_property
2738 def description_safe(self):
2759 def description_safe(self):
2739 from rhodecode.lib import helpers as h
2760 from rhodecode.lib import helpers as h
2740 return h.escape(self.group_description)
2761 return h.escape(self.group_description)
2741
2762
2742 @classmethod
2763 @classmethod
2743 def hash_repo_group_name(cls, repo_group_name):
2764 def hash_repo_group_name(cls, repo_group_name):
2744 val = remove_formatting(repo_group_name)
2765 val = remove_formatting(repo_group_name)
2745 val = safe_str(val).lower()
2766 val = safe_str(val).lower()
2746 chars = []
2767 chars = []
2747 for c in val:
2768 for c in val:
2748 if c not in string.ascii_letters:
2769 if c not in string.ascii_letters:
2749 c = str(ord(c))
2770 c = str(ord(c))
2750 chars.append(c)
2771 chars.append(c)
2751
2772
2752 return ''.join(chars)
2773 return ''.join(chars)
2753
2774
2754 @classmethod
2775 @classmethod
2755 def _generate_choice(cls, repo_group):
2776 def _generate_choice(cls, repo_group):
2756 from webhelpers2.html import literal as _literal
2777 from webhelpers2.html import literal as _literal
2757 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2778 _name = lambda k: _literal(cls.CHOICES_SEPARATOR.join(k))
2758 return repo_group.group_id, _name(repo_group.full_path_splitted)
2779 return repo_group.group_id, _name(repo_group.full_path_splitted)
2759
2780
2760 @classmethod
2781 @classmethod
2761 def groups_choices(cls, groups=None, show_empty_group=True):
2782 def groups_choices(cls, groups=None, show_empty_group=True):
2762 if not groups:
2783 if not groups:
2763 groups = cls.query().all()
2784 groups = cls.query().all()
2764
2785
2765 repo_groups = []
2786 repo_groups = []
2766 if show_empty_group:
2787 if show_empty_group:
2767 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2788 repo_groups = [(-1, u'-- %s --' % _('No parent'))]
2768
2789
2769 repo_groups.extend([cls._generate_choice(x) for x in groups])
2790 repo_groups.extend([cls._generate_choice(x) for x in groups])
2770
2791
2771 repo_groups = sorted(
2792 repo_groups = sorted(
2772 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2793 repo_groups, key=lambda t: t[1].split(cls.CHOICES_SEPARATOR)[0])
2773 return repo_groups
2794 return repo_groups
2774
2795
2775 @classmethod
2796 @classmethod
2776 def url_sep(cls):
2797 def url_sep(cls):
2777 return URL_SEP
2798 return URL_SEP
2778
2799
2779 @classmethod
2800 @classmethod
2780 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2801 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
2781 if case_insensitive:
2802 if case_insensitive:
2782 gr = cls.query().filter(func.lower(cls.group_name)
2803 gr = cls.query().filter(func.lower(cls.group_name)
2783 == func.lower(group_name))
2804 == func.lower(group_name))
2784 else:
2805 else:
2785 gr = cls.query().filter(cls.group_name == group_name)
2806 gr = cls.query().filter(cls.group_name == group_name)
2786 if cache:
2807 if cache:
2787 name_key = _hash_key(group_name)
2808 name_key = _hash_key(group_name)
2788 gr = gr.options(
2809 gr = gr.options(
2789 FromCache("sql_cache_short", "get_group_%s" % name_key))
2810 FromCache("sql_cache_short", "get_group_%s" % name_key))
2790 return gr.scalar()
2811 return gr.scalar()
2791
2812
2792 @classmethod
2813 @classmethod
2793 def get_user_personal_repo_group(cls, user_id):
2814 def get_user_personal_repo_group(cls, user_id):
2794 user = User.get(user_id)
2815 user = User.get(user_id)
2795 if user.username == User.DEFAULT_USER:
2816 if user.username == User.DEFAULT_USER:
2796 return None
2817 return None
2797
2818
2798 return cls.query()\
2819 return cls.query()\
2799 .filter(cls.personal == true()) \
2820 .filter(cls.personal == true()) \
2800 .filter(cls.user == user) \
2821 .filter(cls.user == user) \
2801 .order_by(cls.group_id.asc()) \
2822 .order_by(cls.group_id.asc()) \
2802 .first()
2823 .first()
2803
2824
2804 @classmethod
2825 @classmethod
2805 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2826 def get_all_repo_groups(cls, user_id=Optional(None), group_id=Optional(None),
2806 case_insensitive=True):
2827 case_insensitive=True):
2807 q = RepoGroup.query()
2828 q = RepoGroup.query()
2808
2829
2809 if not isinstance(user_id, Optional):
2830 if not isinstance(user_id, Optional):
2810 q = q.filter(RepoGroup.user_id == user_id)
2831 q = q.filter(RepoGroup.user_id == user_id)
2811
2832
2812 if not isinstance(group_id, Optional):
2833 if not isinstance(group_id, Optional):
2813 q = q.filter(RepoGroup.group_parent_id == group_id)
2834 q = q.filter(RepoGroup.group_parent_id == group_id)
2814
2835
2815 if case_insensitive:
2836 if case_insensitive:
2816 q = q.order_by(func.lower(RepoGroup.group_name))
2837 q = q.order_by(func.lower(RepoGroup.group_name))
2817 else:
2838 else:
2818 q = q.order_by(RepoGroup.group_name)
2839 q = q.order_by(RepoGroup.group_name)
2819 return q.all()
2840 return q.all()
2820
2841
2821 @property
2842 @property
2822 def parents(self, parents_recursion_limit=10):
2843 def parents(self, parents_recursion_limit=10):
2823 groups = []
2844 groups = []
2824 if self.parent_group is None:
2845 if self.parent_group is None:
2825 return groups
2846 return groups
2826 cur_gr = self.parent_group
2847 cur_gr = self.parent_group
2827 groups.insert(0, cur_gr)
2848 groups.insert(0, cur_gr)
2828 cnt = 0
2849 cnt = 0
2829 while 1:
2850 while 1:
2830 cnt += 1
2851 cnt += 1
2831 gr = getattr(cur_gr, 'parent_group', None)
2852 gr = getattr(cur_gr, 'parent_group', None)
2832 cur_gr = cur_gr.parent_group
2853 cur_gr = cur_gr.parent_group
2833 if gr is None:
2854 if gr is None:
2834 break
2855 break
2835 if cnt == parents_recursion_limit:
2856 if cnt == parents_recursion_limit:
2836 # this will prevent accidental infinit loops
2857 # this will prevent accidental infinit loops
2837 log.error('more than %s parents found for group %s, stopping '
2858 log.error('more than %s parents found for group %s, stopping '
2838 'recursive parent fetching', parents_recursion_limit, self)
2859 'recursive parent fetching', parents_recursion_limit, self)
2839 break
2860 break
2840
2861
2841 groups.insert(0, gr)
2862 groups.insert(0, gr)
2842 return groups
2863 return groups
2843
2864
2844 @property
2865 @property
2845 def last_commit_cache_update_diff(self):
2866 def last_commit_cache_update_diff(self):
2846 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2867 return time.time() - (safe_int(self.changeset_cache.get('updated_on')) or 0)
2847
2868
2848 @classmethod
2869 @classmethod
2849 def _load_commit_change(cls, last_commit_cache):
2870 def _load_commit_change(cls, last_commit_cache):
2850 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2871 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2851 empty_date = datetime.datetime.fromtimestamp(0)
2872 empty_date = datetime.datetime.fromtimestamp(0)
2852 date_latest = last_commit_cache.get('date', empty_date)
2873 date_latest = last_commit_cache.get('date', empty_date)
2853 try:
2874 try:
2854 return parse_datetime(date_latest)
2875 return parse_datetime(date_latest)
2855 except Exception:
2876 except Exception:
2856 return empty_date
2877 return empty_date
2857
2878
2858 @property
2879 @property
2859 def last_commit_change(self):
2880 def last_commit_change(self):
2860 return self._load_commit_change(self.changeset_cache)
2881 return self._load_commit_change(self.changeset_cache)
2861
2882
2862 @property
2883 @property
2863 def last_db_change(self):
2884 def last_db_change(self):
2864 return self.updated_on
2885 return self.updated_on
2865
2886
2866 @property
2887 @property
2867 def children(self):
2888 def children(self):
2868 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2889 return RepoGroup.query().filter(RepoGroup.parent_group == self)
2869
2890
2870 @property
2891 @property
2871 def name(self):
2892 def name(self):
2872 return self.group_name.split(RepoGroup.url_sep())[-1]
2893 return self.group_name.split(RepoGroup.url_sep())[-1]
2873
2894
2874 @property
2895 @property
2875 def full_path(self):
2896 def full_path(self):
2876 return self.group_name
2897 return self.group_name
2877
2898
2878 @property
2899 @property
2879 def full_path_splitted(self):
2900 def full_path_splitted(self):
2880 return self.group_name.split(RepoGroup.url_sep())
2901 return self.group_name.split(RepoGroup.url_sep())
2881
2902
2882 @property
2903 @property
2883 def repositories(self):
2904 def repositories(self):
2884 return Repository.query()\
2905 return Repository.query()\
2885 .filter(Repository.group == self)\
2906 .filter(Repository.group == self)\
2886 .order_by(Repository.repo_name)
2907 .order_by(Repository.repo_name)
2887
2908
2888 @property
2909 @property
2889 def repositories_recursive_count(self):
2910 def repositories_recursive_count(self):
2890 cnt = self.repositories.count()
2911 cnt = self.repositories.count()
2891
2912
2892 def children_count(group):
2913 def children_count(group):
2893 cnt = 0
2914 cnt = 0
2894 for child in group.children:
2915 for child in group.children:
2895 cnt += child.repositories.count()
2916 cnt += child.repositories.count()
2896 cnt += children_count(child)
2917 cnt += children_count(child)
2897 return cnt
2918 return cnt
2898
2919
2899 return cnt + children_count(self)
2920 return cnt + children_count(self)
2900
2921
2901 def _recursive_objects(self, include_repos=True, include_groups=True):
2922 def _recursive_objects(self, include_repos=True, include_groups=True):
2902 all_ = []
2923 all_ = []
2903
2924
2904 def _get_members(root_gr):
2925 def _get_members(root_gr):
2905 if include_repos:
2926 if include_repos:
2906 for r in root_gr.repositories:
2927 for r in root_gr.repositories:
2907 all_.append(r)
2928 all_.append(r)
2908 childs = root_gr.children.all()
2929 childs = root_gr.children.all()
2909 if childs:
2930 if childs:
2910 for gr in childs:
2931 for gr in childs:
2911 if include_groups:
2932 if include_groups:
2912 all_.append(gr)
2933 all_.append(gr)
2913 _get_members(gr)
2934 _get_members(gr)
2914
2935
2915 root_group = []
2936 root_group = []
2916 if include_groups:
2937 if include_groups:
2917 root_group = [self]
2938 root_group = [self]
2918
2939
2919 _get_members(self)
2940 _get_members(self)
2920 return root_group + all_
2941 return root_group + all_
2921
2942
2922 def recursive_groups_and_repos(self):
2943 def recursive_groups_and_repos(self):
2923 """
2944 """
2924 Recursive return all groups, with repositories in those groups
2945 Recursive return all groups, with repositories in those groups
2925 """
2946 """
2926 return self._recursive_objects()
2947 return self._recursive_objects()
2927
2948
2928 def recursive_groups(self):
2949 def recursive_groups(self):
2929 """
2950 """
2930 Returns all children groups for this group including children of children
2951 Returns all children groups for this group including children of children
2931 """
2952 """
2932 return self._recursive_objects(include_repos=False)
2953 return self._recursive_objects(include_repos=False)
2933
2954
2934 def recursive_repos(self):
2955 def recursive_repos(self):
2935 """
2956 """
2936 Returns all children repositories for this group
2957 Returns all children repositories for this group
2937 """
2958 """
2938 return self._recursive_objects(include_groups=False)
2959 return self._recursive_objects(include_groups=False)
2939
2960
2940 def get_new_name(self, group_name):
2961 def get_new_name(self, group_name):
2941 """
2962 """
2942 returns new full group name based on parent and new name
2963 returns new full group name based on parent and new name
2943
2964
2944 :param group_name:
2965 :param group_name:
2945 """
2966 """
2946 path_prefix = (self.parent_group.full_path_splitted if
2967 path_prefix = (self.parent_group.full_path_splitted if
2947 self.parent_group else [])
2968 self.parent_group else [])
2948 return RepoGroup.url_sep().join(path_prefix + [group_name])
2969 return RepoGroup.url_sep().join(path_prefix + [group_name])
2949
2970
2950 def update_commit_cache(self, config=None):
2971 def update_commit_cache(self, config=None):
2951 """
2972 """
2952 Update cache of last commit for newest repository inside this repository group.
2973 Update cache of last commit for newest repository inside this repository group.
2953 cache_keys should be::
2974 cache_keys should be::
2954
2975
2955 source_repo_id
2976 source_repo_id
2956 short_id
2977 short_id
2957 raw_id
2978 raw_id
2958 revision
2979 revision
2959 parents
2980 parents
2960 message
2981 message
2961 date
2982 date
2962 author
2983 author
2963
2984
2964 """
2985 """
2965 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2986 from rhodecode.lib.vcs.utils.helpers import parse_datetime
2966 empty_date = datetime.datetime.fromtimestamp(0)
2987 empty_date = datetime.datetime.fromtimestamp(0)
2967
2988
2968 def repo_groups_and_repos(root_gr):
2989 def repo_groups_and_repos(root_gr):
2969 for _repo in root_gr.repositories:
2990 for _repo in root_gr.repositories:
2970 yield _repo
2991 yield _repo
2971 for child_group in root_gr.children.all():
2992 for child_group in root_gr.children.all():
2972 yield child_group
2993 yield child_group
2973
2994
2974 latest_repo_cs_cache = {}
2995 latest_repo_cs_cache = {}
2975 for obj in repo_groups_and_repos(self):
2996 for obj in repo_groups_and_repos(self):
2976 repo_cs_cache = obj.changeset_cache
2997 repo_cs_cache = obj.changeset_cache
2977 date_latest = latest_repo_cs_cache.get('date', empty_date)
2998 date_latest = latest_repo_cs_cache.get('date', empty_date)
2978 date_current = repo_cs_cache.get('date', empty_date)
2999 date_current = repo_cs_cache.get('date', empty_date)
2979 current_timestamp = datetime_to_time(parse_datetime(date_latest))
3000 current_timestamp = datetime_to_time(parse_datetime(date_latest))
2980 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
3001 if current_timestamp < datetime_to_time(parse_datetime(date_current)):
2981 latest_repo_cs_cache = repo_cs_cache
3002 latest_repo_cs_cache = repo_cs_cache
2982 if hasattr(obj, 'repo_id'):
3003 if hasattr(obj, 'repo_id'):
2983 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
3004 latest_repo_cs_cache['source_repo_id'] = obj.repo_id
2984 else:
3005 else:
2985 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
3006 latest_repo_cs_cache['source_repo_id'] = repo_cs_cache.get('source_repo_id')
2986
3007
2987 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
3008 _date_latest = parse_datetime(latest_repo_cs_cache.get('date') or empty_date)
2988
3009
2989 latest_repo_cs_cache['updated_on'] = time.time()
3010 latest_repo_cs_cache['updated_on'] = time.time()
2990 self.changeset_cache = latest_repo_cs_cache
3011 self.changeset_cache = latest_repo_cs_cache
2991 self.updated_on = _date_latest
3012 self.updated_on = _date_latest
2992 Session().add(self)
3013 Session().add(self)
2993 Session().commit()
3014 Session().commit()
2994
3015
2995 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
3016 log.debug('updated repo group `%s` with new commit cache %s, and last update_date: %s',
2996 self.group_name, latest_repo_cs_cache, _date_latest)
3017 self.group_name, latest_repo_cs_cache, _date_latest)
2997
3018
2998 def permissions(self, with_admins=True, with_owner=True,
3019 def permissions(self, with_admins=True, with_owner=True,
2999 expand_from_user_groups=False):
3020 expand_from_user_groups=False):
3000 """
3021 """
3001 Permissions for repository groups
3022 Permissions for repository groups
3002 """
3023 """
3003 _admin_perm = 'group.admin'
3024 _admin_perm = 'group.admin'
3004
3025
3005 owner_row = []
3026 owner_row = []
3006 if with_owner:
3027 if with_owner:
3007 usr = AttributeDict(self.user.get_dict())
3028 usr = AttributeDict(self.user.get_dict())
3008 usr.owner_row = True
3029 usr.owner_row = True
3009 usr.permission = _admin_perm
3030 usr.permission = _admin_perm
3010 owner_row.append(usr)
3031 owner_row.append(usr)
3011
3032
3012 super_admin_ids = []
3033 super_admin_ids = []
3013 super_admin_rows = []
3034 super_admin_rows = []
3014 if with_admins:
3035 if with_admins:
3015 for usr in User.get_all_super_admins():
3036 for usr in User.get_all_super_admins():
3016 super_admin_ids.append(usr.user_id)
3037 super_admin_ids.append(usr.user_id)
3017 # if this admin is also owner, don't double the record
3038 # if this admin is also owner, don't double the record
3018 if usr.user_id == owner_row[0].user_id:
3039 if usr.user_id == owner_row[0].user_id:
3019 owner_row[0].admin_row = True
3040 owner_row[0].admin_row = True
3020 else:
3041 else:
3021 usr = AttributeDict(usr.get_dict())
3042 usr = AttributeDict(usr.get_dict())
3022 usr.admin_row = True
3043 usr.admin_row = True
3023 usr.permission = _admin_perm
3044 usr.permission = _admin_perm
3024 super_admin_rows.append(usr)
3045 super_admin_rows.append(usr)
3025
3046
3026 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3047 q = UserRepoGroupToPerm.query().filter(UserRepoGroupToPerm.group == self)
3027 q = q.options(joinedload(UserRepoGroupToPerm.group),
3048 q = q.options(joinedload(UserRepoGroupToPerm.group),
3028 joinedload(UserRepoGroupToPerm.user),
3049 joinedload(UserRepoGroupToPerm.user),
3029 joinedload(UserRepoGroupToPerm.permission),)
3050 joinedload(UserRepoGroupToPerm.permission),)
3030
3051
3031 # get owners and admins and permissions. We do a trick of re-writing
3052 # get owners and admins and permissions. We do a trick of re-writing
3032 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3053 # objects from sqlalchemy to named-tuples due to sqlalchemy session
3033 # has a global reference and changing one object propagates to all
3054 # has a global reference and changing one object propagates to all
3034 # others. This means if admin is also an owner admin_row that change
3055 # others. This means if admin is also an owner admin_row that change
3035 # would propagate to both objects
3056 # would propagate to both objects
3036 perm_rows = []
3057 perm_rows = []
3037 for _usr in q.all():
3058 for _usr in q.all():
3038 usr = AttributeDict(_usr.user.get_dict())
3059 usr = AttributeDict(_usr.user.get_dict())
3039 # if this user is also owner/admin, mark as duplicate record
3060 # if this user is also owner/admin, mark as duplicate record
3040 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3061 if usr.user_id == owner_row[0].user_id or usr.user_id in super_admin_ids:
3041 usr.duplicate_perm = True
3062 usr.duplicate_perm = True
3042 usr.permission = _usr.permission.permission_name
3063 usr.permission = _usr.permission.permission_name
3043 perm_rows.append(usr)
3064 perm_rows.append(usr)
3044
3065
3045 # filter the perm rows by 'default' first and then sort them by
3066 # filter the perm rows by 'default' first and then sort them by
3046 # admin,write,read,none permissions sorted again alphabetically in
3067 # admin,write,read,none permissions sorted again alphabetically in
3047 # each group
3068 # each group
3048 perm_rows = sorted(perm_rows, key=display_user_sort)
3069 perm_rows = sorted(perm_rows, key=display_user_sort)
3049
3070
3050 user_groups_rows = []
3071 user_groups_rows = []
3051 if expand_from_user_groups:
3072 if expand_from_user_groups:
3052 for ug in self.permission_user_groups(with_members=True):
3073 for ug in self.permission_user_groups(with_members=True):
3053 for user_data in ug.members:
3074 for user_data in ug.members:
3054 user_groups_rows.append(user_data)
3075 user_groups_rows.append(user_data)
3055
3076
3056 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3077 return super_admin_rows + owner_row + perm_rows + user_groups_rows
3057
3078
3058 def permission_user_groups(self, with_members=False):
3079 def permission_user_groups(self, with_members=False):
3059 q = UserGroupRepoGroupToPerm.query()\
3080 q = UserGroupRepoGroupToPerm.query()\
3060 .filter(UserGroupRepoGroupToPerm.group == self)
3081 .filter(UserGroupRepoGroupToPerm.group == self)
3061 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3082 q = q.options(joinedload(UserGroupRepoGroupToPerm.group),
3062 joinedload(UserGroupRepoGroupToPerm.users_group),
3083 joinedload(UserGroupRepoGroupToPerm.users_group),
3063 joinedload(UserGroupRepoGroupToPerm.permission),)
3084 joinedload(UserGroupRepoGroupToPerm.permission),)
3064
3085
3065 perm_rows = []
3086 perm_rows = []
3066 for _user_group in q.all():
3087 for _user_group in q.all():
3067 entry = AttributeDict(_user_group.users_group.get_dict())
3088 entry = AttributeDict(_user_group.users_group.get_dict())
3068 entry.permission = _user_group.permission.permission_name
3089 entry.permission = _user_group.permission.permission_name
3069 if with_members:
3090 if with_members:
3070 entry.members = [x.user.get_dict()
3091 entry.members = [x.user.get_dict()
3071 for x in _user_group.users_group.members]
3092 for x in _user_group.users_group.members]
3072 perm_rows.append(entry)
3093 perm_rows.append(entry)
3073
3094
3074 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3095 perm_rows = sorted(perm_rows, key=display_user_group_sort)
3075 return perm_rows
3096 return perm_rows
3076
3097
3077 def get_api_data(self):
3098 def get_api_data(self):
3078 """
3099 """
3079 Common function for generating api data
3100 Common function for generating api data
3080
3101
3081 """
3102 """
3082 group = self
3103 group = self
3083 data = {
3104 data = {
3084 'group_id': group.group_id,
3105 'group_id': group.group_id,
3085 'group_name': group.group_name,
3106 'group_name': group.group_name,
3086 'group_description': group.description_safe,
3107 'group_description': group.description_safe,
3087 'parent_group': group.parent_group.group_name if group.parent_group else None,
3108 'parent_group': group.parent_group.group_name if group.parent_group else None,
3088 'repositories': [x.repo_name for x in group.repositories],
3109 'repositories': [x.repo_name for x in group.repositories],
3089 'owner': group.user.username,
3110 'owner': group.user.username,
3090 }
3111 }
3091 return data
3112 return data
3092
3113
3093 def get_dict(self):
3114 def get_dict(self):
3094 # Since we transformed `group_name` to a hybrid property, we need to
3115 # Since we transformed `group_name` to a hybrid property, we need to
3095 # keep compatibility with the code which uses `group_name` field.
3116 # keep compatibility with the code which uses `group_name` field.
3096 result = super(RepoGroup, self).get_dict()
3117 result = super(RepoGroup, self).get_dict()
3097 result['group_name'] = result.pop('_group_name', None)
3118 result['group_name'] = result.pop('_group_name', None)
3098 return result
3119 return result
3099
3120
3100
3121
3101 class Permission(Base, BaseModel):
3122 class Permission(Base, BaseModel):
3102 __tablename__ = 'permissions'
3123 __tablename__ = 'permissions'
3103 __table_args__ = (
3124 __table_args__ = (
3104 Index('p_perm_name_idx', 'permission_name'),
3125 Index('p_perm_name_idx', 'permission_name'),
3105 base_table_args,
3126 base_table_args,
3106 )
3127 )
3107
3128
3108 PERMS = [
3129 PERMS = [
3109 ('hg.admin', _('RhodeCode Super Administrator')),
3130 ('hg.admin', _('RhodeCode Super Administrator')),
3110
3131
3111 ('repository.none', _('Repository no access')),
3132 ('repository.none', _('Repository no access')),
3112 ('repository.read', _('Repository read access')),
3133 ('repository.read', _('Repository read access')),
3113 ('repository.write', _('Repository write access')),
3134 ('repository.write', _('Repository write access')),
3114 ('repository.admin', _('Repository admin access')),
3135 ('repository.admin', _('Repository admin access')),
3115
3136
3116 ('group.none', _('Repository group no access')),
3137 ('group.none', _('Repository group no access')),
3117 ('group.read', _('Repository group read access')),
3138 ('group.read', _('Repository group read access')),
3118 ('group.write', _('Repository group write access')),
3139 ('group.write', _('Repository group write access')),
3119 ('group.admin', _('Repository group admin access')),
3140 ('group.admin', _('Repository group admin access')),
3120
3141
3121 ('usergroup.none', _('User group no access')),
3142 ('usergroup.none', _('User group no access')),
3122 ('usergroup.read', _('User group read access')),
3143 ('usergroup.read', _('User group read access')),
3123 ('usergroup.write', _('User group write access')),
3144 ('usergroup.write', _('User group write access')),
3124 ('usergroup.admin', _('User group admin access')),
3145 ('usergroup.admin', _('User group admin access')),
3125
3146
3126 ('branch.none', _('Branch no permissions')),
3147 ('branch.none', _('Branch no permissions')),
3127 ('branch.merge', _('Branch access by web merge')),
3148 ('branch.merge', _('Branch access by web merge')),
3128 ('branch.push', _('Branch access by push')),
3149 ('branch.push', _('Branch access by push')),
3129 ('branch.push_force', _('Branch access by push with force')),
3150 ('branch.push_force', _('Branch access by push with force')),
3130
3151
3131 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3152 ('hg.repogroup.create.false', _('Repository Group creation disabled')),
3132 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3153 ('hg.repogroup.create.true', _('Repository Group creation enabled')),
3133
3154
3134 ('hg.usergroup.create.false', _('User Group creation disabled')),
3155 ('hg.usergroup.create.false', _('User Group creation disabled')),
3135 ('hg.usergroup.create.true', _('User Group creation enabled')),
3156 ('hg.usergroup.create.true', _('User Group creation enabled')),
3136
3157
3137 ('hg.create.none', _('Repository creation disabled')),
3158 ('hg.create.none', _('Repository creation disabled')),
3138 ('hg.create.repository', _('Repository creation enabled')),
3159 ('hg.create.repository', _('Repository creation enabled')),
3139 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3160 ('hg.create.write_on_repogroup.true', _('Repository creation enabled with write permission to a repository group')),
3140 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3161 ('hg.create.write_on_repogroup.false', _('Repository creation disabled with write permission to a repository group')),
3141
3162
3142 ('hg.fork.none', _('Repository forking disabled')),
3163 ('hg.fork.none', _('Repository forking disabled')),
3143 ('hg.fork.repository', _('Repository forking enabled')),
3164 ('hg.fork.repository', _('Repository forking enabled')),
3144
3165
3145 ('hg.register.none', _('Registration disabled')),
3166 ('hg.register.none', _('Registration disabled')),
3146 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3167 ('hg.register.manual_activate', _('User Registration with manual account activation')),
3147 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3168 ('hg.register.auto_activate', _('User Registration with automatic account activation')),
3148
3169
3149 ('hg.password_reset.enabled', _('Password reset enabled')),
3170 ('hg.password_reset.enabled', _('Password reset enabled')),
3150 ('hg.password_reset.hidden', _('Password reset hidden')),
3171 ('hg.password_reset.hidden', _('Password reset hidden')),
3151 ('hg.password_reset.disabled', _('Password reset disabled')),
3172 ('hg.password_reset.disabled', _('Password reset disabled')),
3152
3173
3153 ('hg.extern_activate.manual', _('Manual activation of external account')),
3174 ('hg.extern_activate.manual', _('Manual activation of external account')),
3154 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3175 ('hg.extern_activate.auto', _('Automatic activation of external account')),
3155
3176
3156 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3177 ('hg.inherit_default_perms.false', _('Inherit object permissions from default user disabled')),
3157 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3178 ('hg.inherit_default_perms.true', _('Inherit object permissions from default user enabled')),
3158 ]
3179 ]
3159
3180
3160 # definition of system default permissions for DEFAULT user, created on
3181 # definition of system default permissions for DEFAULT user, created on
3161 # system setup
3182 # system setup
3162 DEFAULT_USER_PERMISSIONS = [
3183 DEFAULT_USER_PERMISSIONS = [
3163 # object perms
3184 # object perms
3164 'repository.read',
3185 'repository.read',
3165 'group.read',
3186 'group.read',
3166 'usergroup.read',
3187 'usergroup.read',
3167 # branch, for backward compat we need same value as before so forced pushed
3188 # branch, for backward compat we need same value as before so forced pushed
3168 'branch.push_force',
3189 'branch.push_force',
3169 # global
3190 # global
3170 'hg.create.repository',
3191 'hg.create.repository',
3171 'hg.repogroup.create.false',
3192 'hg.repogroup.create.false',
3172 'hg.usergroup.create.false',
3193 'hg.usergroup.create.false',
3173 'hg.create.write_on_repogroup.true',
3194 'hg.create.write_on_repogroup.true',
3174 'hg.fork.repository',
3195 'hg.fork.repository',
3175 'hg.register.manual_activate',
3196 'hg.register.manual_activate',
3176 'hg.password_reset.enabled',
3197 'hg.password_reset.enabled',
3177 'hg.extern_activate.auto',
3198 'hg.extern_activate.auto',
3178 'hg.inherit_default_perms.true',
3199 'hg.inherit_default_perms.true',
3179 ]
3200 ]
3180
3201
3181 # defines which permissions are more important higher the more important
3202 # defines which permissions are more important higher the more important
3182 # Weight defines which permissions are more important.
3203 # Weight defines which permissions are more important.
3183 # The higher number the more important.
3204 # The higher number the more important.
3184 PERM_WEIGHTS = {
3205 PERM_WEIGHTS = {
3185 'repository.none': 0,
3206 'repository.none': 0,
3186 'repository.read': 1,
3207 'repository.read': 1,
3187 'repository.write': 3,
3208 'repository.write': 3,
3188 'repository.admin': 4,
3209 'repository.admin': 4,
3189
3210
3190 'group.none': 0,
3211 'group.none': 0,
3191 'group.read': 1,
3212 'group.read': 1,
3192 'group.write': 3,
3213 'group.write': 3,
3193 'group.admin': 4,
3214 'group.admin': 4,
3194
3215
3195 'usergroup.none': 0,
3216 'usergroup.none': 0,
3196 'usergroup.read': 1,
3217 'usergroup.read': 1,
3197 'usergroup.write': 3,
3218 'usergroup.write': 3,
3198 'usergroup.admin': 4,
3219 'usergroup.admin': 4,
3199
3220
3200 'branch.none': 0,
3221 'branch.none': 0,
3201 'branch.merge': 1,
3222 'branch.merge': 1,
3202 'branch.push': 3,
3223 'branch.push': 3,
3203 'branch.push_force': 4,
3224 'branch.push_force': 4,
3204
3225
3205 'hg.repogroup.create.false': 0,
3226 'hg.repogroup.create.false': 0,
3206 'hg.repogroup.create.true': 1,
3227 'hg.repogroup.create.true': 1,
3207
3228
3208 'hg.usergroup.create.false': 0,
3229 'hg.usergroup.create.false': 0,
3209 'hg.usergroup.create.true': 1,
3230 'hg.usergroup.create.true': 1,
3210
3231
3211 'hg.fork.none': 0,
3232 'hg.fork.none': 0,
3212 'hg.fork.repository': 1,
3233 'hg.fork.repository': 1,
3213 'hg.create.none': 0,
3234 'hg.create.none': 0,
3214 'hg.create.repository': 1
3235 'hg.create.repository': 1
3215 }
3236 }
3216
3237
3217 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3238 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3218 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3239 permission_name = Column("permission_name", String(255), nullable=True, unique=None, default=None)
3219 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3240 permission_longname = Column("permission_longname", String(255), nullable=True, unique=None, default=None)
3220
3241
3221 def __unicode__(self):
3242 def __unicode__(self):
3222 return u"<%s('%s:%s')>" % (
3243 return u"<%s('%s:%s')>" % (
3223 self.__class__.__name__, self.permission_id, self.permission_name
3244 self.__class__.__name__, self.permission_id, self.permission_name
3224 )
3245 )
3225
3246
3226 @classmethod
3247 @classmethod
3227 def get_by_key(cls, key):
3248 def get_by_key(cls, key):
3228 return cls.query().filter(cls.permission_name == key).scalar()
3249 return cls.query().filter(cls.permission_name == key).scalar()
3229
3250
3230 @classmethod
3251 @classmethod
3231 def get_default_repo_perms(cls, user_id, repo_id=None):
3252 def get_default_repo_perms(cls, user_id, repo_id=None):
3232 q = Session().query(UserRepoToPerm, Repository, Permission)\
3253 q = Session().query(UserRepoToPerm, Repository, Permission)\
3233 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3254 .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
3234 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3255 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
3235 .filter(UserRepoToPerm.user_id == user_id)
3256 .filter(UserRepoToPerm.user_id == user_id)
3236 if repo_id:
3257 if repo_id:
3237 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3258 q = q.filter(UserRepoToPerm.repository_id == repo_id)
3238 return q.all()
3259 return q.all()
3239
3260
3240 @classmethod
3261 @classmethod
3241 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3262 def get_default_repo_branch_perms(cls, user_id, repo_id=None):
3242 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3263 q = Session().query(UserToRepoBranchPermission, UserRepoToPerm, Permission) \
3243 .join(
3264 .join(
3244 Permission,
3265 Permission,
3245 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3266 UserToRepoBranchPermission.permission_id == Permission.permission_id) \
3246 .join(
3267 .join(
3247 UserRepoToPerm,
3268 UserRepoToPerm,
3248 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3269 UserToRepoBranchPermission.rule_to_perm_id == UserRepoToPerm.repo_to_perm_id) \
3249 .filter(UserRepoToPerm.user_id == user_id)
3270 .filter(UserRepoToPerm.user_id == user_id)
3250
3271
3251 if repo_id:
3272 if repo_id:
3252 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3273 q = q.filter(UserToRepoBranchPermission.repository_id == repo_id)
3253 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3274 return q.order_by(UserToRepoBranchPermission.rule_order).all()
3254
3275
3255 @classmethod
3276 @classmethod
3256 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3277 def get_default_repo_perms_from_user_group(cls, user_id, repo_id=None):
3257 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3278 q = Session().query(UserGroupRepoToPerm, Repository, Permission)\
3258 .join(
3279 .join(
3259 Permission,
3280 Permission,
3260 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3281 UserGroupRepoToPerm.permission_id == Permission.permission_id)\
3261 .join(
3282 .join(
3262 Repository,
3283 Repository,
3263 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3284 UserGroupRepoToPerm.repository_id == Repository.repo_id)\
3264 .join(
3285 .join(
3265 UserGroup,
3286 UserGroup,
3266 UserGroupRepoToPerm.users_group_id ==
3287 UserGroupRepoToPerm.users_group_id ==
3267 UserGroup.users_group_id)\
3288 UserGroup.users_group_id)\
3268 .join(
3289 .join(
3269 UserGroupMember,
3290 UserGroupMember,
3270 UserGroupRepoToPerm.users_group_id ==
3291 UserGroupRepoToPerm.users_group_id ==
3271 UserGroupMember.users_group_id)\
3292 UserGroupMember.users_group_id)\
3272 .filter(
3293 .filter(
3273 UserGroupMember.user_id == user_id,
3294 UserGroupMember.user_id == user_id,
3274 UserGroup.users_group_active == true())
3295 UserGroup.users_group_active == true())
3275 if repo_id:
3296 if repo_id:
3276 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3297 q = q.filter(UserGroupRepoToPerm.repository_id == repo_id)
3277 return q.all()
3298 return q.all()
3278
3299
3279 @classmethod
3300 @classmethod
3280 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3301 def get_default_repo_branch_perms_from_user_group(cls, user_id, repo_id=None):
3281 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3302 q = Session().query(UserGroupToRepoBranchPermission, UserGroupRepoToPerm, Permission) \
3282 .join(
3303 .join(
3283 Permission,
3304 Permission,
3284 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3305 UserGroupToRepoBranchPermission.permission_id == Permission.permission_id) \
3285 .join(
3306 .join(
3286 UserGroupRepoToPerm,
3307 UserGroupRepoToPerm,
3287 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3308 UserGroupToRepoBranchPermission.rule_to_perm_id == UserGroupRepoToPerm.users_group_to_perm_id) \
3288 .join(
3309 .join(
3289 UserGroup,
3310 UserGroup,
3290 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3311 UserGroupRepoToPerm.users_group_id == UserGroup.users_group_id) \
3291 .join(
3312 .join(
3292 UserGroupMember,
3313 UserGroupMember,
3293 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3314 UserGroupRepoToPerm.users_group_id == UserGroupMember.users_group_id) \
3294 .filter(
3315 .filter(
3295 UserGroupMember.user_id == user_id,
3316 UserGroupMember.user_id == user_id,
3296 UserGroup.users_group_active == true())
3317 UserGroup.users_group_active == true())
3297
3318
3298 if repo_id:
3319 if repo_id:
3299 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3320 q = q.filter(UserGroupToRepoBranchPermission.repository_id == repo_id)
3300 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3321 return q.order_by(UserGroupToRepoBranchPermission.rule_order).all()
3301
3322
3302 @classmethod
3323 @classmethod
3303 def get_default_group_perms(cls, user_id, repo_group_id=None):
3324 def get_default_group_perms(cls, user_id, repo_group_id=None):
3304 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3325 q = Session().query(UserRepoGroupToPerm, RepoGroup, Permission)\
3305 .join(
3326 .join(
3306 Permission,
3327 Permission,
3307 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3328 UserRepoGroupToPerm.permission_id == Permission.permission_id)\
3308 .join(
3329 .join(
3309 RepoGroup,
3330 RepoGroup,
3310 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3331 UserRepoGroupToPerm.group_id == RepoGroup.group_id)\
3311 .filter(UserRepoGroupToPerm.user_id == user_id)
3332 .filter(UserRepoGroupToPerm.user_id == user_id)
3312 if repo_group_id:
3333 if repo_group_id:
3313 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3334 q = q.filter(UserRepoGroupToPerm.group_id == repo_group_id)
3314 return q.all()
3335 return q.all()
3315
3336
3316 @classmethod
3337 @classmethod
3317 def get_default_group_perms_from_user_group(
3338 def get_default_group_perms_from_user_group(
3318 cls, user_id, repo_group_id=None):
3339 cls, user_id, repo_group_id=None):
3319 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3340 q = Session().query(UserGroupRepoGroupToPerm, RepoGroup, Permission)\
3320 .join(
3341 .join(
3321 Permission,
3342 Permission,
3322 UserGroupRepoGroupToPerm.permission_id ==
3343 UserGroupRepoGroupToPerm.permission_id ==
3323 Permission.permission_id)\
3344 Permission.permission_id)\
3324 .join(
3345 .join(
3325 RepoGroup,
3346 RepoGroup,
3326 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3347 UserGroupRepoGroupToPerm.group_id == RepoGroup.group_id)\
3327 .join(
3348 .join(
3328 UserGroup,
3349 UserGroup,
3329 UserGroupRepoGroupToPerm.users_group_id ==
3350 UserGroupRepoGroupToPerm.users_group_id ==
3330 UserGroup.users_group_id)\
3351 UserGroup.users_group_id)\
3331 .join(
3352 .join(
3332 UserGroupMember,
3353 UserGroupMember,
3333 UserGroupRepoGroupToPerm.users_group_id ==
3354 UserGroupRepoGroupToPerm.users_group_id ==
3334 UserGroupMember.users_group_id)\
3355 UserGroupMember.users_group_id)\
3335 .filter(
3356 .filter(
3336 UserGroupMember.user_id == user_id,
3357 UserGroupMember.user_id == user_id,
3337 UserGroup.users_group_active == true())
3358 UserGroup.users_group_active == true())
3338 if repo_group_id:
3359 if repo_group_id:
3339 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3360 q = q.filter(UserGroupRepoGroupToPerm.group_id == repo_group_id)
3340 return q.all()
3361 return q.all()
3341
3362
3342 @classmethod
3363 @classmethod
3343 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3364 def get_default_user_group_perms(cls, user_id, user_group_id=None):
3344 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3365 q = Session().query(UserUserGroupToPerm, UserGroup, Permission)\
3345 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3366 .join((Permission, UserUserGroupToPerm.permission_id == Permission.permission_id))\
3346 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3367 .join((UserGroup, UserUserGroupToPerm.user_group_id == UserGroup.users_group_id))\
3347 .filter(UserUserGroupToPerm.user_id == user_id)
3368 .filter(UserUserGroupToPerm.user_id == user_id)
3348 if user_group_id:
3369 if user_group_id:
3349 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3370 q = q.filter(UserUserGroupToPerm.user_group_id == user_group_id)
3350 return q.all()
3371 return q.all()
3351
3372
3352 @classmethod
3373 @classmethod
3353 def get_default_user_group_perms_from_user_group(
3374 def get_default_user_group_perms_from_user_group(
3354 cls, user_id, user_group_id=None):
3375 cls, user_id, user_group_id=None):
3355 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3376 TargetUserGroup = aliased(UserGroup, name='target_user_group')
3356 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3377 q = Session().query(UserGroupUserGroupToPerm, UserGroup, Permission)\
3357 .join(
3378 .join(
3358 Permission,
3379 Permission,
3359 UserGroupUserGroupToPerm.permission_id ==
3380 UserGroupUserGroupToPerm.permission_id ==
3360 Permission.permission_id)\
3381 Permission.permission_id)\
3361 .join(
3382 .join(
3362 TargetUserGroup,
3383 TargetUserGroup,
3363 UserGroupUserGroupToPerm.target_user_group_id ==
3384 UserGroupUserGroupToPerm.target_user_group_id ==
3364 TargetUserGroup.users_group_id)\
3385 TargetUserGroup.users_group_id)\
3365 .join(
3386 .join(
3366 UserGroup,
3387 UserGroup,
3367 UserGroupUserGroupToPerm.user_group_id ==
3388 UserGroupUserGroupToPerm.user_group_id ==
3368 UserGroup.users_group_id)\
3389 UserGroup.users_group_id)\
3369 .join(
3390 .join(
3370 UserGroupMember,
3391 UserGroupMember,
3371 UserGroupUserGroupToPerm.user_group_id ==
3392 UserGroupUserGroupToPerm.user_group_id ==
3372 UserGroupMember.users_group_id)\
3393 UserGroupMember.users_group_id)\
3373 .filter(
3394 .filter(
3374 UserGroupMember.user_id == user_id,
3395 UserGroupMember.user_id == user_id,
3375 UserGroup.users_group_active == true())
3396 UserGroup.users_group_active == true())
3376 if user_group_id:
3397 if user_group_id:
3377 q = q.filter(
3398 q = q.filter(
3378 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3399 UserGroupUserGroupToPerm.user_group_id == user_group_id)
3379
3400
3380 return q.all()
3401 return q.all()
3381
3402
3382
3403
3383 class UserRepoToPerm(Base, BaseModel):
3404 class UserRepoToPerm(Base, BaseModel):
3384 __tablename__ = 'repo_to_perm'
3405 __tablename__ = 'repo_to_perm'
3385 __table_args__ = (
3406 __table_args__ = (
3386 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3407 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
3387 base_table_args
3408 base_table_args
3388 )
3409 )
3389
3410
3390 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3411 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3391 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3412 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3392 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3413 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3393 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3414 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3394
3415
3395 user = relationship('User')
3416 user = relationship('User')
3396 repository = relationship('Repository')
3417 repository = relationship('Repository')
3397 permission = relationship('Permission')
3418 permission = relationship('Permission')
3398
3419
3399 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3420 branch_perm_entry = relationship('UserToRepoBranchPermission', cascade="all, delete-orphan", lazy='joined')
3400
3421
3401 @classmethod
3422 @classmethod
3402 def create(cls, user, repository, permission):
3423 def create(cls, user, repository, permission):
3403 n = cls()
3424 n = cls()
3404 n.user = user
3425 n.user = user
3405 n.repository = repository
3426 n.repository = repository
3406 n.permission = permission
3427 n.permission = permission
3407 Session().add(n)
3428 Session().add(n)
3408 return n
3429 return n
3409
3430
3410 def __unicode__(self):
3431 def __unicode__(self):
3411 return u'<%s => %s >' % (self.user, self.repository)
3432 return u'<%s => %s >' % (self.user, self.repository)
3412
3433
3413
3434
3414 class UserUserGroupToPerm(Base, BaseModel):
3435 class UserUserGroupToPerm(Base, BaseModel):
3415 __tablename__ = 'user_user_group_to_perm'
3436 __tablename__ = 'user_user_group_to_perm'
3416 __table_args__ = (
3437 __table_args__ = (
3417 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3438 UniqueConstraint('user_id', 'user_group_id', 'permission_id'),
3418 base_table_args
3439 base_table_args
3419 )
3440 )
3420
3441
3421 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3442 user_user_group_to_perm_id = Column("user_user_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3422 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3443 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3423 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3444 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3424 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3445 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3425
3446
3426 user = relationship('User')
3447 user = relationship('User')
3427 user_group = relationship('UserGroup')
3448 user_group = relationship('UserGroup')
3428 permission = relationship('Permission')
3449 permission = relationship('Permission')
3429
3450
3430 @classmethod
3451 @classmethod
3431 def create(cls, user, user_group, permission):
3452 def create(cls, user, user_group, permission):
3432 n = cls()
3453 n = cls()
3433 n.user = user
3454 n.user = user
3434 n.user_group = user_group
3455 n.user_group = user_group
3435 n.permission = permission
3456 n.permission = permission
3436 Session().add(n)
3457 Session().add(n)
3437 return n
3458 return n
3438
3459
3439 def __unicode__(self):
3460 def __unicode__(self):
3440 return u'<%s => %s >' % (self.user, self.user_group)
3461 return u'<%s => %s >' % (self.user, self.user_group)
3441
3462
3442
3463
3443 class UserToPerm(Base, BaseModel):
3464 class UserToPerm(Base, BaseModel):
3444 __tablename__ = 'user_to_perm'
3465 __tablename__ = 'user_to_perm'
3445 __table_args__ = (
3466 __table_args__ = (
3446 UniqueConstraint('user_id', 'permission_id'),
3467 UniqueConstraint('user_id', 'permission_id'),
3447 base_table_args
3468 base_table_args
3448 )
3469 )
3449
3470
3450 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3471 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3451 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3472 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3452 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3473 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3453
3474
3454 user = relationship('User')
3475 user = relationship('User')
3455 permission = relationship('Permission', lazy='joined')
3476 permission = relationship('Permission', lazy='joined')
3456
3477
3457 def __unicode__(self):
3478 def __unicode__(self):
3458 return u'<%s => %s >' % (self.user, self.permission)
3479 return u'<%s => %s >' % (self.user, self.permission)
3459
3480
3460
3481
3461 class UserGroupRepoToPerm(Base, BaseModel):
3482 class UserGroupRepoToPerm(Base, BaseModel):
3462 __tablename__ = 'users_group_repo_to_perm'
3483 __tablename__ = 'users_group_repo_to_perm'
3463 __table_args__ = (
3484 __table_args__ = (
3464 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3485 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
3465 base_table_args
3486 base_table_args
3466 )
3487 )
3467
3488
3468 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3489 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3469 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3490 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3470 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3491 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3471 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3492 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
3472
3493
3473 users_group = relationship('UserGroup')
3494 users_group = relationship('UserGroup')
3474 permission = relationship('Permission')
3495 permission = relationship('Permission')
3475 repository = relationship('Repository')
3496 repository = relationship('Repository')
3476 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3497 user_group_branch_perms = relationship('UserGroupToRepoBranchPermission', cascade='all')
3477
3498
3478 @classmethod
3499 @classmethod
3479 def create(cls, users_group, repository, permission):
3500 def create(cls, users_group, repository, permission):
3480 n = cls()
3501 n = cls()
3481 n.users_group = users_group
3502 n.users_group = users_group
3482 n.repository = repository
3503 n.repository = repository
3483 n.permission = permission
3504 n.permission = permission
3484 Session().add(n)
3505 Session().add(n)
3485 return n
3506 return n
3486
3507
3487 def __unicode__(self):
3508 def __unicode__(self):
3488 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3509 return u'<UserGroupRepoToPerm:%s => %s >' % (self.users_group, self.repository)
3489
3510
3490
3511
3491 class UserGroupUserGroupToPerm(Base, BaseModel):
3512 class UserGroupUserGroupToPerm(Base, BaseModel):
3492 __tablename__ = 'user_group_user_group_to_perm'
3513 __tablename__ = 'user_group_user_group_to_perm'
3493 __table_args__ = (
3514 __table_args__ = (
3494 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3515 UniqueConstraint('target_user_group_id', 'user_group_id', 'permission_id'),
3495 CheckConstraint('target_user_group_id != user_group_id'),
3516 CheckConstraint('target_user_group_id != user_group_id'),
3496 base_table_args
3517 base_table_args
3497 )
3518 )
3498
3519
3499 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)
3520 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)
3500 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3521 target_user_group_id = Column("target_user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3501 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3522 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3502 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3523 user_group_id = Column("user_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3503
3524
3504 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3525 target_user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.target_user_group_id==UserGroup.users_group_id')
3505 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3526 user_group = relationship('UserGroup', primaryjoin='UserGroupUserGroupToPerm.user_group_id==UserGroup.users_group_id')
3506 permission = relationship('Permission')
3527 permission = relationship('Permission')
3507
3528
3508 @classmethod
3529 @classmethod
3509 def create(cls, target_user_group, user_group, permission):
3530 def create(cls, target_user_group, user_group, permission):
3510 n = cls()
3531 n = cls()
3511 n.target_user_group = target_user_group
3532 n.target_user_group = target_user_group
3512 n.user_group = user_group
3533 n.user_group = user_group
3513 n.permission = permission
3534 n.permission = permission
3514 Session().add(n)
3535 Session().add(n)
3515 return n
3536 return n
3516
3537
3517 def __unicode__(self):
3538 def __unicode__(self):
3518 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3539 return u'<UserGroupUserGroup:%s => %s >' % (self.target_user_group, self.user_group)
3519
3540
3520
3541
3521 class UserGroupToPerm(Base, BaseModel):
3542 class UserGroupToPerm(Base, BaseModel):
3522 __tablename__ = 'users_group_to_perm'
3543 __tablename__ = 'users_group_to_perm'
3523 __table_args__ = (
3544 __table_args__ = (
3524 UniqueConstraint('users_group_id', 'permission_id',),
3545 UniqueConstraint('users_group_id', 'permission_id',),
3525 base_table_args
3546 base_table_args
3526 )
3547 )
3527
3548
3528 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3549 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3529 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3550 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3530 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3551 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3531
3552
3532 users_group = relationship('UserGroup')
3553 users_group = relationship('UserGroup')
3533 permission = relationship('Permission')
3554 permission = relationship('Permission')
3534
3555
3535
3556
3536 class UserRepoGroupToPerm(Base, BaseModel):
3557 class UserRepoGroupToPerm(Base, BaseModel):
3537 __tablename__ = 'user_repo_group_to_perm'
3558 __tablename__ = 'user_repo_group_to_perm'
3538 __table_args__ = (
3559 __table_args__ = (
3539 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3560 UniqueConstraint('user_id', 'group_id', 'permission_id'),
3540 base_table_args
3561 base_table_args
3541 )
3562 )
3542
3563
3543 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3564 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3544 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3565 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3545 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3566 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3546 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3567 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3547
3568
3548 user = relationship('User')
3569 user = relationship('User')
3549 group = relationship('RepoGroup')
3570 group = relationship('RepoGroup')
3550 permission = relationship('Permission')
3571 permission = relationship('Permission')
3551
3572
3552 @classmethod
3573 @classmethod
3553 def create(cls, user, repository_group, permission):
3574 def create(cls, user, repository_group, permission):
3554 n = cls()
3575 n = cls()
3555 n.user = user
3576 n.user = user
3556 n.group = repository_group
3577 n.group = repository_group
3557 n.permission = permission
3578 n.permission = permission
3558 Session().add(n)
3579 Session().add(n)
3559 return n
3580 return n
3560
3581
3561
3582
3562 class UserGroupRepoGroupToPerm(Base, BaseModel):
3583 class UserGroupRepoGroupToPerm(Base, BaseModel):
3563 __tablename__ = 'users_group_repo_group_to_perm'
3584 __tablename__ = 'users_group_repo_group_to_perm'
3564 __table_args__ = (
3585 __table_args__ = (
3565 UniqueConstraint('users_group_id', 'group_id'),
3586 UniqueConstraint('users_group_id', 'group_id'),
3566 base_table_args
3587 base_table_args
3567 )
3588 )
3568
3589
3569 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)
3590 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)
3570 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3591 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
3571 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3592 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
3572 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3593 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
3573
3594
3574 users_group = relationship('UserGroup')
3595 users_group = relationship('UserGroup')
3575 permission = relationship('Permission')
3596 permission = relationship('Permission')
3576 group = relationship('RepoGroup')
3597 group = relationship('RepoGroup')
3577
3598
3578 @classmethod
3599 @classmethod
3579 def create(cls, user_group, repository_group, permission):
3600 def create(cls, user_group, repository_group, permission):
3580 n = cls()
3601 n = cls()
3581 n.users_group = user_group
3602 n.users_group = user_group
3582 n.group = repository_group
3603 n.group = repository_group
3583 n.permission = permission
3604 n.permission = permission
3584 Session().add(n)
3605 Session().add(n)
3585 return n
3606 return n
3586
3607
3587 def __unicode__(self):
3608 def __unicode__(self):
3588 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3609 return u'<UserGroupRepoGroupToPerm:%s => %s >' % (self.users_group, self.group)
3589
3610
3590
3611
3591 class Statistics(Base, BaseModel):
3612 class Statistics(Base, BaseModel):
3592 __tablename__ = 'statistics'
3613 __tablename__ = 'statistics'
3593 __table_args__ = (
3614 __table_args__ = (
3594 base_table_args
3615 base_table_args
3595 )
3616 )
3596
3617
3597 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3618 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3598 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3619 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
3599 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3620 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
3600 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3621 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
3601 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3622 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
3602 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3623 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
3603
3624
3604 repository = relationship('Repository', single_parent=True)
3625 repository = relationship('Repository', single_parent=True)
3605
3626
3606
3627
3607 class UserFollowing(Base, BaseModel):
3628 class UserFollowing(Base, BaseModel):
3608 __tablename__ = 'user_followings'
3629 __tablename__ = 'user_followings'
3609 __table_args__ = (
3630 __table_args__ = (
3610 UniqueConstraint('user_id', 'follows_repository_id'),
3631 UniqueConstraint('user_id', 'follows_repository_id'),
3611 UniqueConstraint('user_id', 'follows_user_id'),
3632 UniqueConstraint('user_id', 'follows_user_id'),
3612 base_table_args
3633 base_table_args
3613 )
3634 )
3614
3635
3615 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3636 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3616 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3637 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
3617 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3638 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
3618 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3639 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
3619 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3640 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
3620
3641
3621 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3642 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
3622
3643
3623 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3644 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
3624 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3645 follows_repository = relationship('Repository', order_by='Repository.repo_name')
3625
3646
3626 @classmethod
3647 @classmethod
3627 def get_repo_followers(cls, repo_id):
3648 def get_repo_followers(cls, repo_id):
3628 return cls.query().filter(cls.follows_repo_id == repo_id)
3649 return cls.query().filter(cls.follows_repo_id == repo_id)
3629
3650
3630
3651
3631 class CacheKey(Base, BaseModel):
3652 class CacheKey(Base, BaseModel):
3632 __tablename__ = 'cache_invalidation'
3653 __tablename__ = 'cache_invalidation'
3633 __table_args__ = (
3654 __table_args__ = (
3634 UniqueConstraint('cache_key'),
3655 UniqueConstraint('cache_key'),
3635 Index('key_idx', 'cache_key'),
3656 Index('key_idx', 'cache_key'),
3636 base_table_args,
3657 base_table_args,
3637 )
3658 )
3638
3659
3639 CACHE_TYPE_FEED = 'FEED'
3660 CACHE_TYPE_FEED = 'FEED'
3640
3661
3641 # namespaces used to register process/thread aware caches
3662 # namespaces used to register process/thread aware caches
3642 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3663 REPO_INVALIDATION_NAMESPACE = 'repo_cache:{repo_id}'
3643 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3664 SETTINGS_INVALIDATION_NAMESPACE = 'system_settings'
3644
3665
3645 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3666 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
3646 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3667 cache_key = Column("cache_key", String(255), nullable=True, unique=None, default=None)
3647 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3668 cache_args = Column("cache_args", String(255), nullable=True, unique=None, default=None)
3648 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3669 cache_state_uid = Column("cache_state_uid", String(255), nullable=True, unique=None, default=None)
3649 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3670 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
3650
3671
3651 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3672 def __init__(self, cache_key, cache_args='', cache_state_uid=None):
3652 self.cache_key = cache_key
3673 self.cache_key = cache_key
3653 self.cache_args = cache_args
3674 self.cache_args = cache_args
3654 self.cache_active = False
3675 self.cache_active = False
3655 # first key should be same for all entries, since all workers should share it
3676 # first key should be same for all entries, since all workers should share it
3656 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3677 self.cache_state_uid = cache_state_uid or self.generate_new_state_uid()
3657
3678
3658 def __unicode__(self):
3679 def __unicode__(self):
3659 return u"<%s('%s:%s[%s]')>" % (
3680 return u"<%s('%s:%s[%s]')>" % (
3660 self.__class__.__name__,
3681 self.__class__.__name__,
3661 self.cache_id, self.cache_key, self.cache_active)
3682 self.cache_id, self.cache_key, self.cache_active)
3662
3683
3663 def _cache_key_partition(self):
3684 def _cache_key_partition(self):
3664 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3685 prefix, repo_name, suffix = self.cache_key.partition(self.cache_args)
3665 return prefix, repo_name, suffix
3686 return prefix, repo_name, suffix
3666
3687
3667 def get_prefix(self):
3688 def get_prefix(self):
3668 """
3689 """
3669 Try to extract prefix from existing cache key. The key could consist
3690 Try to extract prefix from existing cache key. The key could consist
3670 of prefix, repo_name, suffix
3691 of prefix, repo_name, suffix
3671 """
3692 """
3672 # this returns prefix, repo_name, suffix
3693 # this returns prefix, repo_name, suffix
3673 return self._cache_key_partition()[0]
3694 return self._cache_key_partition()[0]
3674
3695
3675 def get_suffix(self):
3696 def get_suffix(self):
3676 """
3697 """
3677 get suffix that might have been used in _get_cache_key to
3698 get suffix that might have been used in _get_cache_key to
3678 generate self.cache_key. Only used for informational purposes
3699 generate self.cache_key. Only used for informational purposes
3679 in repo_edit.mako.
3700 in repo_edit.mako.
3680 """
3701 """
3681 # prefix, repo_name, suffix
3702 # prefix, repo_name, suffix
3682 return self._cache_key_partition()[2]
3703 return self._cache_key_partition()[2]
3683
3704
3684 @classmethod
3705 @classmethod
3685 def generate_new_state_uid(cls, based_on=None):
3706 def generate_new_state_uid(cls, based_on=None):
3686 if based_on:
3707 if based_on:
3687 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3708 return str(uuid.uuid5(uuid.NAMESPACE_URL, safe_str(based_on)))
3688 else:
3709 else:
3689 return str(uuid.uuid4())
3710 return str(uuid.uuid4())
3690
3711
3691 @classmethod
3712 @classmethod
3692 def delete_all_cache(cls):
3713 def delete_all_cache(cls):
3693 """
3714 """
3694 Delete all cache keys from database.
3715 Delete all cache keys from database.
3695 Should only be run when all instances are down and all entries
3716 Should only be run when all instances are down and all entries
3696 thus stale.
3717 thus stale.
3697 """
3718 """
3698 cls.query().delete()
3719 cls.query().delete()
3699 Session().commit()
3720 Session().commit()
3700
3721
3701 @classmethod
3722 @classmethod
3702 def set_invalidate(cls, cache_uid, delete=False):
3723 def set_invalidate(cls, cache_uid, delete=False):
3703 """
3724 """
3704 Mark all caches of a repo as invalid in the database.
3725 Mark all caches of a repo as invalid in the database.
3705 """
3726 """
3706
3727
3707 try:
3728 try:
3708 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3729 qry = Session().query(cls).filter(cls.cache_args == cache_uid)
3709 if delete:
3730 if delete:
3710 qry.delete()
3731 qry.delete()
3711 log.debug('cache objects deleted for cache args %s',
3732 log.debug('cache objects deleted for cache args %s',
3712 safe_str(cache_uid))
3733 safe_str(cache_uid))
3713 else:
3734 else:
3714 qry.update({"cache_active": False,
3735 qry.update({"cache_active": False,
3715 "cache_state_uid": cls.generate_new_state_uid()})
3736 "cache_state_uid": cls.generate_new_state_uid()})
3716 log.debug('cache objects marked as invalid for cache args %s',
3737 log.debug('cache objects marked as invalid for cache args %s',
3717 safe_str(cache_uid))
3738 safe_str(cache_uid))
3718
3739
3719 Session().commit()
3740 Session().commit()
3720 except Exception:
3741 except Exception:
3721 log.exception(
3742 log.exception(
3722 'Cache key invalidation failed for cache args %s',
3743 'Cache key invalidation failed for cache args %s',
3723 safe_str(cache_uid))
3744 safe_str(cache_uid))
3724 Session().rollback()
3745 Session().rollback()
3725
3746
3726 @classmethod
3747 @classmethod
3727 def get_active_cache(cls, cache_key):
3748 def get_active_cache(cls, cache_key):
3728 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3749 inv_obj = cls.query().filter(cls.cache_key == cache_key).scalar()
3729 if inv_obj:
3750 if inv_obj:
3730 return inv_obj
3751 return inv_obj
3731 return None
3752 return None
3732
3753
3733 @classmethod
3754 @classmethod
3734 def get_namespace_map(cls, namespace):
3755 def get_namespace_map(cls, namespace):
3735 return {
3756 return {
3736 x.cache_key: x
3757 x.cache_key: x
3737 for x in cls.query().filter(cls.cache_args == namespace)}
3758 for x in cls.query().filter(cls.cache_args == namespace)}
3738
3759
3739
3760
3740 class ChangesetComment(Base, BaseModel):
3761 class ChangesetComment(Base, BaseModel):
3741 __tablename__ = 'changeset_comments'
3762 __tablename__ = 'changeset_comments'
3742 __table_args__ = (
3763 __table_args__ = (
3743 Index('cc_revision_idx', 'revision'),
3764 Index('cc_revision_idx', 'revision'),
3744 base_table_args,
3765 base_table_args,
3745 )
3766 )
3746
3767
3747 COMMENT_OUTDATED = u'comment_outdated'
3768 COMMENT_OUTDATED = u'comment_outdated'
3748 COMMENT_TYPE_NOTE = u'note'
3769 COMMENT_TYPE_NOTE = u'note'
3749 COMMENT_TYPE_TODO = u'todo'
3770 COMMENT_TYPE_TODO = u'todo'
3750 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3771 COMMENT_TYPES = [COMMENT_TYPE_NOTE, COMMENT_TYPE_TODO]
3751
3772
3752 OP_IMMUTABLE = u'immutable'
3773 OP_IMMUTABLE = u'immutable'
3753 OP_CHANGEABLE = u'changeable'
3774 OP_CHANGEABLE = u'changeable'
3754
3775
3755 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3776 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
3756 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3777 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3757 revision = Column('revision', String(40), nullable=True)
3778 revision = Column('revision', String(40), nullable=True)
3758 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3779 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3759 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3780 pull_request_version_id = Column("pull_request_version_id", Integer(), ForeignKey('pull_request_versions.pull_request_version_id'), nullable=True)
3760 line_no = Column('line_no', Unicode(10), nullable=True)
3781 line_no = Column('line_no', Unicode(10), nullable=True)
3761 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3782 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
3762 f_path = Column('f_path', Unicode(1000), nullable=True)
3783 f_path = Column('f_path', Unicode(1000), nullable=True)
3763 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3784 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3764 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3785 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3765 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3786 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3766 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3787 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3767 renderer = Column('renderer', Unicode(64), nullable=True)
3788 renderer = Column('renderer', Unicode(64), nullable=True)
3768 display_state = Column('display_state', Unicode(128), nullable=True)
3789 display_state = Column('display_state', Unicode(128), nullable=True)
3769 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3790 immutable_state = Column('immutable_state', Unicode(128), nullable=True, default=OP_CHANGEABLE)
3770 draft = Column('draft', Boolean(), nullable=True, default=False)
3791 draft = Column('draft', Boolean(), nullable=True, default=False)
3771
3792
3772 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3793 comment_type = Column('comment_type', Unicode(128), nullable=True, default=COMMENT_TYPE_NOTE)
3773 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3794 resolved_comment_id = Column('resolved_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=True)
3774
3795
3775 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3796 resolved_comment = relationship('ChangesetComment', remote_side=comment_id, back_populates='resolved_by')
3776 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3797 resolved_by = relationship('ChangesetComment', back_populates='resolved_comment')
3777
3798
3778 author = relationship('User', lazy='select')
3799 author = relationship('User', lazy='select')
3779 repo = relationship('Repository')
3800 repo = relationship('Repository')
3780 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select')
3801 status_change = relationship('ChangesetStatus', cascade="all, delete-orphan", lazy='select')
3781 pull_request = relationship('PullRequest', lazy='select')
3802 pull_request = relationship('PullRequest', lazy='select')
3782 pull_request_version = relationship('PullRequestVersion', lazy='select')
3803 pull_request_version = relationship('PullRequestVersion', lazy='select')
3783 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version')
3804 history = relationship('ChangesetCommentHistory', cascade='all, delete-orphan', lazy='select', order_by='ChangesetCommentHistory.version')
3784
3805
3785 @classmethod
3806 @classmethod
3786 def get_users(cls, revision=None, pull_request_id=None):
3807 def get_users(cls, revision=None, pull_request_id=None):
3787 """
3808 """
3788 Returns user associated with this ChangesetComment. ie those
3809 Returns user associated with this ChangesetComment. ie those
3789 who actually commented
3810 who actually commented
3790
3811
3791 :param cls:
3812 :param cls:
3792 :param revision:
3813 :param revision:
3793 """
3814 """
3794 q = Session().query(User)\
3815 q = Session().query(User)\
3795 .join(ChangesetComment.author)
3816 .join(ChangesetComment.author)
3796 if revision:
3817 if revision:
3797 q = q.filter(cls.revision == revision)
3818 q = q.filter(cls.revision == revision)
3798 elif pull_request_id:
3819 elif pull_request_id:
3799 q = q.filter(cls.pull_request_id == pull_request_id)
3820 q = q.filter(cls.pull_request_id == pull_request_id)
3800 return q.all()
3821 return q.all()
3801
3822
3802 @classmethod
3823 @classmethod
3803 def get_index_from_version(cls, pr_version, versions):
3824 def get_index_from_version(cls, pr_version, versions):
3804 num_versions = [x.pull_request_version_id for x in versions]
3825 num_versions = [x.pull_request_version_id for x in versions]
3805 try:
3826 try:
3806 return num_versions.index(pr_version) + 1
3827 return num_versions.index(pr_version) + 1
3807 except (IndexError, ValueError):
3828 except (IndexError, ValueError):
3808 return
3829 return
3809
3830
3810 @property
3831 @property
3811 def outdated(self):
3832 def outdated(self):
3812 return self.display_state == self.COMMENT_OUTDATED
3833 return self.display_state == self.COMMENT_OUTDATED
3813
3834
3814 @property
3835 @property
3815 def outdated_js(self):
3836 def outdated_js(self):
3816 return json.dumps(self.display_state == self.COMMENT_OUTDATED)
3837 return json.dumps(self.display_state == self.COMMENT_OUTDATED)
3817
3838
3818 @property
3839 @property
3819 def immutable(self):
3840 def immutable(self):
3820 return self.immutable_state == self.OP_IMMUTABLE
3841 return self.immutable_state == self.OP_IMMUTABLE
3821
3842
3822 def outdated_at_version(self, version):
3843 def outdated_at_version(self, version):
3823 """
3844 """
3824 Checks if comment is outdated for given pull request version
3845 Checks if comment is outdated for given pull request version
3825 """
3846 """
3826 def version_check():
3847 def version_check():
3827 return self.pull_request_version_id and self.pull_request_version_id != version
3848 return self.pull_request_version_id and self.pull_request_version_id != version
3828
3849
3829 if self.is_inline:
3850 if self.is_inline:
3830 return self.outdated and version_check()
3851 return self.outdated and version_check()
3831 else:
3852 else:
3832 # general comments don't have .outdated set, also latest don't have a version
3853 # general comments don't have .outdated set, also latest don't have a version
3833 return version_check()
3854 return version_check()
3834
3855
3835 def outdated_at_version_js(self, version):
3856 def outdated_at_version_js(self, version):
3836 """
3857 """
3837 Checks if comment is outdated for given pull request version
3858 Checks if comment is outdated for given pull request version
3838 """
3859 """
3839 return json.dumps(self.outdated_at_version(version))
3860 return json.dumps(self.outdated_at_version(version))
3840
3861
3841 def older_than_version(self, version):
3862 def older_than_version(self, version):
3842 """
3863 """
3843 Checks if comment is made from previous version than given
3864 Checks if comment is made from previous version than given
3844 """
3865 """
3845 if version is None:
3866 if version is None:
3846 return self.pull_request_version != version
3867 return self.pull_request_version != version
3847
3868
3848 return self.pull_request_version < version
3869 return self.pull_request_version < version
3849
3870
3850 def older_than_version_js(self, version):
3871 def older_than_version_js(self, version):
3851 """
3872 """
3852 Checks if comment is made from previous version than given
3873 Checks if comment is made from previous version than given
3853 """
3874 """
3854 return json.dumps(self.older_than_version(version))
3875 return json.dumps(self.older_than_version(version))
3855
3876
3856 @property
3877 @property
3857 def commit_id(self):
3878 def commit_id(self):
3858 """New style naming to stop using .revision"""
3879 """New style naming to stop using .revision"""
3859 return self.revision
3880 return self.revision
3860
3881
3861 @property
3882 @property
3862 def resolved(self):
3883 def resolved(self):
3863 return self.resolved_by[0] if self.resolved_by else None
3884 return self.resolved_by[0] if self.resolved_by else None
3864
3885
3865 @property
3886 @property
3866 def is_todo(self):
3887 def is_todo(self):
3867 return self.comment_type == self.COMMENT_TYPE_TODO
3888 return self.comment_type == self.COMMENT_TYPE_TODO
3868
3889
3869 @property
3890 @property
3870 def is_inline(self):
3891 def is_inline(self):
3871 if self.line_no and self.f_path:
3892 if self.line_no and self.f_path:
3872 return True
3893 return True
3873 return False
3894 return False
3874
3895
3875 @property
3896 @property
3876 def last_version(self):
3897 def last_version(self):
3877 version = 0
3898 version = 0
3878 if self.history:
3899 if self.history:
3879 version = self.history[-1].version
3900 version = self.history[-1].version
3880 return version
3901 return version
3881
3902
3882 def get_index_version(self, versions):
3903 def get_index_version(self, versions):
3883 return self.get_index_from_version(
3904 return self.get_index_from_version(
3884 self.pull_request_version_id, versions)
3905 self.pull_request_version_id, versions)
3885
3906
3886 @property
3907 @property
3887 def review_status(self):
3908 def review_status(self):
3888 if self.status_change:
3909 if self.status_change:
3889 return self.status_change[0].status
3910 return self.status_change[0].status
3890
3911
3891 @property
3912 @property
3892 def review_status_lbl(self):
3913 def review_status_lbl(self):
3893 if self.status_change:
3914 if self.status_change:
3894 return self.status_change[0].status_lbl
3915 return self.status_change[0].status_lbl
3895
3916
3896 def __repr__(self):
3917 def __repr__(self):
3897 if self.comment_id:
3918 if self.comment_id:
3898 return '<DB:Comment #%s>' % self.comment_id
3919 return '<DB:Comment #%s>' % self.comment_id
3899 else:
3920 else:
3900 return '<DB:Comment at %#x>' % id(self)
3921 return '<DB:Comment at %#x>' % id(self)
3901
3922
3902 def get_api_data(self):
3923 def get_api_data(self):
3903 comment = self
3924 comment = self
3904
3925
3905 data = {
3926 data = {
3906 'comment_id': comment.comment_id,
3927 'comment_id': comment.comment_id,
3907 'comment_type': comment.comment_type,
3928 'comment_type': comment.comment_type,
3908 'comment_text': comment.text,
3929 'comment_text': comment.text,
3909 'comment_status': comment.status_change,
3930 'comment_status': comment.status_change,
3910 'comment_f_path': comment.f_path,
3931 'comment_f_path': comment.f_path,
3911 'comment_lineno': comment.line_no,
3932 'comment_lineno': comment.line_no,
3912 'comment_author': comment.author,
3933 'comment_author': comment.author,
3913 'comment_created_on': comment.created_on,
3934 'comment_created_on': comment.created_on,
3914 'comment_resolved_by': self.resolved,
3935 'comment_resolved_by': self.resolved,
3915 'comment_commit_id': comment.revision,
3936 'comment_commit_id': comment.revision,
3916 'comment_pull_request_id': comment.pull_request_id,
3937 'comment_pull_request_id': comment.pull_request_id,
3917 'comment_last_version': self.last_version
3938 'comment_last_version': self.last_version
3918 }
3939 }
3919 return data
3940 return data
3920
3941
3921 def __json__(self):
3942 def __json__(self):
3922 data = dict()
3943 data = dict()
3923 data.update(self.get_api_data())
3944 data.update(self.get_api_data())
3924 return data
3945 return data
3925
3946
3926
3947
3927 class ChangesetCommentHistory(Base, BaseModel):
3948 class ChangesetCommentHistory(Base, BaseModel):
3928 __tablename__ = 'changeset_comments_history'
3949 __tablename__ = 'changeset_comments_history'
3929 __table_args__ = (
3950 __table_args__ = (
3930 Index('cch_comment_id_idx', 'comment_id'),
3951 Index('cch_comment_id_idx', 'comment_id'),
3931 base_table_args,
3952 base_table_args,
3932 )
3953 )
3933
3954
3934 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3955 comment_history_id = Column('comment_history_id', Integer(), nullable=False, primary_key=True)
3935 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3956 comment_id = Column('comment_id', Integer(), ForeignKey('changeset_comments.comment_id'), nullable=False)
3936 version = Column("version", Integer(), nullable=False, default=0)
3957 version = Column("version", Integer(), nullable=False, default=0)
3937 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3958 created_by_user_id = Column('created_by_user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
3938 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3959 text = Column('text', UnicodeText().with_variant(UnicodeText(25000), 'mysql'), nullable=False)
3939 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3960 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
3940 deleted = Column('deleted', Boolean(), default=False)
3961 deleted = Column('deleted', Boolean(), default=False)
3941
3962
3942 author = relationship('User', lazy='joined')
3963 author = relationship('User', lazy='joined')
3943 comment = relationship('ChangesetComment', cascade="all, delete")
3964 comment = relationship('ChangesetComment', cascade="all, delete")
3944
3965
3945 @classmethod
3966 @classmethod
3946 def get_version(cls, comment_id):
3967 def get_version(cls, comment_id):
3947 q = Session().query(ChangesetCommentHistory).filter(
3968 q = Session().query(ChangesetCommentHistory).filter(
3948 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3969 ChangesetCommentHistory.comment_id == comment_id).order_by(ChangesetCommentHistory.version.desc())
3949 if q.count() == 0:
3970 if q.count() == 0:
3950 return 1
3971 return 1
3951 elif q.count() >= q[0].version:
3972 elif q.count() >= q[0].version:
3952 return q.count() + 1
3973 return q.count() + 1
3953 else:
3974 else:
3954 return q[0].version + 1
3975 return q[0].version + 1
3955
3976
3956
3977
3957 class ChangesetStatus(Base, BaseModel):
3978 class ChangesetStatus(Base, BaseModel):
3958 __tablename__ = 'changeset_statuses'
3979 __tablename__ = 'changeset_statuses'
3959 __table_args__ = (
3980 __table_args__ = (
3960 Index('cs_revision_idx', 'revision'),
3981 Index('cs_revision_idx', 'revision'),
3961 Index('cs_version_idx', 'version'),
3982 Index('cs_version_idx', 'version'),
3962 UniqueConstraint('repo_id', 'revision', 'version'),
3983 UniqueConstraint('repo_id', 'revision', 'version'),
3963 base_table_args
3984 base_table_args
3964 )
3985 )
3965
3986
3966 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3987 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
3967 STATUS_APPROVED = 'approved'
3988 STATUS_APPROVED = 'approved'
3968 STATUS_REJECTED = 'rejected'
3989 STATUS_REJECTED = 'rejected'
3969 STATUS_UNDER_REVIEW = 'under_review'
3990 STATUS_UNDER_REVIEW = 'under_review'
3970 CheckConstraint,
3991 CheckConstraint,
3971 STATUSES = [
3992 STATUSES = [
3972 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3993 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
3973 (STATUS_APPROVED, _("Approved")),
3994 (STATUS_APPROVED, _("Approved")),
3974 (STATUS_REJECTED, _("Rejected")),
3995 (STATUS_REJECTED, _("Rejected")),
3975 (STATUS_UNDER_REVIEW, _("Under Review")),
3996 (STATUS_UNDER_REVIEW, _("Under Review")),
3976 ]
3997 ]
3977
3998
3978 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3999 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
3979 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
4000 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
3980 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
4001 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
3981 revision = Column('revision', String(40), nullable=False)
4002 revision = Column('revision', String(40), nullable=False)
3982 status = Column('status', String(128), nullable=False, default=DEFAULT)
4003 status = Column('status', String(128), nullable=False, default=DEFAULT)
3983 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
4004 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
3984 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
4005 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
3985 version = Column('version', Integer(), nullable=False, default=0)
4006 version = Column('version', Integer(), nullable=False, default=0)
3986 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
4007 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
3987
4008
3988 author = relationship('User', lazy='select')
4009 author = relationship('User', lazy='select')
3989 repo = relationship('Repository', lazy='select')
4010 repo = relationship('Repository', lazy='select')
3990 comment = relationship('ChangesetComment', lazy='select')
4011 comment = relationship('ChangesetComment', lazy='select')
3991 pull_request = relationship('PullRequest', lazy='select')
4012 pull_request = relationship('PullRequest', lazy='select')
3992
4013
3993 def __unicode__(self):
4014 def __unicode__(self):
3994 return u"<%s('%s[v%s]:%s')>" % (
4015 return u"<%s('%s[v%s]:%s')>" % (
3995 self.__class__.__name__,
4016 self.__class__.__name__,
3996 self.status, self.version, self.author
4017 self.status, self.version, self.author
3997 )
4018 )
3998
4019
3999 @classmethod
4020 @classmethod
4000 def get_status_lbl(cls, value):
4021 def get_status_lbl(cls, value):
4001 return dict(cls.STATUSES).get(value)
4022 return dict(cls.STATUSES).get(value)
4002
4023
4003 @property
4024 @property
4004 def status_lbl(self):
4025 def status_lbl(self):
4005 return ChangesetStatus.get_status_lbl(self.status)
4026 return ChangesetStatus.get_status_lbl(self.status)
4006
4027
4007 def get_api_data(self):
4028 def get_api_data(self):
4008 status = self
4029 status = self
4009 data = {
4030 data = {
4010 'status_id': status.changeset_status_id,
4031 'status_id': status.changeset_status_id,
4011 'status': status.status,
4032 'status': status.status,
4012 }
4033 }
4013 return data
4034 return data
4014
4035
4015 def __json__(self):
4036 def __json__(self):
4016 data = dict()
4037 data = dict()
4017 data.update(self.get_api_data())
4038 data.update(self.get_api_data())
4018 return data
4039 return data
4019
4040
4020
4041
4021 class _SetState(object):
4042 class _SetState(object):
4022 """
4043 """
4023 Context processor allowing changing state for sensitive operation such as
4044 Context processor allowing changing state for sensitive operation such as
4024 pull request update or merge
4045 pull request update or merge
4025 """
4046 """
4026
4047
4027 def __init__(self, pull_request, pr_state, back_state=None):
4048 def __init__(self, pull_request, pr_state, back_state=None):
4028 self._pr = pull_request
4049 self._pr = pull_request
4029 self._org_state = back_state or pull_request.pull_request_state
4050 self._org_state = back_state or pull_request.pull_request_state
4030 self._pr_state = pr_state
4051 self._pr_state = pr_state
4031 self._current_state = None
4052 self._current_state = None
4032
4053
4033 def __enter__(self):
4054 def __enter__(self):
4034 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4055 log.debug('StateLock: entering set state context of pr %s, setting state to: `%s`',
4035 self._pr, self._pr_state)
4056 self._pr, self._pr_state)
4036 self.set_pr_state(self._pr_state)
4057 self.set_pr_state(self._pr_state)
4037 return self
4058 return self
4038
4059
4039 def __exit__(self, exc_type, exc_val, exc_tb):
4060 def __exit__(self, exc_type, exc_val, exc_tb):
4040 if exc_val is not None:
4061 if exc_val is not None:
4041 log.error(traceback.format_exc(exc_tb))
4062 log.error(traceback.format_exc(exc_tb))
4042 return None
4063 return None
4043
4064
4044 self.set_pr_state(self._org_state)
4065 self.set_pr_state(self._org_state)
4045 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4066 log.debug('StateLock: exiting set state context of pr %s, setting state to: `%s`',
4046 self._pr, self._org_state)
4067 self._pr, self._org_state)
4047
4068
4048 @property
4069 @property
4049 def state(self):
4070 def state(self):
4050 return self._current_state
4071 return self._current_state
4051
4072
4052 def set_pr_state(self, pr_state):
4073 def set_pr_state(self, pr_state):
4053 try:
4074 try:
4054 self._pr.pull_request_state = pr_state
4075 self._pr.pull_request_state = pr_state
4055 Session().add(self._pr)
4076 Session().add(self._pr)
4056 Session().commit()
4077 Session().commit()
4057 self._current_state = pr_state
4078 self._current_state = pr_state
4058 except Exception:
4079 except Exception:
4059 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4080 log.exception('Failed to set PullRequest %s state to %s', self._pr, pr_state)
4060 raise
4081 raise
4061
4082
4062
4083
4063 class _PullRequestBase(BaseModel):
4084 class _PullRequestBase(BaseModel):
4064 """
4085 """
4065 Common attributes of pull request and version entries.
4086 Common attributes of pull request and version entries.
4066 """
4087 """
4067
4088
4068 # .status values
4089 # .status values
4069 STATUS_NEW = u'new'
4090 STATUS_NEW = u'new'
4070 STATUS_OPEN = u'open'
4091 STATUS_OPEN = u'open'
4071 STATUS_CLOSED = u'closed'
4092 STATUS_CLOSED = u'closed'
4072
4093
4073 # available states
4094 # available states
4074 STATE_CREATING = u'creating'
4095 STATE_CREATING = u'creating'
4075 STATE_UPDATING = u'updating'
4096 STATE_UPDATING = u'updating'
4076 STATE_MERGING = u'merging'
4097 STATE_MERGING = u'merging'
4077 STATE_CREATED = u'created'
4098 STATE_CREATED = u'created'
4078
4099
4079 title = Column('title', Unicode(255), nullable=True)
4100 title = Column('title', Unicode(255), nullable=True)
4080 description = Column(
4101 description = Column(
4081 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4102 'description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'),
4082 nullable=True)
4103 nullable=True)
4083 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4104 description_renderer = Column('description_renderer', Unicode(64), nullable=True)
4084
4105
4085 # new/open/closed status of pull request (not approve/reject/etc)
4106 # new/open/closed status of pull request (not approve/reject/etc)
4086 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4107 status = Column('status', Unicode(255), nullable=False, default=STATUS_NEW)
4087 created_on = Column(
4108 created_on = Column(
4088 'created_on', DateTime(timezone=False), nullable=False,
4109 'created_on', DateTime(timezone=False), nullable=False,
4089 default=datetime.datetime.now)
4110 default=datetime.datetime.now)
4090 updated_on = Column(
4111 updated_on = Column(
4091 'updated_on', DateTime(timezone=False), nullable=False,
4112 'updated_on', DateTime(timezone=False), nullable=False,
4092 default=datetime.datetime.now)
4113 default=datetime.datetime.now)
4093
4114
4094 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4115 pull_request_state = Column("pull_request_state", String(255), nullable=True)
4095
4116
4096 @declared_attr
4117 @declared_attr
4097 def user_id(cls):
4118 def user_id(cls):
4098 return Column(
4119 return Column(
4099 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4120 "user_id", Integer(), ForeignKey('users.user_id'), nullable=False,
4100 unique=None)
4121 unique=None)
4101
4122
4102 # 500 revisions max
4123 # 500 revisions max
4103 _revisions = Column(
4124 _revisions = Column(
4104 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4125 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
4105
4126
4106 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4127 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
4107
4128
4108 @declared_attr
4129 @declared_attr
4109 def source_repo_id(cls):
4130 def source_repo_id(cls):
4110 # TODO: dan: rename column to source_repo_id
4131 # TODO: dan: rename column to source_repo_id
4111 return Column(
4132 return Column(
4112 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4133 'org_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4113 nullable=False)
4134 nullable=False)
4114
4135
4115 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4136 _source_ref = Column('org_ref', Unicode(255), nullable=False)
4116
4137
4117 @hybrid_property
4138 @hybrid_property
4118 def source_ref(self):
4139 def source_ref(self):
4119 return self._source_ref
4140 return self._source_ref
4120
4141
4121 @source_ref.setter
4142 @source_ref.setter
4122 def source_ref(self, val):
4143 def source_ref(self, val):
4123 parts = (val or '').split(':')
4144 parts = (val or '').split(':')
4124 if len(parts) != 3:
4145 if len(parts) != 3:
4125 raise ValueError(
4146 raise ValueError(
4126 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4147 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4127 self._source_ref = safe_unicode(val)
4148 self._source_ref = safe_unicode(val)
4128
4149
4129 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4150 _target_ref = Column('other_ref', Unicode(255), nullable=False)
4130
4151
4131 @hybrid_property
4152 @hybrid_property
4132 def target_ref(self):
4153 def target_ref(self):
4133 return self._target_ref
4154 return self._target_ref
4134
4155
4135 @target_ref.setter
4156 @target_ref.setter
4136 def target_ref(self, val):
4157 def target_ref(self, val):
4137 parts = (val or '').split(':')
4158 parts = (val or '').split(':')
4138 if len(parts) != 3:
4159 if len(parts) != 3:
4139 raise ValueError(
4160 raise ValueError(
4140 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4161 'Invalid reference format given: {}, expected X:Y:Z'.format(val))
4141 self._target_ref = safe_unicode(val)
4162 self._target_ref = safe_unicode(val)
4142
4163
4143 @declared_attr
4164 @declared_attr
4144 def target_repo_id(cls):
4165 def target_repo_id(cls):
4145 # TODO: dan: rename column to target_repo_id
4166 # TODO: dan: rename column to target_repo_id
4146 return Column(
4167 return Column(
4147 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4168 'other_repo_id', Integer(), ForeignKey('repositories.repo_id'),
4148 nullable=False)
4169 nullable=False)
4149
4170
4150 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4171 _shadow_merge_ref = Column('shadow_merge_ref', Unicode(255), nullable=True)
4151
4172
4152 # TODO: dan: rename column to last_merge_source_rev
4173 # TODO: dan: rename column to last_merge_source_rev
4153 _last_merge_source_rev = Column(
4174 _last_merge_source_rev = Column(
4154 'last_merge_org_rev', String(40), nullable=True)
4175 'last_merge_org_rev', String(40), nullable=True)
4155 # TODO: dan: rename column to last_merge_target_rev
4176 # TODO: dan: rename column to last_merge_target_rev
4156 _last_merge_target_rev = Column(
4177 _last_merge_target_rev = Column(
4157 'last_merge_other_rev', String(40), nullable=True)
4178 'last_merge_other_rev', String(40), nullable=True)
4158 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4179 _last_merge_status = Column('merge_status', Integer(), nullable=True)
4159 last_merge_metadata = Column(
4180 last_merge_metadata = Column(
4160 'last_merge_metadata', MutationObj.as_mutable(
4181 'last_merge_metadata', MutationObj.as_mutable(
4161 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4182 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4162
4183
4163 merge_rev = Column('merge_rev', String(40), nullable=True)
4184 merge_rev = Column('merge_rev', String(40), nullable=True)
4164
4185
4165 reviewer_data = Column(
4186 reviewer_data = Column(
4166 'reviewer_data_json', MutationObj.as_mutable(
4187 'reviewer_data_json', MutationObj.as_mutable(
4167 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4188 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4168
4189
4169 @property
4190 @property
4170 def reviewer_data_json(self):
4191 def reviewer_data_json(self):
4171 return json.dumps(self.reviewer_data)
4192 return json.dumps(self.reviewer_data)
4172
4193
4173 @property
4194 @property
4174 def last_merge_metadata_parsed(self):
4195 def last_merge_metadata_parsed(self):
4175 metadata = {}
4196 metadata = {}
4176 if not self.last_merge_metadata:
4197 if not self.last_merge_metadata:
4177 return metadata
4198 return metadata
4178
4199
4179 if hasattr(self.last_merge_metadata, 'de_coerce'):
4200 if hasattr(self.last_merge_metadata, 'de_coerce'):
4180 for k, v in self.last_merge_metadata.de_coerce().items():
4201 for k, v in self.last_merge_metadata.de_coerce().items():
4181 if k in ['target_ref', 'source_ref']:
4202 if k in ['target_ref', 'source_ref']:
4182 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4203 metadata[k] = Reference(v['type'], v['name'], v['commit_id'])
4183 else:
4204 else:
4184 if hasattr(v, 'de_coerce'):
4205 if hasattr(v, 'de_coerce'):
4185 metadata[k] = v.de_coerce()
4206 metadata[k] = v.de_coerce()
4186 else:
4207 else:
4187 metadata[k] = v
4208 metadata[k] = v
4188 return metadata
4209 return metadata
4189
4210
4190 @property
4211 @property
4191 def work_in_progress(self):
4212 def work_in_progress(self):
4192 """checks if pull request is work in progress by checking the title"""
4213 """checks if pull request is work in progress by checking the title"""
4193 title = self.title.upper()
4214 title = self.title.upper()
4194 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4215 if re.match(r'^(\[WIP\]\s*|WIP:\s*|WIP\s+)', title):
4195 return True
4216 return True
4196 return False
4217 return False
4197
4218
4198 @hybrid_property
4219 @hybrid_property
4199 def description_safe(self):
4220 def description_safe(self):
4200 from rhodecode.lib import helpers as h
4221 from rhodecode.lib import helpers as h
4201 return h.escape(self.description)
4222 return h.escape(self.description)
4202
4223
4203 @hybrid_property
4224 @hybrid_property
4204 def revisions(self):
4225 def revisions(self):
4205 return self._revisions.split(':') if self._revisions else []
4226 return self._revisions.split(':') if self._revisions else []
4206
4227
4207 @revisions.setter
4228 @revisions.setter
4208 def revisions(self, val):
4229 def revisions(self, val):
4209 self._revisions = u':'.join(val)
4230 self._revisions = u':'.join(val)
4210
4231
4211 @hybrid_property
4232 @hybrid_property
4212 def last_merge_status(self):
4233 def last_merge_status(self):
4213 return safe_int(self._last_merge_status)
4234 return safe_int(self._last_merge_status)
4214
4235
4215 @last_merge_status.setter
4236 @last_merge_status.setter
4216 def last_merge_status(self, val):
4237 def last_merge_status(self, val):
4217 self._last_merge_status = val
4238 self._last_merge_status = val
4218
4239
4219 @declared_attr
4240 @declared_attr
4220 def author(cls):
4241 def author(cls):
4221 return relationship('User', lazy='joined')
4242 return relationship('User', lazy='joined')
4222
4243
4223 @declared_attr
4244 @declared_attr
4224 def source_repo(cls):
4245 def source_repo(cls):
4225 return relationship(
4246 return relationship(
4226 'Repository',
4247 'Repository',
4227 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4248 primaryjoin='%s.source_repo_id==Repository.repo_id' % cls.__name__)
4228
4249
4229 @property
4250 @property
4230 def source_ref_parts(self):
4251 def source_ref_parts(self):
4231 return self.unicode_to_reference(self.source_ref)
4252 return self.unicode_to_reference(self.source_ref)
4232
4253
4233 @declared_attr
4254 @declared_attr
4234 def target_repo(cls):
4255 def target_repo(cls):
4235 return relationship(
4256 return relationship(
4236 'Repository',
4257 'Repository',
4237 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4258 primaryjoin='%s.target_repo_id==Repository.repo_id' % cls.__name__)
4238
4259
4239 @property
4260 @property
4240 def target_ref_parts(self):
4261 def target_ref_parts(self):
4241 return self.unicode_to_reference(self.target_ref)
4262 return self.unicode_to_reference(self.target_ref)
4242
4263
4243 @property
4264 @property
4244 def shadow_merge_ref(self):
4265 def shadow_merge_ref(self):
4245 return self.unicode_to_reference(self._shadow_merge_ref)
4266 return self.unicode_to_reference(self._shadow_merge_ref)
4246
4267
4247 @shadow_merge_ref.setter
4268 @shadow_merge_ref.setter
4248 def shadow_merge_ref(self, ref):
4269 def shadow_merge_ref(self, ref):
4249 self._shadow_merge_ref = self.reference_to_unicode(ref)
4270 self._shadow_merge_ref = self.reference_to_unicode(ref)
4250
4271
4251 @staticmethod
4272 @staticmethod
4252 def unicode_to_reference(raw):
4273 def unicode_to_reference(raw):
4253 return unicode_to_reference(raw)
4274 return unicode_to_reference(raw)
4254
4275
4255 @staticmethod
4276 @staticmethod
4256 def reference_to_unicode(ref):
4277 def reference_to_unicode(ref):
4257 return reference_to_unicode(ref)
4278 return reference_to_unicode(ref)
4258
4279
4259 def get_api_data(self, with_merge_state=True):
4280 def get_api_data(self, with_merge_state=True):
4260 from rhodecode.model.pull_request import PullRequestModel
4281 from rhodecode.model.pull_request import PullRequestModel
4261
4282
4262 pull_request = self
4283 pull_request = self
4263 if with_merge_state:
4284 if with_merge_state:
4264 merge_response, merge_status, msg = \
4285 merge_response, merge_status, msg = \
4265 PullRequestModel().merge_status(pull_request)
4286 PullRequestModel().merge_status(pull_request)
4266 merge_state = {
4287 merge_state = {
4267 'status': merge_status,
4288 'status': merge_status,
4268 'message': safe_unicode(msg),
4289 'message': safe_unicode(msg),
4269 }
4290 }
4270 else:
4291 else:
4271 merge_state = {'status': 'not_available',
4292 merge_state = {'status': 'not_available',
4272 'message': 'not_available'}
4293 'message': 'not_available'}
4273
4294
4274 merge_data = {
4295 merge_data = {
4275 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4296 'clone_url': PullRequestModel().get_shadow_clone_url(pull_request),
4276 'reference': (
4297 'reference': (
4277 pull_request.shadow_merge_ref._asdict()
4298 pull_request.shadow_merge_ref._asdict()
4278 if pull_request.shadow_merge_ref else None),
4299 if pull_request.shadow_merge_ref else None),
4279 }
4300 }
4280
4301
4281 data = {
4302 data = {
4282 'pull_request_id': pull_request.pull_request_id,
4303 'pull_request_id': pull_request.pull_request_id,
4283 'url': PullRequestModel().get_url(pull_request),
4304 'url': PullRequestModel().get_url(pull_request),
4284 'title': pull_request.title,
4305 'title': pull_request.title,
4285 'description': pull_request.description,
4306 'description': pull_request.description,
4286 'status': pull_request.status,
4307 'status': pull_request.status,
4287 'state': pull_request.pull_request_state,
4308 'state': pull_request.pull_request_state,
4288 'created_on': pull_request.created_on,
4309 'created_on': pull_request.created_on,
4289 'updated_on': pull_request.updated_on,
4310 'updated_on': pull_request.updated_on,
4290 'commit_ids': pull_request.revisions,
4311 'commit_ids': pull_request.revisions,
4291 'review_status': pull_request.calculated_review_status(),
4312 'review_status': pull_request.calculated_review_status(),
4292 'mergeable': merge_state,
4313 'mergeable': merge_state,
4293 'source': {
4314 'source': {
4294 'clone_url': pull_request.source_repo.clone_url(),
4315 'clone_url': pull_request.source_repo.clone_url(),
4295 'repository': pull_request.source_repo.repo_name,
4316 'repository': pull_request.source_repo.repo_name,
4296 'reference': {
4317 'reference': {
4297 'name': pull_request.source_ref_parts.name,
4318 'name': pull_request.source_ref_parts.name,
4298 'type': pull_request.source_ref_parts.type,
4319 'type': pull_request.source_ref_parts.type,
4299 'commit_id': pull_request.source_ref_parts.commit_id,
4320 'commit_id': pull_request.source_ref_parts.commit_id,
4300 },
4321 },
4301 },
4322 },
4302 'target': {
4323 'target': {
4303 'clone_url': pull_request.target_repo.clone_url(),
4324 'clone_url': pull_request.target_repo.clone_url(),
4304 'repository': pull_request.target_repo.repo_name,
4325 'repository': pull_request.target_repo.repo_name,
4305 'reference': {
4326 'reference': {
4306 'name': pull_request.target_ref_parts.name,
4327 'name': pull_request.target_ref_parts.name,
4307 'type': pull_request.target_ref_parts.type,
4328 'type': pull_request.target_ref_parts.type,
4308 'commit_id': pull_request.target_ref_parts.commit_id,
4329 'commit_id': pull_request.target_ref_parts.commit_id,
4309 },
4330 },
4310 },
4331 },
4311 'merge': merge_data,
4332 'merge': merge_data,
4312 'author': pull_request.author.get_api_data(include_secrets=False,
4333 'author': pull_request.author.get_api_data(include_secrets=False,
4313 details='basic'),
4334 details='basic'),
4314 'reviewers': [
4335 'reviewers': [
4315 {
4336 {
4316 'user': reviewer.get_api_data(include_secrets=False,
4337 'user': reviewer.get_api_data(include_secrets=False,
4317 details='basic'),
4338 details='basic'),
4318 'reasons': reasons,
4339 'reasons': reasons,
4319 'review_status': st[0][1].status if st else 'not_reviewed',
4340 'review_status': st[0][1].status if st else 'not_reviewed',
4320 }
4341 }
4321 for obj, reviewer, reasons, mandatory, st in
4342 for obj, reviewer, reasons, mandatory, st in
4322 pull_request.reviewers_statuses()
4343 pull_request.reviewers_statuses()
4323 ]
4344 ]
4324 }
4345 }
4325
4346
4326 return data
4347 return data
4327
4348
4328 def set_state(self, pull_request_state, final_state=None):
4349 def set_state(self, pull_request_state, final_state=None):
4329 """
4350 """
4330 # goes from initial state to updating to initial state.
4351 # goes from initial state to updating to initial state.
4331 # initial state can be changed by specifying back_state=
4352 # initial state can be changed by specifying back_state=
4332 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4353 with pull_request_obj.set_state(PullRequest.STATE_UPDATING):
4333 pull_request.merge()
4354 pull_request.merge()
4334
4355
4335 :param pull_request_state:
4356 :param pull_request_state:
4336 :param final_state:
4357 :param final_state:
4337
4358
4338 """
4359 """
4339
4360
4340 return _SetState(self, pull_request_state, back_state=final_state)
4361 return _SetState(self, pull_request_state, back_state=final_state)
4341
4362
4342
4363
4343 class PullRequest(Base, _PullRequestBase):
4364 class PullRequest(Base, _PullRequestBase):
4344 __tablename__ = 'pull_requests'
4365 __tablename__ = 'pull_requests'
4345 __table_args__ = (
4366 __table_args__ = (
4346 base_table_args,
4367 base_table_args,
4347 )
4368 )
4348 LATEST_VER = 'latest'
4369 LATEST_VER = 'latest'
4349
4370
4350 pull_request_id = Column(
4371 pull_request_id = Column(
4351 'pull_request_id', Integer(), nullable=False, primary_key=True)
4372 'pull_request_id', Integer(), nullable=False, primary_key=True)
4352
4373
4353 def __repr__(self):
4374 def __repr__(self):
4354 if self.pull_request_id:
4375 if self.pull_request_id:
4355 return '<DB:PullRequest #%s>' % self.pull_request_id
4376 return '<DB:PullRequest #%s>' % self.pull_request_id
4356 else:
4377 else:
4357 return '<DB:PullRequest at %#x>' % id(self)
4378 return '<DB:PullRequest at %#x>' % id(self)
4358
4379
4359 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4380 reviewers = relationship('PullRequestReviewers', cascade="all, delete-orphan")
4360 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4381 statuses = relationship('ChangesetStatus', cascade="all, delete-orphan")
4361 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4382 comments = relationship('ChangesetComment', cascade="all, delete-orphan")
4362 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4383 versions = relationship('PullRequestVersion', cascade="all, delete-orphan",
4363 lazy='dynamic')
4384 lazy='dynamic')
4364
4385
4365 @classmethod
4386 @classmethod
4366 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4387 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
4367 internal_methods=None):
4388 internal_methods=None):
4368
4389
4369 class PullRequestDisplay(object):
4390 class PullRequestDisplay(object):
4370 """
4391 """
4371 Special object wrapper for showing PullRequest data via Versions
4392 Special object wrapper for showing PullRequest data via Versions
4372 It mimics PR object as close as possible. This is read only object
4393 It mimics PR object as close as possible. This is read only object
4373 just for display
4394 just for display
4374 """
4395 """
4375
4396
4376 def __init__(self, attrs, internal=None):
4397 def __init__(self, attrs, internal=None):
4377 self.attrs = attrs
4398 self.attrs = attrs
4378 # internal have priority over the given ones via attrs
4399 # internal have priority over the given ones via attrs
4379 self.internal = internal or ['versions']
4400 self.internal = internal or ['versions']
4380
4401
4381 def __getattr__(self, item):
4402 def __getattr__(self, item):
4382 if item in self.internal:
4403 if item in self.internal:
4383 return getattr(self, item)
4404 return getattr(self, item)
4384 try:
4405 try:
4385 return self.attrs[item]
4406 return self.attrs[item]
4386 except KeyError:
4407 except KeyError:
4387 raise AttributeError(
4408 raise AttributeError(
4388 '%s object has no attribute %s' % (self, item))
4409 '%s object has no attribute %s' % (self, item))
4389
4410
4390 def __repr__(self):
4411 def __repr__(self):
4391 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4412 return '<DB:PullRequestDisplay #%s>' % self.attrs.get('pull_request_id')
4392
4413
4393 def versions(self):
4414 def versions(self):
4394 return pull_request_obj.versions.order_by(
4415 return pull_request_obj.versions.order_by(
4395 PullRequestVersion.pull_request_version_id).all()
4416 PullRequestVersion.pull_request_version_id).all()
4396
4417
4397 def is_closed(self):
4418 def is_closed(self):
4398 return pull_request_obj.is_closed()
4419 return pull_request_obj.is_closed()
4399
4420
4400 def is_state_changing(self):
4421 def is_state_changing(self):
4401 return pull_request_obj.is_state_changing()
4422 return pull_request_obj.is_state_changing()
4402
4423
4403 @property
4424 @property
4404 def pull_request_version_id(self):
4425 def pull_request_version_id(self):
4405 return getattr(pull_request_obj, 'pull_request_version_id', None)
4426 return getattr(pull_request_obj, 'pull_request_version_id', None)
4406
4427
4407 @property
4428 @property
4408 def pull_request_last_version(self):
4429 def pull_request_last_version(self):
4409 return pull_request_obj.pull_request_last_version
4430 return pull_request_obj.pull_request_last_version
4410
4431
4411 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4432 attrs = StrictAttributeDict(pull_request_obj.get_api_data(with_merge_state=False))
4412
4433
4413 attrs.author = StrictAttributeDict(
4434 attrs.author = StrictAttributeDict(
4414 pull_request_obj.author.get_api_data())
4435 pull_request_obj.author.get_api_data())
4415 if pull_request_obj.target_repo:
4436 if pull_request_obj.target_repo:
4416 attrs.target_repo = StrictAttributeDict(
4437 attrs.target_repo = StrictAttributeDict(
4417 pull_request_obj.target_repo.get_api_data())
4438 pull_request_obj.target_repo.get_api_data())
4418 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4439 attrs.target_repo.clone_url = pull_request_obj.target_repo.clone_url
4419
4440
4420 if pull_request_obj.source_repo:
4441 if pull_request_obj.source_repo:
4421 attrs.source_repo = StrictAttributeDict(
4442 attrs.source_repo = StrictAttributeDict(
4422 pull_request_obj.source_repo.get_api_data())
4443 pull_request_obj.source_repo.get_api_data())
4423 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4444 attrs.source_repo.clone_url = pull_request_obj.source_repo.clone_url
4424
4445
4425 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4446 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4426 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4447 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4427 attrs.revisions = pull_request_obj.revisions
4448 attrs.revisions = pull_request_obj.revisions
4428 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4449 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4429 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4450 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4430 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4451 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4431 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4452 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4432
4453
4433 return PullRequestDisplay(attrs, internal=internal_methods)
4454 return PullRequestDisplay(attrs, internal=internal_methods)
4434
4455
4435 def is_closed(self):
4456 def is_closed(self):
4436 return self.status == self.STATUS_CLOSED
4457 return self.status == self.STATUS_CLOSED
4437
4458
4438 def is_state_changing(self):
4459 def is_state_changing(self):
4439 return self.pull_request_state != PullRequest.STATE_CREATED
4460 return self.pull_request_state != PullRequest.STATE_CREATED
4440
4461
4441 def __json__(self):
4462 def __json__(self):
4442 return {
4463 return {
4443 'revisions': self.revisions,
4464 'revisions': self.revisions,
4444 'versions': self.versions_count
4465 'versions': self.versions_count
4445 }
4466 }
4446
4467
4447 def calculated_review_status(self):
4468 def calculated_review_status(self):
4448 from rhodecode.model.changeset_status import ChangesetStatusModel
4469 from rhodecode.model.changeset_status import ChangesetStatusModel
4449 return ChangesetStatusModel().calculated_review_status(self)
4470 return ChangesetStatusModel().calculated_review_status(self)
4450
4471
4451 def reviewers_statuses(self):
4472 def reviewers_statuses(self):
4452 from rhodecode.model.changeset_status import ChangesetStatusModel
4473 from rhodecode.model.changeset_status import ChangesetStatusModel
4453 return ChangesetStatusModel().reviewers_statuses(self)
4474 return ChangesetStatusModel().reviewers_statuses(self)
4454
4475
4455 def get_pull_request_reviewers(self, role=None):
4476 def get_pull_request_reviewers(self, role=None):
4456 qry = PullRequestReviewers.query()\
4477 qry = PullRequestReviewers.query()\
4457 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4478 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)
4458 if role:
4479 if role:
4459 qry = qry.filter(PullRequestReviewers.role == role)
4480 qry = qry.filter(PullRequestReviewers.role == role)
4460
4481
4461 return qry.all()
4482 return qry.all()
4462
4483
4463 @property
4484 @property
4464 def reviewers_count(self):
4485 def reviewers_count(self):
4465 qry = PullRequestReviewers.query()\
4486 qry = PullRequestReviewers.query()\
4466 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4487 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4467 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4488 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_REVIEWER)
4468 return qry.count()
4489 return qry.count()
4469
4490
4470 @property
4491 @property
4471 def observers_count(self):
4492 def observers_count(self):
4472 qry = PullRequestReviewers.query()\
4493 qry = PullRequestReviewers.query()\
4473 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4494 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4474 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4495 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)
4475 return qry.count()
4496 return qry.count()
4476
4497
4477 def observers(self):
4498 def observers(self):
4478 qry = PullRequestReviewers.query()\
4499 qry = PullRequestReviewers.query()\
4479 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4500 .filter(PullRequestReviewers.pull_request_id == self.pull_request_id)\
4480 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4501 .filter(PullRequestReviewers.role == PullRequestReviewers.ROLE_OBSERVER)\
4481 .all()
4502 .all()
4482
4503
4483 for entry in qry:
4504 for entry in qry:
4484 yield entry, entry.user
4505 yield entry, entry.user
4485
4506
4486 @property
4507 @property
4487 def workspace_id(self):
4508 def workspace_id(self):
4488 from rhodecode.model.pull_request import PullRequestModel
4509 from rhodecode.model.pull_request import PullRequestModel
4489 return PullRequestModel()._workspace_id(self)
4510 return PullRequestModel()._workspace_id(self)
4490
4511
4491 def get_shadow_repo(self):
4512 def get_shadow_repo(self):
4492 workspace_id = self.workspace_id
4513 workspace_id = self.workspace_id
4493 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4514 shadow_repository_path = self.target_repo.get_shadow_repository_path(workspace_id)
4494 if os.path.isdir(shadow_repository_path):
4515 if os.path.isdir(shadow_repository_path):
4495 vcs_obj = self.target_repo.scm_instance()
4516 vcs_obj = self.target_repo.scm_instance()
4496 return vcs_obj.get_shadow_instance(shadow_repository_path)
4517 return vcs_obj.get_shadow_instance(shadow_repository_path)
4497
4518
4498 @property
4519 @property
4499 def versions_count(self):
4520 def versions_count(self):
4500 """
4521 """
4501 return number of versions this PR have, e.g a PR that once been
4522 return number of versions this PR have, e.g a PR that once been
4502 updated will have 2 versions
4523 updated will have 2 versions
4503 """
4524 """
4504 return self.versions.count() + 1
4525 return self.versions.count() + 1
4505
4526
4506 @property
4527 @property
4507 def pull_request_last_version(self):
4528 def pull_request_last_version(self):
4508 return self.versions_count
4529 return self.versions_count
4509
4530
4510
4531
4511 class PullRequestVersion(Base, _PullRequestBase):
4532 class PullRequestVersion(Base, _PullRequestBase):
4512 __tablename__ = 'pull_request_versions'
4533 __tablename__ = 'pull_request_versions'
4513 __table_args__ = (
4534 __table_args__ = (
4514 base_table_args,
4535 base_table_args,
4515 )
4536 )
4516
4537
4517 pull_request_version_id = Column(
4538 pull_request_version_id = Column(
4518 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4539 'pull_request_version_id', Integer(), nullable=False, primary_key=True)
4519 pull_request_id = Column(
4540 pull_request_id = Column(
4520 'pull_request_id', Integer(),
4541 'pull_request_id', Integer(),
4521 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4542 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4522 pull_request = relationship('PullRequest')
4543 pull_request = relationship('PullRequest')
4523
4544
4524 def __repr__(self):
4545 def __repr__(self):
4525 if self.pull_request_version_id:
4546 if self.pull_request_version_id:
4526 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4547 return '<DB:PullRequestVersion #%s>' % self.pull_request_version_id
4527 else:
4548 else:
4528 return '<DB:PullRequestVersion at %#x>' % id(self)
4549 return '<DB:PullRequestVersion at %#x>' % id(self)
4529
4550
4530 @property
4551 @property
4531 def reviewers(self):
4552 def reviewers(self):
4532 return self.pull_request.reviewers
4553 return self.pull_request.reviewers
4533 @property
4554 @property
4534 def reviewers(self):
4555 def reviewers(self):
4535 return self.pull_request.reviewers
4556 return self.pull_request.reviewers
4536
4557
4537 @property
4558 @property
4538 def versions(self):
4559 def versions(self):
4539 return self.pull_request.versions
4560 return self.pull_request.versions
4540
4561
4541 def is_closed(self):
4562 def is_closed(self):
4542 # calculate from original
4563 # calculate from original
4543 return self.pull_request.status == self.STATUS_CLOSED
4564 return self.pull_request.status == self.STATUS_CLOSED
4544
4565
4545 def is_state_changing(self):
4566 def is_state_changing(self):
4546 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4567 return self.pull_request.pull_request_state != PullRequest.STATE_CREATED
4547
4568
4548 def calculated_review_status(self):
4569 def calculated_review_status(self):
4549 return self.pull_request.calculated_review_status()
4570 return self.pull_request.calculated_review_status()
4550
4571
4551 def reviewers_statuses(self):
4572 def reviewers_statuses(self):
4552 return self.pull_request.reviewers_statuses()
4573 return self.pull_request.reviewers_statuses()
4553
4574
4554 def observers(self):
4575 def observers(self):
4555 return self.pull_request.observers()
4576 return self.pull_request.observers()
4556
4577
4557
4578
4558 class PullRequestReviewers(Base, BaseModel):
4579 class PullRequestReviewers(Base, BaseModel):
4559 __tablename__ = 'pull_request_reviewers'
4580 __tablename__ = 'pull_request_reviewers'
4560 __table_args__ = (
4581 __table_args__ = (
4561 base_table_args,
4582 base_table_args,
4562 )
4583 )
4563 ROLE_REVIEWER = u'reviewer'
4584 ROLE_REVIEWER = u'reviewer'
4564 ROLE_OBSERVER = u'observer'
4585 ROLE_OBSERVER = u'observer'
4565 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4586 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4566
4587
4567 @hybrid_property
4588 @hybrid_property
4568 def reasons(self):
4589 def reasons(self):
4569 if not self._reasons:
4590 if not self._reasons:
4570 return []
4591 return []
4571 return self._reasons
4592 return self._reasons
4572
4593
4573 @reasons.setter
4594 @reasons.setter
4574 def reasons(self, val):
4595 def reasons(self, val):
4575 val = val or []
4596 val = val or []
4576 if any(not isinstance(x, compat.string_types) for x in val):
4597 if any(not isinstance(x, compat.string_types) for x in val):
4577 raise Exception('invalid reasons type, must be list of strings')
4598 raise Exception('invalid reasons type, must be list of strings')
4578 self._reasons = val
4599 self._reasons = val
4579
4600
4580 pull_requests_reviewers_id = Column(
4601 pull_requests_reviewers_id = Column(
4581 'pull_requests_reviewers_id', Integer(), nullable=False,
4602 'pull_requests_reviewers_id', Integer(), nullable=False,
4582 primary_key=True)
4603 primary_key=True)
4583 pull_request_id = Column(
4604 pull_request_id = Column(
4584 "pull_request_id", Integer(),
4605 "pull_request_id", Integer(),
4585 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4606 ForeignKey('pull_requests.pull_request_id'), nullable=False)
4586 user_id = Column(
4607 user_id = Column(
4587 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4608 "user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
4588 _reasons = Column(
4609 _reasons = Column(
4589 'reason', MutationList.as_mutable(
4610 'reason', MutationList.as_mutable(
4590 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4611 JsonType('list', dialect_map=dict(mysql=UnicodeText(16384)))))
4591
4612
4592 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4613 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
4593 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4614 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
4594
4615
4595 user = relationship('User')
4616 user = relationship('User')
4596 pull_request = relationship('PullRequest')
4617 pull_request = relationship('PullRequest')
4597
4618
4598 rule_data = Column(
4619 rule_data = Column(
4599 'rule_data_json',
4620 'rule_data_json',
4600 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4621 JsonType(dialect_map=dict(mysql=UnicodeText(16384))))
4601
4622
4602 def rule_user_group_data(self):
4623 def rule_user_group_data(self):
4603 """
4624 """
4604 Returns the voting user group rule data for this reviewer
4625 Returns the voting user group rule data for this reviewer
4605 """
4626 """
4606
4627
4607 if self.rule_data and 'vote_rule' in self.rule_data:
4628 if self.rule_data and 'vote_rule' in self.rule_data:
4608 user_group_data = {}
4629 user_group_data = {}
4609 if 'rule_user_group_entry_id' in self.rule_data:
4630 if 'rule_user_group_entry_id' in self.rule_data:
4610 # means a group with voting rules !
4631 # means a group with voting rules !
4611 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4632 user_group_data['id'] = self.rule_data['rule_user_group_entry_id']
4612 user_group_data['name'] = self.rule_data['rule_name']
4633 user_group_data['name'] = self.rule_data['rule_name']
4613 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4634 user_group_data['vote_rule'] = self.rule_data['vote_rule']
4614
4635
4615 return user_group_data
4636 return user_group_data
4616
4637
4617 @classmethod
4638 @classmethod
4618 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4639 def get_pull_request_reviewers(cls, pull_request_id, role=None):
4619 qry = PullRequestReviewers.query()\
4640 qry = PullRequestReviewers.query()\
4620 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4641 .filter(PullRequestReviewers.pull_request_id == pull_request_id)
4621 if role:
4642 if role:
4622 qry = qry.filter(PullRequestReviewers.role == role)
4643 qry = qry.filter(PullRequestReviewers.role == role)
4623
4644
4624 return qry.all()
4645 return qry.all()
4625
4646
4626 def __unicode__(self):
4647 def __unicode__(self):
4627 return u"<%s('id:%s')>" % (self.__class__.__name__,
4648 return u"<%s('id:%s')>" % (self.__class__.__name__,
4628 self.pull_requests_reviewers_id)
4649 self.pull_requests_reviewers_id)
4629
4650
4630
4651
4631 class Notification(Base, BaseModel):
4652 class Notification(Base, BaseModel):
4632 __tablename__ = 'notifications'
4653 __tablename__ = 'notifications'
4633 __table_args__ = (
4654 __table_args__ = (
4634 Index('notification_type_idx', 'type'),
4655 Index('notification_type_idx', 'type'),
4635 base_table_args,
4656 base_table_args,
4636 )
4657 )
4637
4658
4638 TYPE_CHANGESET_COMMENT = u'cs_comment'
4659 TYPE_CHANGESET_COMMENT = u'cs_comment'
4639 TYPE_MESSAGE = u'message'
4660 TYPE_MESSAGE = u'message'
4640 TYPE_MENTION = u'mention'
4661 TYPE_MENTION = u'mention'
4641 TYPE_REGISTRATION = u'registration'
4662 TYPE_REGISTRATION = u'registration'
4642 TYPE_PULL_REQUEST = u'pull_request'
4663 TYPE_PULL_REQUEST = u'pull_request'
4643 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4664 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
4644 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4665 TYPE_PULL_REQUEST_UPDATE = u'pull_request_update'
4645
4666
4646 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4667 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
4647 subject = Column('subject', Unicode(512), nullable=True)
4668 subject = Column('subject', Unicode(512), nullable=True)
4648 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4669 body = Column('body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4649 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4670 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
4650 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4671 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4651 type_ = Column('type', Unicode(255))
4672 type_ = Column('type', Unicode(255))
4652
4673
4653 created_by_user = relationship('User')
4674 created_by_user = relationship('User')
4654 notifications_to_users = relationship('UserNotification', lazy='joined',
4675 notifications_to_users = relationship('UserNotification', lazy='joined',
4655 cascade="all, delete-orphan")
4676 cascade="all, delete-orphan")
4656
4677
4657 @property
4678 @property
4658 def recipients(self):
4679 def recipients(self):
4659 return [x.user for x in UserNotification.query()\
4680 return [x.user for x in UserNotification.query()\
4660 .filter(UserNotification.notification == self)\
4681 .filter(UserNotification.notification == self)\
4661 .order_by(UserNotification.user_id.asc()).all()]
4682 .order_by(UserNotification.user_id.asc()).all()]
4662
4683
4663 @classmethod
4684 @classmethod
4664 def create(cls, created_by, subject, body, recipients, type_=None):
4685 def create(cls, created_by, subject, body, recipients, type_=None):
4665 if type_ is None:
4686 if type_ is None:
4666 type_ = Notification.TYPE_MESSAGE
4687 type_ = Notification.TYPE_MESSAGE
4667
4688
4668 notification = cls()
4689 notification = cls()
4669 notification.created_by_user = created_by
4690 notification.created_by_user = created_by
4670 notification.subject = subject
4691 notification.subject = subject
4671 notification.body = body
4692 notification.body = body
4672 notification.type_ = type_
4693 notification.type_ = type_
4673 notification.created_on = datetime.datetime.now()
4694 notification.created_on = datetime.datetime.now()
4674
4695
4675 # For each recipient link the created notification to his account
4696 # For each recipient link the created notification to his account
4676 for u in recipients:
4697 for u in recipients:
4677 assoc = UserNotification()
4698 assoc = UserNotification()
4678 assoc.user_id = u.user_id
4699 assoc.user_id = u.user_id
4679 assoc.notification = notification
4700 assoc.notification = notification
4680
4701
4681 # if created_by is inside recipients mark his notification
4702 # if created_by is inside recipients mark his notification
4682 # as read
4703 # as read
4683 if u.user_id == created_by.user_id:
4704 if u.user_id == created_by.user_id:
4684 assoc.read = True
4705 assoc.read = True
4685 Session().add(assoc)
4706 Session().add(assoc)
4686
4707
4687 Session().add(notification)
4708 Session().add(notification)
4688
4709
4689 return notification
4710 return notification
4690
4711
4691
4712
4692 class UserNotification(Base, BaseModel):
4713 class UserNotification(Base, BaseModel):
4693 __tablename__ = 'user_to_notification'
4714 __tablename__ = 'user_to_notification'
4694 __table_args__ = (
4715 __table_args__ = (
4695 UniqueConstraint('user_id', 'notification_id'),
4716 UniqueConstraint('user_id', 'notification_id'),
4696 base_table_args
4717 base_table_args
4697 )
4718 )
4698
4719
4699 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4720 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4700 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4721 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
4701 read = Column('read', Boolean, default=False)
4722 read = Column('read', Boolean, default=False)
4702 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4723 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
4703
4724
4704 user = relationship('User', lazy="joined")
4725 user = relationship('User', lazy="joined")
4705 notification = relationship('Notification', lazy="joined",
4726 notification = relationship('Notification', lazy="joined",
4706 order_by=lambda: Notification.created_on.desc(),)
4727 order_by=lambda: Notification.created_on.desc(),)
4707
4728
4708 def mark_as_read(self):
4729 def mark_as_read(self):
4709 self.read = True
4730 self.read = True
4710 Session().add(self)
4731 Session().add(self)
4711
4732
4712
4733
4713 class UserNotice(Base, BaseModel):
4734 class UserNotice(Base, BaseModel):
4714 __tablename__ = 'user_notices'
4735 __tablename__ = 'user_notices'
4715 __table_args__ = (
4736 __table_args__ = (
4716 base_table_args
4737 base_table_args
4717 )
4738 )
4718
4739
4719 NOTIFICATION_TYPE_MESSAGE = 'message'
4740 NOTIFICATION_TYPE_MESSAGE = 'message'
4720 NOTIFICATION_TYPE_NOTICE = 'notice'
4741 NOTIFICATION_TYPE_NOTICE = 'notice'
4721
4742
4722 NOTIFICATION_LEVEL_INFO = 'info'
4743 NOTIFICATION_LEVEL_INFO = 'info'
4723 NOTIFICATION_LEVEL_WARNING = 'warning'
4744 NOTIFICATION_LEVEL_WARNING = 'warning'
4724 NOTIFICATION_LEVEL_ERROR = 'error'
4745 NOTIFICATION_LEVEL_ERROR = 'error'
4725
4746
4726 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4747 user_notice_id = Column('gist_id', Integer(), primary_key=True)
4727
4748
4728 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4749 notice_subject = Column('notice_subject', Unicode(512), nullable=True)
4729 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4750 notice_body = Column('notice_body', UnicodeText().with_variant(UnicodeText(50000), 'mysql'), nullable=True)
4730
4751
4731 notice_read = Column('notice_read', Boolean, default=False)
4752 notice_read = Column('notice_read', Boolean, default=False)
4732
4753
4733 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4754 notification_level = Column('notification_level', String(1024), default=NOTIFICATION_LEVEL_INFO)
4734 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4755 notification_type = Column('notification_type', String(1024), default=NOTIFICATION_TYPE_NOTICE)
4735
4756
4736 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4757 notice_created_by = Column('notice_created_by', Integer(), ForeignKey('users.user_id'), nullable=True)
4737 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4758 notice_created_on = Column('notice_created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4738
4759
4739 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4760 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'))
4740 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4761 user = relationship('User', lazy="joined", primaryjoin='User.user_id==UserNotice.user_id')
4741
4762
4742 @classmethod
4763 @classmethod
4743 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4764 def create_for_user(cls, user, subject, body, notice_level=NOTIFICATION_LEVEL_INFO, allow_duplicate=False):
4744
4765
4745 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4766 if notice_level not in [cls.NOTIFICATION_LEVEL_ERROR,
4746 cls.NOTIFICATION_LEVEL_WARNING,
4767 cls.NOTIFICATION_LEVEL_WARNING,
4747 cls.NOTIFICATION_LEVEL_INFO]:
4768 cls.NOTIFICATION_LEVEL_INFO]:
4748 return
4769 return
4749
4770
4750 from rhodecode.model.user import UserModel
4771 from rhodecode.model.user import UserModel
4751 user = UserModel().get_user(user)
4772 user = UserModel().get_user(user)
4752
4773
4753 new_notice = UserNotice()
4774 new_notice = UserNotice()
4754 if not allow_duplicate:
4775 if not allow_duplicate:
4755 existing_msg = UserNotice().query() \
4776 existing_msg = UserNotice().query() \
4756 .filter(UserNotice.user == user) \
4777 .filter(UserNotice.user == user) \
4757 .filter(UserNotice.notice_body == body) \
4778 .filter(UserNotice.notice_body == body) \
4758 .filter(UserNotice.notice_read == false()) \
4779 .filter(UserNotice.notice_read == false()) \
4759 .scalar()
4780 .scalar()
4760 if existing_msg:
4781 if existing_msg:
4761 log.warning('Ignoring duplicate notice for user %s', user)
4782 log.warning('Ignoring duplicate notice for user %s', user)
4762 return
4783 return
4763
4784
4764 new_notice.user = user
4785 new_notice.user = user
4765 new_notice.notice_subject = subject
4786 new_notice.notice_subject = subject
4766 new_notice.notice_body = body
4787 new_notice.notice_body = body
4767 new_notice.notification_level = notice_level
4788 new_notice.notification_level = notice_level
4768 Session().add(new_notice)
4789 Session().add(new_notice)
4769 Session().commit()
4790 Session().commit()
4770
4791
4771
4792
4772 class Gist(Base, BaseModel):
4793 class Gist(Base, BaseModel):
4773 __tablename__ = 'gists'
4794 __tablename__ = 'gists'
4774 __table_args__ = (
4795 __table_args__ = (
4775 Index('g_gist_access_id_idx', 'gist_access_id'),
4796 Index('g_gist_access_id_idx', 'gist_access_id'),
4776 Index('g_created_on_idx', 'created_on'),
4797 Index('g_created_on_idx', 'created_on'),
4777 base_table_args
4798 base_table_args
4778 )
4799 )
4779
4800
4780 GIST_PUBLIC = u'public'
4801 GIST_PUBLIC = u'public'
4781 GIST_PRIVATE = u'private'
4802 GIST_PRIVATE = u'private'
4782 DEFAULT_FILENAME = u'gistfile1.txt'
4803 DEFAULT_FILENAME = u'gistfile1.txt'
4783
4804
4784 ACL_LEVEL_PUBLIC = u'acl_public'
4805 ACL_LEVEL_PUBLIC = u'acl_public'
4785 ACL_LEVEL_PRIVATE = u'acl_private'
4806 ACL_LEVEL_PRIVATE = u'acl_private'
4786
4807
4787 gist_id = Column('gist_id', Integer(), primary_key=True)
4808 gist_id = Column('gist_id', Integer(), primary_key=True)
4788 gist_access_id = Column('gist_access_id', Unicode(250))
4809 gist_access_id = Column('gist_access_id', Unicode(250))
4789 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4810 gist_description = Column('gist_description', UnicodeText().with_variant(UnicodeText(1024), 'mysql'))
4790 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4811 gist_owner = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=True)
4791 gist_expires = Column('gist_expires', Float(53), nullable=False)
4812 gist_expires = Column('gist_expires', Float(53), nullable=False)
4792 gist_type = Column('gist_type', Unicode(128), nullable=False)
4813 gist_type = Column('gist_type', Unicode(128), nullable=False)
4793 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4814 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4794 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4815 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
4795 acl_level = Column('acl_level', Unicode(128), nullable=True)
4816 acl_level = Column('acl_level', Unicode(128), nullable=True)
4796
4817
4797 owner = relationship('User')
4818 owner = relationship('User')
4798
4819
4799 def __repr__(self):
4820 def __repr__(self):
4800 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4821 return '<Gist:[%s]%s>' % (self.gist_type, self.gist_access_id)
4801
4822
4802 @hybrid_property
4823 @hybrid_property
4803 def description_safe(self):
4824 def description_safe(self):
4804 from rhodecode.lib import helpers as h
4825 from rhodecode.lib import helpers as h
4805 return h.escape(self.gist_description)
4826 return h.escape(self.gist_description)
4806
4827
4807 @classmethod
4828 @classmethod
4808 def get_or_404(cls, id_):
4829 def get_or_404(cls, id_):
4809 from pyramid.httpexceptions import HTTPNotFound
4830 from pyramid.httpexceptions import HTTPNotFound
4810
4831
4811 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4832 res = cls.query().filter(cls.gist_access_id == id_).scalar()
4812 if not res:
4833 if not res:
4813 log.debug('WARN: No DB entry with id %s', id_)
4834 log.debug('WARN: No DB entry with id %s', id_)
4814 raise HTTPNotFound()
4835 raise HTTPNotFound()
4815 return res
4836 return res
4816
4837
4817 @classmethod
4838 @classmethod
4818 def get_by_access_id(cls, gist_access_id):
4839 def get_by_access_id(cls, gist_access_id):
4819 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4840 return cls.query().filter(cls.gist_access_id == gist_access_id).scalar()
4820
4841
4821 def gist_url(self):
4842 def gist_url(self):
4822 from rhodecode.model.gist import GistModel
4843 from rhodecode.model.gist import GistModel
4823 return GistModel().get_url(self)
4844 return GistModel().get_url(self)
4824
4845
4825 @classmethod
4846 @classmethod
4826 def base_path(cls):
4847 def base_path(cls):
4827 """
4848 """
4828 Returns base path when all gists are stored
4849 Returns base path when all gists are stored
4829
4850
4830 :param cls:
4851 :param cls:
4831 """
4852 """
4832 from rhodecode.model.gist import GIST_STORE_LOC
4853 from rhodecode.model.gist import GIST_STORE_LOC
4833 q = Session().query(RhodeCodeUi)\
4854 q = Session().query(RhodeCodeUi)\
4834 .filter(RhodeCodeUi.ui_key == URL_SEP)
4855 .filter(RhodeCodeUi.ui_key == URL_SEP)
4835 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4856 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
4836 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4857 return os.path.join(q.one().ui_value, GIST_STORE_LOC)
4837
4858
4838 def get_api_data(self):
4859 def get_api_data(self):
4839 """
4860 """
4840 Common function for generating gist related data for API
4861 Common function for generating gist related data for API
4841 """
4862 """
4842 gist = self
4863 gist = self
4843 data = {
4864 data = {
4844 'gist_id': gist.gist_id,
4865 'gist_id': gist.gist_id,
4845 'type': gist.gist_type,
4866 'type': gist.gist_type,
4846 'access_id': gist.gist_access_id,
4867 'access_id': gist.gist_access_id,
4847 'description': gist.gist_description,
4868 'description': gist.gist_description,
4848 'url': gist.gist_url(),
4869 'url': gist.gist_url(),
4849 'expires': gist.gist_expires,
4870 'expires': gist.gist_expires,
4850 'created_on': gist.created_on,
4871 'created_on': gist.created_on,
4851 'modified_at': gist.modified_at,
4872 'modified_at': gist.modified_at,
4852 'content': None,
4873 'content': None,
4853 'acl_level': gist.acl_level,
4874 'acl_level': gist.acl_level,
4854 }
4875 }
4855 return data
4876 return data
4856
4877
4857 def __json__(self):
4878 def __json__(self):
4858 data = dict(
4879 data = dict(
4859 )
4880 )
4860 data.update(self.get_api_data())
4881 data.update(self.get_api_data())
4861 return data
4882 return data
4862 # SCM functions
4883 # SCM functions
4863
4884
4864 def scm_instance(self, **kwargs):
4885 def scm_instance(self, **kwargs):
4865 """
4886 """
4866 Get an instance of VCS Repository
4887 Get an instance of VCS Repository
4867
4888
4868 :param kwargs:
4889 :param kwargs:
4869 """
4890 """
4870 from rhodecode.model.gist import GistModel
4891 from rhodecode.model.gist import GistModel
4871 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4892 full_repo_path = os.path.join(self.base_path(), self.gist_access_id)
4872 return get_vcs_instance(
4893 return get_vcs_instance(
4873 repo_path=safe_str(full_repo_path), create=False,
4894 repo_path=safe_str(full_repo_path), create=False,
4874 _vcs_alias=GistModel.vcs_backend)
4895 _vcs_alias=GistModel.vcs_backend)
4875
4896
4876
4897
4877 class ExternalIdentity(Base, BaseModel):
4898 class ExternalIdentity(Base, BaseModel):
4878 __tablename__ = 'external_identities'
4899 __tablename__ = 'external_identities'
4879 __table_args__ = (
4900 __table_args__ = (
4880 Index('local_user_id_idx', 'local_user_id'),
4901 Index('local_user_id_idx', 'local_user_id'),
4881 Index('external_id_idx', 'external_id'),
4902 Index('external_id_idx', 'external_id'),
4882 base_table_args
4903 base_table_args
4883 )
4904 )
4884
4905
4885 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4906 external_id = Column('external_id', Unicode(255), default=u'', primary_key=True)
4886 external_username = Column('external_username', Unicode(1024), default=u'')
4907 external_username = Column('external_username', Unicode(1024), default=u'')
4887 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4908 local_user_id = Column('local_user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
4888 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4909 provider_name = Column('provider_name', Unicode(255), default=u'', primary_key=True)
4889 access_token = Column('access_token', String(1024), default=u'')
4910 access_token = Column('access_token', String(1024), default=u'')
4890 alt_token = Column('alt_token', String(1024), default=u'')
4911 alt_token = Column('alt_token', String(1024), default=u'')
4891 token_secret = Column('token_secret', String(1024), default=u'')
4912 token_secret = Column('token_secret', String(1024), default=u'')
4892
4913
4893 @classmethod
4914 @classmethod
4894 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4915 def by_external_id_and_provider(cls, external_id, provider_name, local_user_id=None):
4895 """
4916 """
4896 Returns ExternalIdentity instance based on search params
4917 Returns ExternalIdentity instance based on search params
4897
4918
4898 :param external_id:
4919 :param external_id:
4899 :param provider_name:
4920 :param provider_name:
4900 :return: ExternalIdentity
4921 :return: ExternalIdentity
4901 """
4922 """
4902 query = cls.query()
4923 query = cls.query()
4903 query = query.filter(cls.external_id == external_id)
4924 query = query.filter(cls.external_id == external_id)
4904 query = query.filter(cls.provider_name == provider_name)
4925 query = query.filter(cls.provider_name == provider_name)
4905 if local_user_id:
4926 if local_user_id:
4906 query = query.filter(cls.local_user_id == local_user_id)
4927 query = query.filter(cls.local_user_id == local_user_id)
4907 return query.first()
4928 return query.first()
4908
4929
4909 @classmethod
4930 @classmethod
4910 def user_by_external_id_and_provider(cls, external_id, provider_name):
4931 def user_by_external_id_and_provider(cls, external_id, provider_name):
4911 """
4932 """
4912 Returns User instance based on search params
4933 Returns User instance based on search params
4913
4934
4914 :param external_id:
4935 :param external_id:
4915 :param provider_name:
4936 :param provider_name:
4916 :return: User
4937 :return: User
4917 """
4938 """
4918 query = User.query()
4939 query = User.query()
4919 query = query.filter(cls.external_id == external_id)
4940 query = query.filter(cls.external_id == external_id)
4920 query = query.filter(cls.provider_name == provider_name)
4941 query = query.filter(cls.provider_name == provider_name)
4921 query = query.filter(User.user_id == cls.local_user_id)
4942 query = query.filter(User.user_id == cls.local_user_id)
4922 return query.first()
4943 return query.first()
4923
4944
4924 @classmethod
4945 @classmethod
4925 def by_local_user_id(cls, local_user_id):
4946 def by_local_user_id(cls, local_user_id):
4926 """
4947 """
4927 Returns all tokens for user
4948 Returns all tokens for user
4928
4949
4929 :param local_user_id:
4950 :param local_user_id:
4930 :return: ExternalIdentity
4951 :return: ExternalIdentity
4931 """
4952 """
4932 query = cls.query()
4953 query = cls.query()
4933 query = query.filter(cls.local_user_id == local_user_id)
4954 query = query.filter(cls.local_user_id == local_user_id)
4934 return query
4955 return query
4935
4956
4936 @classmethod
4957 @classmethod
4937 def load_provider_plugin(cls, plugin_id):
4958 def load_provider_plugin(cls, plugin_id):
4938 from rhodecode.authentication.base import loadplugin
4959 from rhodecode.authentication.base import loadplugin
4939 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4960 _plugin_id = 'egg:rhodecode-enterprise-ee#{}'.format(plugin_id)
4940 auth_plugin = loadplugin(_plugin_id)
4961 auth_plugin = loadplugin(_plugin_id)
4941 return auth_plugin
4962 return auth_plugin
4942
4963
4943
4964
4944 class Integration(Base, BaseModel):
4965 class Integration(Base, BaseModel):
4945 __tablename__ = 'integrations'
4966 __tablename__ = 'integrations'
4946 __table_args__ = (
4967 __table_args__ = (
4947 base_table_args
4968 base_table_args
4948 )
4969 )
4949
4970
4950 integration_id = Column('integration_id', Integer(), primary_key=True)
4971 integration_id = Column('integration_id', Integer(), primary_key=True)
4951 integration_type = Column('integration_type', String(255))
4972 integration_type = Column('integration_type', String(255))
4952 enabled = Column('enabled', Boolean(), nullable=False)
4973 enabled = Column('enabled', Boolean(), nullable=False)
4953 name = Column('name', String(255), nullable=False)
4974 name = Column('name', String(255), nullable=False)
4954 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4975 child_repos_only = Column('child_repos_only', Boolean(), nullable=False,
4955 default=False)
4976 default=False)
4956
4977
4957 settings = Column(
4978 settings = Column(
4958 'settings_json', MutationObj.as_mutable(
4979 'settings_json', MutationObj.as_mutable(
4959 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4980 JsonType(dialect_map=dict(mysql=UnicodeText(16384)))))
4960 repo_id = Column(
4981 repo_id = Column(
4961 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4982 'repo_id', Integer(), ForeignKey('repositories.repo_id'),
4962 nullable=True, unique=None, default=None)
4983 nullable=True, unique=None, default=None)
4963 repo = relationship('Repository', lazy='joined')
4984 repo = relationship('Repository', lazy='joined')
4964
4985
4965 repo_group_id = Column(
4986 repo_group_id = Column(
4966 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4987 'repo_group_id', Integer(), ForeignKey('groups.group_id'),
4967 nullable=True, unique=None, default=None)
4988 nullable=True, unique=None, default=None)
4968 repo_group = relationship('RepoGroup', lazy='joined')
4989 repo_group = relationship('RepoGroup', lazy='joined')
4969
4990
4970 @property
4991 @property
4971 def scope(self):
4992 def scope(self):
4972 if self.repo:
4993 if self.repo:
4973 return repr(self.repo)
4994 return repr(self.repo)
4974 if self.repo_group:
4995 if self.repo_group:
4975 if self.child_repos_only:
4996 if self.child_repos_only:
4976 return repr(self.repo_group) + ' (child repos only)'
4997 return repr(self.repo_group) + ' (child repos only)'
4977 else:
4998 else:
4978 return repr(self.repo_group) + ' (recursive)'
4999 return repr(self.repo_group) + ' (recursive)'
4979 if self.child_repos_only:
5000 if self.child_repos_only:
4980 return 'root_repos'
5001 return 'root_repos'
4981 return 'global'
5002 return 'global'
4982
5003
4983 def __repr__(self):
5004 def __repr__(self):
4984 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
5005 return '<Integration(%r, %r)>' % (self.integration_type, self.scope)
4985
5006
4986
5007
4987 class RepoReviewRuleUser(Base, BaseModel):
5008 class RepoReviewRuleUser(Base, BaseModel):
4988 __tablename__ = 'repo_review_rules_users'
5009 __tablename__ = 'repo_review_rules_users'
4989 __table_args__ = (
5010 __table_args__ = (
4990 base_table_args
5011 base_table_args
4991 )
5012 )
4992 ROLE_REVIEWER = u'reviewer'
5013 ROLE_REVIEWER = u'reviewer'
4993 ROLE_OBSERVER = u'observer'
5014 ROLE_OBSERVER = u'observer'
4994 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5015 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
4995
5016
4996 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
5017 repo_review_rule_user_id = Column('repo_review_rule_user_id', Integer(), primary_key=True)
4997 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5018 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
4998 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
5019 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False)
4999 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5020 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5000 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5021 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5001 user = relationship('User')
5022 user = relationship('User')
5002
5023
5003 def rule_data(self):
5024 def rule_data(self):
5004 return {
5025 return {
5005 'mandatory': self.mandatory,
5026 'mandatory': self.mandatory,
5006 'role': self.role,
5027 'role': self.role,
5007 }
5028 }
5008
5029
5009
5030
5010 class RepoReviewRuleUserGroup(Base, BaseModel):
5031 class RepoReviewRuleUserGroup(Base, BaseModel):
5011 __tablename__ = 'repo_review_rules_users_groups'
5032 __tablename__ = 'repo_review_rules_users_groups'
5012 __table_args__ = (
5033 __table_args__ = (
5013 base_table_args
5034 base_table_args
5014 )
5035 )
5015
5036
5016 VOTE_RULE_ALL = -1
5037 VOTE_RULE_ALL = -1
5017 ROLE_REVIEWER = u'reviewer'
5038 ROLE_REVIEWER = u'reviewer'
5018 ROLE_OBSERVER = u'observer'
5039 ROLE_OBSERVER = u'observer'
5019 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5040 ROLES = [ROLE_REVIEWER, ROLE_OBSERVER]
5020
5041
5021 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5042 repo_review_rule_users_group_id = Column('repo_review_rule_users_group_id', Integer(), primary_key=True)
5022 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5043 repo_review_rule_id = Column("repo_review_rule_id", Integer(), ForeignKey('repo_review_rules.repo_review_rule_id'))
5023 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5044 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False)
5024 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5045 mandatory = Column("mandatory", Boolean(), nullable=False, default=False)
5025 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5046 role = Column('role', Unicode(255), nullable=True, default=ROLE_REVIEWER)
5026 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5047 vote_rule = Column("vote_rule", Integer(), nullable=True, default=VOTE_RULE_ALL)
5027 users_group = relationship('UserGroup')
5048 users_group = relationship('UserGroup')
5028
5049
5029 def rule_data(self):
5050 def rule_data(self):
5030 return {
5051 return {
5031 'mandatory': self.mandatory,
5052 'mandatory': self.mandatory,
5032 'role': self.role,
5053 'role': self.role,
5033 'vote_rule': self.vote_rule
5054 'vote_rule': self.vote_rule
5034 }
5055 }
5035
5056
5036 @property
5057 @property
5037 def vote_rule_label(self):
5058 def vote_rule_label(self):
5038 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5059 if not self.vote_rule or self.vote_rule == self.VOTE_RULE_ALL:
5039 return 'all must vote'
5060 return 'all must vote'
5040 else:
5061 else:
5041 return 'min. vote {}'.format(self.vote_rule)
5062 return 'min. vote {}'.format(self.vote_rule)
5042
5063
5043
5064
5044 class RepoReviewRule(Base, BaseModel):
5065 class RepoReviewRule(Base, BaseModel):
5045 __tablename__ = 'repo_review_rules'
5066 __tablename__ = 'repo_review_rules'
5046 __table_args__ = (
5067 __table_args__ = (
5047 base_table_args
5068 base_table_args
5048 )
5069 )
5049
5070
5050 repo_review_rule_id = Column(
5071 repo_review_rule_id = Column(
5051 'repo_review_rule_id', Integer(), primary_key=True)
5072 'repo_review_rule_id', Integer(), primary_key=True)
5052 repo_id = Column(
5073 repo_id = Column(
5053 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5074 "repo_id", Integer(), ForeignKey('repositories.repo_id'))
5054 repo = relationship('Repository', backref='review_rules')
5075 repo = relationship('Repository', backref='review_rules')
5055
5076
5056 review_rule_name = Column('review_rule_name', String(255))
5077 review_rule_name = Column('review_rule_name', String(255))
5057 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5078 _branch_pattern = Column("branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5058 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5079 _target_branch_pattern = Column("target_branch_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5059 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5080 _file_pattern = Column("file_pattern", UnicodeText().with_variant(UnicodeText(255), 'mysql'), default=u'*') # glob
5060
5081
5061 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5082 use_authors_for_review = Column("use_authors_for_review", Boolean(), nullable=False, default=False)
5062
5083
5063 # Legacy fields, just for backward compat
5084 # Legacy fields, just for backward compat
5064 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5085 _forbid_author_to_review = Column("forbid_author_to_review", Boolean(), nullable=False, default=False)
5065 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5086 _forbid_commit_author_to_review = Column("forbid_commit_author_to_review", Boolean(), nullable=False, default=False)
5066
5087
5067 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5088 pr_author = Column("pr_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5068 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5089 commit_author = Column("commit_author", UnicodeText().with_variant(UnicodeText(255), 'mysql'), nullable=True)
5069
5090
5070 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5091 forbid_adding_reviewers = Column("forbid_adding_reviewers", Boolean(), nullable=False, default=False)
5071
5092
5072 rule_users = relationship('RepoReviewRuleUser')
5093 rule_users = relationship('RepoReviewRuleUser')
5073 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5094 rule_user_groups = relationship('RepoReviewRuleUserGroup')
5074
5095
5075 def _validate_pattern(self, value):
5096 def _validate_pattern(self, value):
5076 re.compile('^' + glob2re(value) + '$')
5097 re.compile('^' + glob2re(value) + '$')
5077
5098
5078 @hybrid_property
5099 @hybrid_property
5079 def source_branch_pattern(self):
5100 def source_branch_pattern(self):
5080 return self._branch_pattern or '*'
5101 return self._branch_pattern or '*'
5081
5102
5082 @source_branch_pattern.setter
5103 @source_branch_pattern.setter
5083 def source_branch_pattern(self, value):
5104 def source_branch_pattern(self, value):
5084 self._validate_pattern(value)
5105 self._validate_pattern(value)
5085 self._branch_pattern = value or '*'
5106 self._branch_pattern = value or '*'
5086
5107
5087 @hybrid_property
5108 @hybrid_property
5088 def target_branch_pattern(self):
5109 def target_branch_pattern(self):
5089 return self._target_branch_pattern or '*'
5110 return self._target_branch_pattern or '*'
5090
5111
5091 @target_branch_pattern.setter
5112 @target_branch_pattern.setter
5092 def target_branch_pattern(self, value):
5113 def target_branch_pattern(self, value):
5093 self._validate_pattern(value)
5114 self._validate_pattern(value)
5094 self._target_branch_pattern = value or '*'
5115 self._target_branch_pattern = value or '*'
5095
5116
5096 @hybrid_property
5117 @hybrid_property
5097 def file_pattern(self):
5118 def file_pattern(self):
5098 return self._file_pattern or '*'
5119 return self._file_pattern or '*'
5099
5120
5100 @file_pattern.setter
5121 @file_pattern.setter
5101 def file_pattern(self, value):
5122 def file_pattern(self, value):
5102 self._validate_pattern(value)
5123 self._validate_pattern(value)
5103 self._file_pattern = value or '*'
5124 self._file_pattern = value or '*'
5104
5125
5105 @hybrid_property
5126 @hybrid_property
5106 def forbid_pr_author_to_review(self):
5127 def forbid_pr_author_to_review(self):
5107 return self.pr_author == 'forbid_pr_author'
5128 return self.pr_author == 'forbid_pr_author'
5108
5129
5109 @hybrid_property
5130 @hybrid_property
5110 def include_pr_author_to_review(self):
5131 def include_pr_author_to_review(self):
5111 return self.pr_author == 'include_pr_author'
5132 return self.pr_author == 'include_pr_author'
5112
5133
5113 @hybrid_property
5134 @hybrid_property
5114 def forbid_commit_author_to_review(self):
5135 def forbid_commit_author_to_review(self):
5115 return self.commit_author == 'forbid_commit_author'
5136 return self.commit_author == 'forbid_commit_author'
5116
5137
5117 @hybrid_property
5138 @hybrid_property
5118 def include_commit_author_to_review(self):
5139 def include_commit_author_to_review(self):
5119 return self.commit_author == 'include_commit_author'
5140 return self.commit_author == 'include_commit_author'
5120
5141
5121 def matches(self, source_branch, target_branch, files_changed):
5142 def matches(self, source_branch, target_branch, files_changed):
5122 """
5143 """
5123 Check if this review rule matches a branch/files in a pull request
5144 Check if this review rule matches a branch/files in a pull request
5124
5145
5125 :param source_branch: source branch name for the commit
5146 :param source_branch: source branch name for the commit
5126 :param target_branch: target branch name for the commit
5147 :param target_branch: target branch name for the commit
5127 :param files_changed: list of file paths changed in the pull request
5148 :param files_changed: list of file paths changed in the pull request
5128 """
5149 """
5129
5150
5130 source_branch = source_branch or ''
5151 source_branch = source_branch or ''
5131 target_branch = target_branch or ''
5152 target_branch = target_branch or ''
5132 files_changed = files_changed or []
5153 files_changed = files_changed or []
5133
5154
5134 branch_matches = True
5155 branch_matches = True
5135 if source_branch or target_branch:
5156 if source_branch or target_branch:
5136 if self.source_branch_pattern == '*':
5157 if self.source_branch_pattern == '*':
5137 source_branch_match = True
5158 source_branch_match = True
5138 else:
5159 else:
5139 if self.source_branch_pattern.startswith('re:'):
5160 if self.source_branch_pattern.startswith('re:'):
5140 source_pattern = self.source_branch_pattern[3:]
5161 source_pattern = self.source_branch_pattern[3:]
5141 else:
5162 else:
5142 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5163 source_pattern = '^' + glob2re(self.source_branch_pattern) + '$'
5143 source_branch_regex = re.compile(source_pattern)
5164 source_branch_regex = re.compile(source_pattern)
5144 source_branch_match = bool(source_branch_regex.search(source_branch))
5165 source_branch_match = bool(source_branch_regex.search(source_branch))
5145 if self.target_branch_pattern == '*':
5166 if self.target_branch_pattern == '*':
5146 target_branch_match = True
5167 target_branch_match = True
5147 else:
5168 else:
5148 if self.target_branch_pattern.startswith('re:'):
5169 if self.target_branch_pattern.startswith('re:'):
5149 target_pattern = self.target_branch_pattern[3:]
5170 target_pattern = self.target_branch_pattern[3:]
5150 else:
5171 else:
5151 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5172 target_pattern = '^' + glob2re(self.target_branch_pattern) + '$'
5152 target_branch_regex = re.compile(target_pattern)
5173 target_branch_regex = re.compile(target_pattern)
5153 target_branch_match = bool(target_branch_regex.search(target_branch))
5174 target_branch_match = bool(target_branch_regex.search(target_branch))
5154
5175
5155 branch_matches = source_branch_match and target_branch_match
5176 branch_matches = source_branch_match and target_branch_match
5156
5177
5157 files_matches = True
5178 files_matches = True
5158 if self.file_pattern != '*':
5179 if self.file_pattern != '*':
5159 files_matches = False
5180 files_matches = False
5160 if self.file_pattern.startswith('re:'):
5181 if self.file_pattern.startswith('re:'):
5161 file_pattern = self.file_pattern[3:]
5182 file_pattern = self.file_pattern[3:]
5162 else:
5183 else:
5163 file_pattern = glob2re(self.file_pattern)
5184 file_pattern = glob2re(self.file_pattern)
5164 file_regex = re.compile(file_pattern)
5185 file_regex = re.compile(file_pattern)
5165 for file_data in files_changed:
5186 for file_data in files_changed:
5166 filename = file_data.get('filename')
5187 filename = file_data.get('filename')
5167
5188
5168 if file_regex.search(filename):
5189 if file_regex.search(filename):
5169 files_matches = True
5190 files_matches = True
5170 break
5191 break
5171
5192
5172 return branch_matches and files_matches
5193 return branch_matches and files_matches
5173
5194
5174 @property
5195 @property
5175 def review_users(self):
5196 def review_users(self):
5176 """ Returns the users which this rule applies to """
5197 """ Returns the users which this rule applies to """
5177
5198
5178 users = collections.OrderedDict()
5199 users = collections.OrderedDict()
5179
5200
5180 for rule_user in self.rule_users:
5201 for rule_user in self.rule_users:
5181 if rule_user.user.active:
5202 if rule_user.user.active:
5182 if rule_user.user not in users:
5203 if rule_user.user not in users:
5183 users[rule_user.user.username] = {
5204 users[rule_user.user.username] = {
5184 'user': rule_user.user,
5205 'user': rule_user.user,
5185 'source': 'user',
5206 'source': 'user',
5186 'source_data': {},
5207 'source_data': {},
5187 'data': rule_user.rule_data()
5208 'data': rule_user.rule_data()
5188 }
5209 }
5189
5210
5190 for rule_user_group in self.rule_user_groups:
5211 for rule_user_group in self.rule_user_groups:
5191 source_data = {
5212 source_data = {
5192 'user_group_id': rule_user_group.users_group.users_group_id,
5213 'user_group_id': rule_user_group.users_group.users_group_id,
5193 'name': rule_user_group.users_group.users_group_name,
5214 'name': rule_user_group.users_group.users_group_name,
5194 'members': len(rule_user_group.users_group.members)
5215 'members': len(rule_user_group.users_group.members)
5195 }
5216 }
5196 for member in rule_user_group.users_group.members:
5217 for member in rule_user_group.users_group.members:
5197 if member.user.active:
5218 if member.user.active:
5198 key = member.user.username
5219 key = member.user.username
5199 if key in users:
5220 if key in users:
5200 # skip this member as we have him already
5221 # skip this member as we have him already
5201 # this prevents from override the "first" matched
5222 # this prevents from override the "first" matched
5202 # users with duplicates in multiple groups
5223 # users with duplicates in multiple groups
5203 continue
5224 continue
5204
5225
5205 users[key] = {
5226 users[key] = {
5206 'user': member.user,
5227 'user': member.user,
5207 'source': 'user_group',
5228 'source': 'user_group',
5208 'source_data': source_data,
5229 'source_data': source_data,
5209 'data': rule_user_group.rule_data()
5230 'data': rule_user_group.rule_data()
5210 }
5231 }
5211
5232
5212 return users
5233 return users
5213
5234
5214 def user_group_vote_rule(self, user_id):
5235 def user_group_vote_rule(self, user_id):
5215
5236
5216 rules = []
5237 rules = []
5217 if not self.rule_user_groups:
5238 if not self.rule_user_groups:
5218 return rules
5239 return rules
5219
5240
5220 for user_group in self.rule_user_groups:
5241 for user_group in self.rule_user_groups:
5221 user_group_members = [x.user_id for x in user_group.users_group.members]
5242 user_group_members = [x.user_id for x in user_group.users_group.members]
5222 if user_id in user_group_members:
5243 if user_id in user_group_members:
5223 rules.append(user_group)
5244 rules.append(user_group)
5224 return rules
5245 return rules
5225
5246
5226 def __repr__(self):
5247 def __repr__(self):
5227 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5248 return '<RepoReviewerRule(id=%r, repo=%r)>' % (
5228 self.repo_review_rule_id, self.repo)
5249 self.repo_review_rule_id, self.repo)
5229
5250
5230
5251
5231 class ScheduleEntry(Base, BaseModel):
5252 class ScheduleEntry(Base, BaseModel):
5232 __tablename__ = 'schedule_entries'
5253 __tablename__ = 'schedule_entries'
5233 __table_args__ = (
5254 __table_args__ = (
5234 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5255 UniqueConstraint('schedule_name', name='s_schedule_name_idx'),
5235 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5256 UniqueConstraint('task_uid', name='s_task_uid_idx'),
5236 base_table_args,
5257 base_table_args,
5237 )
5258 )
5238
5259
5239 schedule_types = ['crontab', 'timedelta', 'integer']
5260 schedule_types = ['crontab', 'timedelta', 'integer']
5240 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5261 schedule_entry_id = Column('schedule_entry_id', Integer(), primary_key=True)
5241
5262
5242 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5263 schedule_name = Column("schedule_name", String(255), nullable=False, unique=None, default=None)
5243 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5264 schedule_description = Column("schedule_description", String(10000), nullable=True, unique=None, default=None)
5244 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5265 schedule_enabled = Column("schedule_enabled", Boolean(), nullable=False, unique=None, default=True)
5245
5266
5246 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5267 _schedule_type = Column("schedule_type", String(255), nullable=False, unique=None, default=None)
5247 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5268 schedule_definition = Column('schedule_definition_json', MutationObj.as_mutable(JsonType(default=lambda: "", dialect_map=dict(mysql=LONGTEXT()))))
5248
5269
5249 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5270 schedule_last_run = Column('schedule_last_run', DateTime(timezone=False), nullable=True, unique=None, default=None)
5250 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5271 schedule_total_run_count = Column('schedule_total_run_count', Integer(), nullable=True, unique=None, default=0)
5251
5272
5252 # task
5273 # task
5253 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5274 task_uid = Column("task_uid", String(255), nullable=False, unique=None, default=None)
5254 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5275 task_dot_notation = Column("task_dot_notation", String(4096), nullable=False, unique=None, default=None)
5255 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5276 task_args = Column('task_args_json', MutationObj.as_mutable(JsonType(default=list, dialect_map=dict(mysql=LONGTEXT()))))
5256 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5277 task_kwargs = Column('task_kwargs_json', MutationObj.as_mutable(JsonType(default=dict, dialect_map=dict(mysql=LONGTEXT()))))
5257
5278
5258 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5279 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5259 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5280 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=None)
5260
5281
5261 @hybrid_property
5282 @hybrid_property
5262 def schedule_type(self):
5283 def schedule_type(self):
5263 return self._schedule_type
5284 return self._schedule_type
5264
5285
5265 @schedule_type.setter
5286 @schedule_type.setter
5266 def schedule_type(self, val):
5287 def schedule_type(self, val):
5267 if val not in self.schedule_types:
5288 if val not in self.schedule_types:
5268 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5289 raise ValueError('Value must be on of `{}` and got `{}`'.format(
5269 val, self.schedule_type))
5290 val, self.schedule_type))
5270
5291
5271 self._schedule_type = val
5292 self._schedule_type = val
5272
5293
5273 @classmethod
5294 @classmethod
5274 def get_uid(cls, obj):
5295 def get_uid(cls, obj):
5275 args = obj.task_args
5296 args = obj.task_args
5276 kwargs = obj.task_kwargs
5297 kwargs = obj.task_kwargs
5277 if isinstance(args, JsonRaw):
5298 if isinstance(args, JsonRaw):
5278 try:
5299 try:
5279 args = json.loads(args)
5300 args = json.loads(args)
5280 except ValueError:
5301 except ValueError:
5281 args = tuple()
5302 args = tuple()
5282
5303
5283 if isinstance(kwargs, JsonRaw):
5304 if isinstance(kwargs, JsonRaw):
5284 try:
5305 try:
5285 kwargs = json.loads(kwargs)
5306 kwargs = json.loads(kwargs)
5286 except ValueError:
5307 except ValueError:
5287 kwargs = dict()
5308 kwargs = dict()
5288
5309
5289 dot_notation = obj.task_dot_notation
5310 dot_notation = obj.task_dot_notation
5290 val = '.'.join(map(safe_str, [
5311 val = '.'.join(map(safe_str, [
5291 sorted(dot_notation), args, sorted(kwargs.items())]))
5312 sorted(dot_notation), args, sorted(kwargs.items())]))
5292 return hashlib.sha1(val).hexdigest()
5313 return hashlib.sha1(val).hexdigest()
5293
5314
5294 @classmethod
5315 @classmethod
5295 def get_by_schedule_name(cls, schedule_name):
5316 def get_by_schedule_name(cls, schedule_name):
5296 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5317 return cls.query().filter(cls.schedule_name == schedule_name).scalar()
5297
5318
5298 @classmethod
5319 @classmethod
5299 def get_by_schedule_id(cls, schedule_id):
5320 def get_by_schedule_id(cls, schedule_id):
5300 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5321 return cls.query().filter(cls.schedule_entry_id == schedule_id).scalar()
5301
5322
5302 @property
5323 @property
5303 def task(self):
5324 def task(self):
5304 return self.task_dot_notation
5325 return self.task_dot_notation
5305
5326
5306 @property
5327 @property
5307 def schedule(self):
5328 def schedule(self):
5308 from rhodecode.lib.celerylib.utils import raw_2_schedule
5329 from rhodecode.lib.celerylib.utils import raw_2_schedule
5309 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5330 schedule = raw_2_schedule(self.schedule_definition, self.schedule_type)
5310 return schedule
5331 return schedule
5311
5332
5312 @property
5333 @property
5313 def args(self):
5334 def args(self):
5314 try:
5335 try:
5315 return list(self.task_args or [])
5336 return list(self.task_args or [])
5316 except ValueError:
5337 except ValueError:
5317 return list()
5338 return list()
5318
5339
5319 @property
5340 @property
5320 def kwargs(self):
5341 def kwargs(self):
5321 try:
5342 try:
5322 return dict(self.task_kwargs or {})
5343 return dict(self.task_kwargs or {})
5323 except ValueError:
5344 except ValueError:
5324 return dict()
5345 return dict()
5325
5346
5326 def _as_raw(self, val, indent=None):
5347 def _as_raw(self, val, indent=None):
5327 if hasattr(val, 'de_coerce'):
5348 if hasattr(val, 'de_coerce'):
5328 val = val.de_coerce()
5349 val = val.de_coerce()
5329 if val:
5350 if val:
5330 val = json.dumps(val, indent=indent, sort_keys=True)
5351 val = json.dumps(val, indent=indent, sort_keys=True)
5331
5352
5332 return val
5353 return val
5333
5354
5334 @property
5355 @property
5335 def schedule_definition_raw(self):
5356 def schedule_definition_raw(self):
5336 return self._as_raw(self.schedule_definition)
5357 return self._as_raw(self.schedule_definition)
5337
5358
5338 def args_raw(self, indent=None):
5359 def args_raw(self, indent=None):
5339 return self._as_raw(self.task_args, indent)
5360 return self._as_raw(self.task_args, indent)
5340
5361
5341 def kwargs_raw(self, indent=None):
5362 def kwargs_raw(self, indent=None):
5342 return self._as_raw(self.task_kwargs, indent)
5363 return self._as_raw(self.task_kwargs, indent)
5343
5364
5344 def __repr__(self):
5365 def __repr__(self):
5345 return '<DB:ScheduleEntry({}:{})>'.format(
5366 return '<DB:ScheduleEntry({}:{})>'.format(
5346 self.schedule_entry_id, self.schedule_name)
5367 self.schedule_entry_id, self.schedule_name)
5347
5368
5348
5369
5349 @event.listens_for(ScheduleEntry, 'before_update')
5370 @event.listens_for(ScheduleEntry, 'before_update')
5350 def update_task_uid(mapper, connection, target):
5371 def update_task_uid(mapper, connection, target):
5351 target.task_uid = ScheduleEntry.get_uid(target)
5372 target.task_uid = ScheduleEntry.get_uid(target)
5352
5373
5353
5374
5354 @event.listens_for(ScheduleEntry, 'before_insert')
5375 @event.listens_for(ScheduleEntry, 'before_insert')
5355 def set_task_uid(mapper, connection, target):
5376 def set_task_uid(mapper, connection, target):
5356 target.task_uid = ScheduleEntry.get_uid(target)
5377 target.task_uid = ScheduleEntry.get_uid(target)
5357
5378
5358
5379
5359 class _BaseBranchPerms(BaseModel):
5380 class _BaseBranchPerms(BaseModel):
5360 @classmethod
5381 @classmethod
5361 def compute_hash(cls, value):
5382 def compute_hash(cls, value):
5362 return sha1_safe(value)
5383 return sha1_safe(value)
5363
5384
5364 @hybrid_property
5385 @hybrid_property
5365 def branch_pattern(self):
5386 def branch_pattern(self):
5366 return self._branch_pattern or '*'
5387 return self._branch_pattern or '*'
5367
5388
5368 @hybrid_property
5389 @hybrid_property
5369 def branch_hash(self):
5390 def branch_hash(self):
5370 return self._branch_hash
5391 return self._branch_hash
5371
5392
5372 def _validate_glob(self, value):
5393 def _validate_glob(self, value):
5373 re.compile('^' + glob2re(value) + '$')
5394 re.compile('^' + glob2re(value) + '$')
5374
5395
5375 @branch_pattern.setter
5396 @branch_pattern.setter
5376 def branch_pattern(self, value):
5397 def branch_pattern(self, value):
5377 self._validate_glob(value)
5398 self._validate_glob(value)
5378 self._branch_pattern = value or '*'
5399 self._branch_pattern = value or '*'
5379 # set the Hash when setting the branch pattern
5400 # set the Hash when setting the branch pattern
5380 self._branch_hash = self.compute_hash(self._branch_pattern)
5401 self._branch_hash = self.compute_hash(self._branch_pattern)
5381
5402
5382 def matches(self, branch):
5403 def matches(self, branch):
5383 """
5404 """
5384 Check if this the branch matches entry
5405 Check if this the branch matches entry
5385
5406
5386 :param branch: branch name for the commit
5407 :param branch: branch name for the commit
5387 """
5408 """
5388
5409
5389 branch = branch or ''
5410 branch = branch or ''
5390
5411
5391 branch_matches = True
5412 branch_matches = True
5392 if branch:
5413 if branch:
5393 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5414 branch_regex = re.compile('^' + glob2re(self.branch_pattern) + '$')
5394 branch_matches = bool(branch_regex.search(branch))
5415 branch_matches = bool(branch_regex.search(branch))
5395
5416
5396 return branch_matches
5417 return branch_matches
5397
5418
5398
5419
5399 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5420 class UserToRepoBranchPermission(Base, _BaseBranchPerms):
5400 __tablename__ = 'user_to_repo_branch_permissions'
5421 __tablename__ = 'user_to_repo_branch_permissions'
5401 __table_args__ = (
5422 __table_args__ = (
5402 base_table_args
5423 base_table_args
5403 )
5424 )
5404
5425
5405 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5426 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5406
5427
5407 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5428 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5408 repo = relationship('Repository', backref='user_branch_perms')
5429 repo = relationship('Repository', backref='user_branch_perms')
5409
5430
5410 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5431 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5411 permission = relationship('Permission')
5432 permission = relationship('Permission')
5412
5433
5413 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5434 rule_to_perm_id = Column('rule_to_perm_id', Integer(), ForeignKey('repo_to_perm.repo_to_perm_id'), nullable=False, unique=None, default=None)
5414 user_repo_to_perm = relationship('UserRepoToPerm')
5435 user_repo_to_perm = relationship('UserRepoToPerm')
5415
5436
5416 rule_order = Column('rule_order', Integer(), nullable=False)
5437 rule_order = Column('rule_order', Integer(), nullable=False)
5417 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5438 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5418 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5439 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5419
5440
5420 def __unicode__(self):
5441 def __unicode__(self):
5421 return u'<UserBranchPermission(%s => %r)>' % (
5442 return u'<UserBranchPermission(%s => %r)>' % (
5422 self.user_repo_to_perm, self.branch_pattern)
5443 self.user_repo_to_perm, self.branch_pattern)
5423
5444
5424
5445
5425 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5446 class UserGroupToRepoBranchPermission(Base, _BaseBranchPerms):
5426 __tablename__ = 'user_group_to_repo_branch_permissions'
5447 __tablename__ = 'user_group_to_repo_branch_permissions'
5427 __table_args__ = (
5448 __table_args__ = (
5428 base_table_args
5449 base_table_args
5429 )
5450 )
5430
5451
5431 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5452 branch_rule_id = Column('branch_rule_id', Integer(), primary_key=True)
5432
5453
5433 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5454 repository_id = Column('repository_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
5434 repo = relationship('Repository', backref='user_group_branch_perms')
5455 repo = relationship('Repository', backref='user_group_branch_perms')
5435
5456
5436 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5457 permission_id = Column('permission_id', Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
5437 permission = relationship('Permission')
5458 permission = relationship('Permission')
5438
5459
5439 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)
5460 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)
5440 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5461 user_group_repo_to_perm = relationship('UserGroupRepoToPerm')
5441
5462
5442 rule_order = Column('rule_order', Integer(), nullable=False)
5463 rule_order = Column('rule_order', Integer(), nullable=False)
5443 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5464 _branch_pattern = Column('branch_pattern', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), default=u'*') # glob
5444 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5465 _branch_hash = Column('branch_hash', UnicodeText().with_variant(UnicodeText(2048), 'mysql'))
5445
5466
5446 def __unicode__(self):
5467 def __unicode__(self):
5447 return u'<UserBranchPermission(%s => %r)>' % (
5468 return u'<UserBranchPermission(%s => %r)>' % (
5448 self.user_group_repo_to_perm, self.branch_pattern)
5469 self.user_group_repo_to_perm, self.branch_pattern)
5449
5470
5450
5471
5451 class UserBookmark(Base, BaseModel):
5472 class UserBookmark(Base, BaseModel):
5452 __tablename__ = 'user_bookmarks'
5473 __tablename__ = 'user_bookmarks'
5453 __table_args__ = (
5474 __table_args__ = (
5454 UniqueConstraint('user_id', 'bookmark_repo_id'),
5475 UniqueConstraint('user_id', 'bookmark_repo_id'),
5455 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5476 UniqueConstraint('user_id', 'bookmark_repo_group_id'),
5456 UniqueConstraint('user_id', 'bookmark_position'),
5477 UniqueConstraint('user_id', 'bookmark_position'),
5457 base_table_args
5478 base_table_args
5458 )
5479 )
5459
5480
5460 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5481 user_bookmark_id = Column("user_bookmark_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
5461 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5482 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
5462 position = Column("bookmark_position", Integer(), nullable=False)
5483 position = Column("bookmark_position", Integer(), nullable=False)
5463 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5484 title = Column("bookmark_title", String(255), nullable=True, unique=None, default=None)
5464 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5485 redirect_url = Column("bookmark_redirect_url", String(10240), nullable=True, unique=None, default=None)
5465 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5486 created_on = Column("created_on", DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5466
5487
5467 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5488 bookmark_repo_id = Column("bookmark_repo_id", Integer(), ForeignKey("repositories.repo_id"), nullable=True, unique=None, default=None)
5468 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5489 bookmark_repo_group_id = Column("bookmark_repo_group_id", Integer(), ForeignKey("groups.group_id"), nullable=True, unique=None, default=None)
5469
5490
5470 user = relationship("User")
5491 user = relationship("User")
5471
5492
5472 repository = relationship("Repository")
5493 repository = relationship("Repository")
5473 repository_group = relationship("RepoGroup")
5494 repository_group = relationship("RepoGroup")
5474
5495
5475 @classmethod
5496 @classmethod
5476 def get_by_position_for_user(cls, position, user_id):
5497 def get_by_position_for_user(cls, position, user_id):
5477 return cls.query() \
5498 return cls.query() \
5478 .filter(UserBookmark.user_id == user_id) \
5499 .filter(UserBookmark.user_id == user_id) \
5479 .filter(UserBookmark.position == position).scalar()
5500 .filter(UserBookmark.position == position).scalar()
5480
5501
5481 @classmethod
5502 @classmethod
5482 def get_bookmarks_for_user(cls, user_id, cache=True):
5503 def get_bookmarks_for_user(cls, user_id, cache=True):
5483 bookmarks = cls.query() \
5504 bookmarks = cls.query() \
5484 .filter(UserBookmark.user_id == user_id) \
5505 .filter(UserBookmark.user_id == user_id) \
5485 .options(joinedload(UserBookmark.repository)) \
5506 .options(joinedload(UserBookmark.repository)) \
5486 .options(joinedload(UserBookmark.repository_group)) \
5507 .options(joinedload(UserBookmark.repository_group)) \
5487 .order_by(UserBookmark.position.asc())
5508 .order_by(UserBookmark.position.asc())
5488
5509
5489 if cache:
5510 if cache:
5490 bookmarks = bookmarks.options(
5511 bookmarks = bookmarks.options(
5491 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5512 FromCache("sql_cache_short", "get_user_{}_bookmarks".format(user_id))
5492 )
5513 )
5493
5514
5494 return bookmarks.all()
5515 return bookmarks.all()
5495
5516
5496 def __unicode__(self):
5517 def __unicode__(self):
5497 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5518 return u'<UserBookmark(%s @ %r)>' % (self.position, self.redirect_url)
5498
5519
5499
5520
5500 class FileStore(Base, BaseModel):
5521 class FileStore(Base, BaseModel):
5501 __tablename__ = 'file_store'
5522 __tablename__ = 'file_store'
5502 __table_args__ = (
5523 __table_args__ = (
5503 base_table_args
5524 base_table_args
5504 )
5525 )
5505
5526
5506 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5527 file_store_id = Column('file_store_id', Integer(), primary_key=True)
5507 file_uid = Column('file_uid', String(1024), nullable=False)
5528 file_uid = Column('file_uid', String(1024), nullable=False)
5508 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5529 file_display_name = Column('file_display_name', UnicodeText().with_variant(UnicodeText(2048), 'mysql'), nullable=True)
5509 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5530 file_description = Column('file_description', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=True)
5510 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5531 file_org_name = Column('file_org_name', UnicodeText().with_variant(UnicodeText(10240), 'mysql'), nullable=False)
5511
5532
5512 # sha256 hash
5533 # sha256 hash
5513 file_hash = Column('file_hash', String(512), nullable=False)
5534 file_hash = Column('file_hash', String(512), nullable=False)
5514 file_size = Column('file_size', BigInteger(), nullable=False)
5535 file_size = Column('file_size', BigInteger(), nullable=False)
5515
5536
5516 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5537 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
5517 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5538 accessed_on = Column('accessed_on', DateTime(timezone=False), nullable=True)
5518 accessed_count = Column('accessed_count', Integer(), default=0)
5539 accessed_count = Column('accessed_count', Integer(), default=0)
5519
5540
5520 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5541 enabled = Column('enabled', Boolean(), nullable=False, default=True)
5521
5542
5522 # if repo/repo_group reference is set, check for permissions
5543 # if repo/repo_group reference is set, check for permissions
5523 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5544 check_acl = Column('check_acl', Boolean(), nullable=False, default=True)
5524
5545
5525 # hidden defines an attachment that should be hidden from showing in artifact listing
5546 # hidden defines an attachment that should be hidden from showing in artifact listing
5526 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5547 hidden = Column('hidden', Boolean(), nullable=False, default=False)
5527
5548
5528 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5549 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
5529 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5550 upload_user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.user_id')
5530
5551
5531 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5552 file_metadata = relationship('FileStoreMetadata', lazy='joined')
5532
5553
5533 # scope limited to user, which requester have access to
5554 # scope limited to user, which requester have access to
5534 scope_user_id = Column(
5555 scope_user_id = Column(
5535 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5556 'scope_user_id', Integer(), ForeignKey('users.user_id'),
5536 nullable=True, unique=None, default=None)
5557 nullable=True, unique=None, default=None)
5537 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5558 user = relationship('User', lazy='joined', primaryjoin='User.user_id==FileStore.scope_user_id')
5538
5559
5539 # scope limited to user group, which requester have access to
5560 # scope limited to user group, which requester have access to
5540 scope_user_group_id = Column(
5561 scope_user_group_id = Column(
5541 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5562 'scope_user_group_id', Integer(), ForeignKey('users_groups.users_group_id'),
5542 nullable=True, unique=None, default=None)
5563 nullable=True, unique=None, default=None)
5543 user_group = relationship('UserGroup', lazy='joined')
5564 user_group = relationship('UserGroup', lazy='joined')
5544
5565
5545 # scope limited to repo, which requester have access to
5566 # scope limited to repo, which requester have access to
5546 scope_repo_id = Column(
5567 scope_repo_id = Column(
5547 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5568 'scope_repo_id', Integer(), ForeignKey('repositories.repo_id'),
5548 nullable=True, unique=None, default=None)
5569 nullable=True, unique=None, default=None)
5549 repo = relationship('Repository', lazy='joined')
5570 repo = relationship('Repository', lazy='joined')
5550
5571
5551 # scope limited to repo group, which requester have access to
5572 # scope limited to repo group, which requester have access to
5552 scope_repo_group_id = Column(
5573 scope_repo_group_id = Column(
5553 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5574 'scope_repo_group_id', Integer(), ForeignKey('groups.group_id'),
5554 nullable=True, unique=None, default=None)
5575 nullable=True, unique=None, default=None)
5555 repo_group = relationship('RepoGroup', lazy='joined')
5576 repo_group = relationship('RepoGroup', lazy='joined')
5556
5577
5557 @classmethod
5578 @classmethod
5558 def get_by_store_uid(cls, file_store_uid, safe=False):
5579 def get_by_store_uid(cls, file_store_uid, safe=False):
5559 if safe:
5580 if safe:
5560 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5581 return FileStore.query().filter(FileStore.file_uid == file_store_uid).first()
5561 else:
5582 else:
5562 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5583 return FileStore.query().filter(FileStore.file_uid == file_store_uid).scalar()
5563
5584
5564 @classmethod
5585 @classmethod
5565 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5586 def create(cls, file_uid, filename, file_hash, file_size, file_display_name='',
5566 file_description='', enabled=True, hidden=False, check_acl=True,
5587 file_description='', enabled=True, hidden=False, check_acl=True,
5567 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5588 user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None):
5568
5589
5569 store_entry = FileStore()
5590 store_entry = FileStore()
5570 store_entry.file_uid = file_uid
5591 store_entry.file_uid = file_uid
5571 store_entry.file_display_name = file_display_name
5592 store_entry.file_display_name = file_display_name
5572 store_entry.file_org_name = filename
5593 store_entry.file_org_name = filename
5573 store_entry.file_size = file_size
5594 store_entry.file_size = file_size
5574 store_entry.file_hash = file_hash
5595 store_entry.file_hash = file_hash
5575 store_entry.file_description = file_description
5596 store_entry.file_description = file_description
5576
5597
5577 store_entry.check_acl = check_acl
5598 store_entry.check_acl = check_acl
5578 store_entry.enabled = enabled
5599 store_entry.enabled = enabled
5579 store_entry.hidden = hidden
5600 store_entry.hidden = hidden
5580
5601
5581 store_entry.user_id = user_id
5602 store_entry.user_id = user_id
5582 store_entry.scope_user_id = scope_user_id
5603 store_entry.scope_user_id = scope_user_id
5583 store_entry.scope_repo_id = scope_repo_id
5604 store_entry.scope_repo_id = scope_repo_id
5584 store_entry.scope_repo_group_id = scope_repo_group_id
5605 store_entry.scope_repo_group_id = scope_repo_group_id
5585
5606
5586 return store_entry
5607 return store_entry
5587
5608
5588 @classmethod
5609 @classmethod
5589 def store_metadata(cls, file_store_id, args, commit=True):
5610 def store_metadata(cls, file_store_id, args, commit=True):
5590 file_store = FileStore.get(file_store_id)
5611 file_store = FileStore.get(file_store_id)
5591 if file_store is None:
5612 if file_store is None:
5592 return
5613 return
5593
5614
5594 for section, key, value, value_type in args:
5615 for section, key, value, value_type in args:
5595 has_key = FileStoreMetadata().query() \
5616 has_key = FileStoreMetadata().query() \
5596 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5617 .filter(FileStoreMetadata.file_store_id == file_store.file_store_id) \
5597 .filter(FileStoreMetadata.file_store_meta_section == section) \
5618 .filter(FileStoreMetadata.file_store_meta_section == section) \
5598 .filter(FileStoreMetadata.file_store_meta_key == key) \
5619 .filter(FileStoreMetadata.file_store_meta_key == key) \
5599 .scalar()
5620 .scalar()
5600 if has_key:
5621 if has_key:
5601 msg = 'key `{}` already defined under section `{}` for this file.'\
5622 msg = 'key `{}` already defined under section `{}` for this file.'\
5602 .format(key, section)
5623 .format(key, section)
5603 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5624 raise ArtifactMetadataDuplicate(msg, err_section=section, err_key=key)
5604
5625
5605 # NOTE(marcink): raises ArtifactMetadataBadValueType
5626 # NOTE(marcink): raises ArtifactMetadataBadValueType
5606 FileStoreMetadata.valid_value_type(value_type)
5627 FileStoreMetadata.valid_value_type(value_type)
5607
5628
5608 meta_entry = FileStoreMetadata()
5629 meta_entry = FileStoreMetadata()
5609 meta_entry.file_store = file_store
5630 meta_entry.file_store = file_store
5610 meta_entry.file_store_meta_section = section
5631 meta_entry.file_store_meta_section = section
5611 meta_entry.file_store_meta_key = key
5632 meta_entry.file_store_meta_key = key
5612 meta_entry.file_store_meta_value_type = value_type
5633 meta_entry.file_store_meta_value_type = value_type
5613 meta_entry.file_store_meta_value = value
5634 meta_entry.file_store_meta_value = value
5614
5635
5615 Session().add(meta_entry)
5636 Session().add(meta_entry)
5616
5637
5617 try:
5638 try:
5618 if commit:
5639 if commit:
5619 Session().commit()
5640 Session().commit()
5620 except IntegrityError:
5641 except IntegrityError:
5621 Session().rollback()
5642 Session().rollback()
5622 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5643 raise ArtifactMetadataDuplicate('Duplicate section/key found for this file.')
5623
5644
5624 @classmethod
5645 @classmethod
5625 def bump_access_counter(cls, file_uid, commit=True):
5646 def bump_access_counter(cls, file_uid, commit=True):
5626 FileStore().query()\
5647 FileStore().query()\
5627 .filter(FileStore.file_uid == file_uid)\
5648 .filter(FileStore.file_uid == file_uid)\
5628 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5649 .update({FileStore.accessed_count: (FileStore.accessed_count + 1),
5629 FileStore.accessed_on: datetime.datetime.now()})
5650 FileStore.accessed_on: datetime.datetime.now()})
5630 if commit:
5651 if commit:
5631 Session().commit()
5652 Session().commit()
5632
5653
5633 def __json__(self):
5654 def __json__(self):
5634 data = {
5655 data = {
5635 'filename': self.file_display_name,
5656 'filename': self.file_display_name,
5636 'filename_org': self.file_org_name,
5657 'filename_org': self.file_org_name,
5637 'file_uid': self.file_uid,
5658 'file_uid': self.file_uid,
5638 'description': self.file_description,
5659 'description': self.file_description,
5639 'hidden': self.hidden,
5660 'hidden': self.hidden,
5640 'size': self.file_size,
5661 'size': self.file_size,
5641 'created_on': self.created_on,
5662 'created_on': self.created_on,
5642 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5663 'uploaded_by': self.upload_user.get_api_data(details='basic'),
5643 'downloaded_times': self.accessed_count,
5664 'downloaded_times': self.accessed_count,
5644 'sha256': self.file_hash,
5665 'sha256': self.file_hash,
5645 'metadata': self.file_metadata,
5666 'metadata': self.file_metadata,
5646 }
5667 }
5647
5668
5648 return data
5669 return data
5649
5670
5650 def __repr__(self):
5671 def __repr__(self):
5651 return '<FileStore({})>'.format(self.file_store_id)
5672 return '<FileStore({})>'.format(self.file_store_id)
5652
5673
5653
5674
5654 class FileStoreMetadata(Base, BaseModel):
5675 class FileStoreMetadata(Base, BaseModel):
5655 __tablename__ = 'file_store_metadata'
5676 __tablename__ = 'file_store_metadata'
5656 __table_args__ = (
5677 __table_args__ = (
5657 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5678 UniqueConstraint('file_store_id', 'file_store_meta_section_hash', 'file_store_meta_key_hash'),
5658 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5679 Index('file_store_meta_section_idx', 'file_store_meta_section', mysql_length=255),
5659 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5680 Index('file_store_meta_key_idx', 'file_store_meta_key', mysql_length=255),
5660 base_table_args
5681 base_table_args
5661 )
5682 )
5662 SETTINGS_TYPES = {
5683 SETTINGS_TYPES = {
5663 'str': safe_str,
5684 'str': safe_str,
5664 'int': safe_int,
5685 'int': safe_int,
5665 'unicode': safe_unicode,
5686 'unicode': safe_unicode,
5666 'bool': str2bool,
5687 'bool': str2bool,
5667 'list': functools.partial(aslist, sep=',')
5688 'list': functools.partial(aslist, sep=',')
5668 }
5689 }
5669
5690
5670 file_store_meta_id = Column(
5691 file_store_meta_id = Column(
5671 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5692 "file_store_meta_id", Integer(), nullable=False, unique=True, default=None,
5672 primary_key=True)
5693 primary_key=True)
5673 _file_store_meta_section = Column(
5694 _file_store_meta_section = Column(
5674 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5695 "file_store_meta_section", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5675 nullable=True, unique=None, default=None)
5696 nullable=True, unique=None, default=None)
5676 _file_store_meta_section_hash = Column(
5697 _file_store_meta_section_hash = Column(
5677 "file_store_meta_section_hash", String(255),
5698 "file_store_meta_section_hash", String(255),
5678 nullable=True, unique=None, default=None)
5699 nullable=True, unique=None, default=None)
5679 _file_store_meta_key = Column(
5700 _file_store_meta_key = Column(
5680 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5701 "file_store_meta_key", UnicodeText().with_variant(UnicodeText(1024), 'mysql'),
5681 nullable=True, unique=None, default=None)
5702 nullable=True, unique=None, default=None)
5682 _file_store_meta_key_hash = Column(
5703 _file_store_meta_key_hash = Column(
5683 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5704 "file_store_meta_key_hash", String(255), nullable=True, unique=None, default=None)
5684 _file_store_meta_value = Column(
5705 _file_store_meta_value = Column(
5685 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5706 "file_store_meta_value", UnicodeText().with_variant(UnicodeText(20480), 'mysql'),
5686 nullable=True, unique=None, default=None)
5707 nullable=True, unique=None, default=None)
5687 _file_store_meta_value_type = Column(
5708 _file_store_meta_value_type = Column(
5688 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5709 "file_store_meta_value_type", String(255), nullable=True, unique=None,
5689 default='unicode')
5710 default='unicode')
5690
5711
5691 file_store_id = Column(
5712 file_store_id = Column(
5692 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5713 'file_store_id', Integer(), ForeignKey('file_store.file_store_id'),
5693 nullable=True, unique=None, default=None)
5714 nullable=True, unique=None, default=None)
5694
5715
5695 file_store = relationship('FileStore', lazy='joined')
5716 file_store = relationship('FileStore', lazy='joined')
5696
5717
5697 @classmethod
5718 @classmethod
5698 def valid_value_type(cls, value):
5719 def valid_value_type(cls, value):
5699 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5720 if value.split('.')[0] not in cls.SETTINGS_TYPES:
5700 raise ArtifactMetadataBadValueType(
5721 raise ArtifactMetadataBadValueType(
5701 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5722 'value_type must be one of %s got %s' % (cls.SETTINGS_TYPES.keys(), value))
5702
5723
5703 @hybrid_property
5724 @hybrid_property
5704 def file_store_meta_section(self):
5725 def file_store_meta_section(self):
5705 return self._file_store_meta_section
5726 return self._file_store_meta_section
5706
5727
5707 @file_store_meta_section.setter
5728 @file_store_meta_section.setter
5708 def file_store_meta_section(self, value):
5729 def file_store_meta_section(self, value):
5709 self._file_store_meta_section = value
5730 self._file_store_meta_section = value
5710 self._file_store_meta_section_hash = _hash_key(value)
5731 self._file_store_meta_section_hash = _hash_key(value)
5711
5732
5712 @hybrid_property
5733 @hybrid_property
5713 def file_store_meta_key(self):
5734 def file_store_meta_key(self):
5714 return self._file_store_meta_key
5735 return self._file_store_meta_key
5715
5736
5716 @file_store_meta_key.setter
5737 @file_store_meta_key.setter
5717 def file_store_meta_key(self, value):
5738 def file_store_meta_key(self, value):
5718 self._file_store_meta_key = value
5739 self._file_store_meta_key = value
5719 self._file_store_meta_key_hash = _hash_key(value)
5740 self._file_store_meta_key_hash = _hash_key(value)
5720
5741
5721 @hybrid_property
5742 @hybrid_property
5722 def file_store_meta_value(self):
5743 def file_store_meta_value(self):
5723 val = self._file_store_meta_value
5744 val = self._file_store_meta_value
5724
5745
5725 if self._file_store_meta_value_type:
5746 if self._file_store_meta_value_type:
5726 # e.g unicode.encrypted == unicode
5747 # e.g unicode.encrypted == unicode
5727 _type = self._file_store_meta_value_type.split('.')[0]
5748 _type = self._file_store_meta_value_type.split('.')[0]
5728 # decode the encrypted value if it's encrypted field type
5749 # decode the encrypted value if it's encrypted field type
5729 if '.encrypted' in self._file_store_meta_value_type:
5750 if '.encrypted' in self._file_store_meta_value_type:
5730 cipher = EncryptedTextValue()
5751 cipher = EncryptedTextValue()
5731 val = safe_unicode(cipher.process_result_value(val, None))
5752 val = safe_unicode(cipher.process_result_value(val, None))
5732 # do final type conversion
5753 # do final type conversion
5733 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5754 converter = self.SETTINGS_TYPES.get(_type) or self.SETTINGS_TYPES['unicode']
5734 val = converter(val)
5755 val = converter(val)
5735
5756
5736 return val
5757 return val
5737
5758
5738 @file_store_meta_value.setter
5759 @file_store_meta_value.setter
5739 def file_store_meta_value(self, val):
5760 def file_store_meta_value(self, val):
5740 val = safe_unicode(val)
5761 val = safe_unicode(val)
5741 # encode the encrypted value
5762 # encode the encrypted value
5742 if '.encrypted' in self.file_store_meta_value_type:
5763 if '.encrypted' in self.file_store_meta_value_type:
5743 cipher = EncryptedTextValue()
5764 cipher = EncryptedTextValue()
5744 val = safe_unicode(cipher.process_bind_param(val, None))
5765 val = safe_unicode(cipher.process_bind_param(val, None))
5745 self._file_store_meta_value = val
5766 self._file_store_meta_value = val
5746
5767
5747 @hybrid_property
5768 @hybrid_property
5748 def file_store_meta_value_type(self):
5769 def file_store_meta_value_type(self):
5749 return self._file_store_meta_value_type
5770 return self._file_store_meta_value_type
5750
5771
5751 @file_store_meta_value_type.setter
5772 @file_store_meta_value_type.setter
5752 def file_store_meta_value_type(self, val):
5773 def file_store_meta_value_type(self, val):
5753 # e.g unicode.encrypted
5774 # e.g unicode.encrypted
5754 self.valid_value_type(val)
5775 self.valid_value_type(val)
5755 self._file_store_meta_value_type = val
5776 self._file_store_meta_value_type = val
5756
5777
5757 def __json__(self):
5778 def __json__(self):
5758 data = {
5779 data = {
5759 'artifact': self.file_store.file_uid,
5780 'artifact': self.file_store.file_uid,
5760 'section': self.file_store_meta_section,
5781 'section': self.file_store_meta_section,
5761 'key': self.file_store_meta_key,
5782 'key': self.file_store_meta_key,
5762 'value': self.file_store_meta_value,
5783 'value': self.file_store_meta_value,
5763 }
5784 }
5764
5785
5765 return data
5786 return data
5766
5787
5767 def __repr__(self):
5788 def __repr__(self):
5768 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5789 return '<%s[%s]%s=>%s]>' % (self.__class__.__name__, self.file_store_meta_section,
5769 self.file_store_meta_key, self.file_store_meta_value)
5790 self.file_store_meta_key, self.file_store_meta_value)
5770
5791
5771
5792
5772 class DbMigrateVersion(Base, BaseModel):
5793 class DbMigrateVersion(Base, BaseModel):
5773 __tablename__ = 'db_migrate_version'
5794 __tablename__ = 'db_migrate_version'
5774 __table_args__ = (
5795 __table_args__ = (
5775 base_table_args,
5796 base_table_args,
5776 )
5797 )
5777
5798
5778 repository_id = Column('repository_id', String(250), primary_key=True)
5799 repository_id = Column('repository_id', String(250), primary_key=True)
5779 repository_path = Column('repository_path', Text)
5800 repository_path = Column('repository_path', Text)
5780 version = Column('version', Integer)
5801 version = Column('version', Integer)
5781
5802
5782 @classmethod
5803 @classmethod
5783 def set_version(cls, version):
5804 def set_version(cls, version):
5784 """
5805 """
5785 Helper for forcing a different version, usually for debugging purposes via ishell.
5806 Helper for forcing a different version, usually for debugging purposes via ishell.
5786 """
5807 """
5787 ver = DbMigrateVersion.query().first()
5808 ver = DbMigrateVersion.query().first()
5788 ver.version = version
5809 ver.version = version
5789 Session().commit()
5810 Session().commit()
5790
5811
5791
5812
5792 class DbSession(Base, BaseModel):
5813 class DbSession(Base, BaseModel):
5793 __tablename__ = 'db_session'
5814 __tablename__ = 'db_session'
5794 __table_args__ = (
5815 __table_args__ = (
5795 base_table_args,
5816 base_table_args,
5796 )
5817 )
5797
5818
5798 def __repr__(self):
5819 def __repr__(self):
5799 return '<DB:DbSession({})>'.format(self.id)
5820 return '<DB:DbSession({})>'.format(self.id)
5800
5821
5801 id = Column('id', Integer())
5822 id = Column('id', Integer())
5802 namespace = Column('namespace', String(255), primary_key=True)
5823 namespace = Column('namespace', String(255), primary_key=True)
5803 accessed = Column('accessed', DateTime, nullable=False)
5824 accessed = Column('accessed', DateTime, nullable=False)
5804 created = Column('created', DateTime, nullable=False)
5825 created = Column('created', DateTime, nullable=False)
5805 data = Column('data', PickleType, nullable=False)
5826 data = Column('data', PickleType, nullable=False)
@@ -1,884 +1,887 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 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 """
22 """
23 repo group model for RhodeCode
23 repo group model for RhodeCode
24 """
24 """
25
25
26 import os
26 import os
27 import datetime
27 import datetime
28 import itertools
28 import itertools
29 import logging
29 import logging
30 import shutil
30 import shutil
31 import time
31 import time
32 import traceback
32 import traceback
33 import string
33 import string
34
34
35 from zope.cachedescriptors.property import Lazy as LazyProperty
35 from zope.cachedescriptors.property import Lazy as LazyProperty
36
36
37 from rhodecode import events
37 from rhodecode import events
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
39 from rhodecode.model.db import (_hash_key, func, or_, in_filter_generator,
40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
40 Session, RepoGroup, UserRepoGroupToPerm, User, Permission, UserGroupRepoGroupToPerm,
41 UserGroup, Repository)
41 UserGroup, Repository)
42 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
42 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
43 from rhodecode.lib.caching_query import FromCache
43 from rhodecode.lib.caching_query import FromCache
44 from rhodecode.lib.utils2 import action_logger_generic
44 from rhodecode.lib.utils2 import action_logger_generic
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class RepoGroupModel(BaseModel):
49 class RepoGroupModel(BaseModel):
50
50
51 cls = RepoGroup
51 cls = RepoGroup
52 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
52 PERSONAL_GROUP_DESC = 'personal repo group of user `%(username)s`'
53 PERSONAL_GROUP_PATTERN = '${username}' # default
53 PERSONAL_GROUP_PATTERN = '${username}' # default
54
54
55 def _get_user_group(self, users_group):
55 def _get_user_group(self, users_group):
56 return self._get_instance(UserGroup, users_group,
56 return self._get_instance(UserGroup, users_group,
57 callback=UserGroup.get_by_group_name)
57 callback=UserGroup.get_by_group_name)
58
58
59 def _get_repo_group(self, repo_group):
59 def _get_repo_group(self, repo_group):
60 return self._get_instance(RepoGroup, repo_group,
60 return self._get_instance(RepoGroup, repo_group,
61 callback=RepoGroup.get_by_group_name)
61 callback=RepoGroup.get_by_group_name)
62
62
63 def get_repo_group(self, repo_group):
64 return self._get_repo_group(repo_group)
65
63 @LazyProperty
66 @LazyProperty
64 def repos_path(self):
67 def repos_path(self):
65 """
68 """
66 Gets the repositories root path from database
69 Gets the repositories root path from database
67 """
70 """
68
71
69 settings_model = VcsSettingsModel(sa=self.sa)
72 settings_model = VcsSettingsModel(sa=self.sa)
70 return settings_model.get_repos_location()
73 return settings_model.get_repos_location()
71
74
72 def get_by_group_name(self, repo_group_name, cache=None):
75 def get_by_group_name(self, repo_group_name, cache=None):
73 repo = self.sa.query(RepoGroup) \
76 repo = self.sa.query(RepoGroup) \
74 .filter(RepoGroup.group_name == repo_group_name)
77 .filter(RepoGroup.group_name == repo_group_name)
75
78
76 if cache:
79 if cache:
77 name_key = _hash_key(repo_group_name)
80 name_key = _hash_key(repo_group_name)
78 repo = repo.options(
81 repo = repo.options(
79 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
82 FromCache("sql_cache_short", "get_repo_group_%s" % name_key))
80 return repo.scalar()
83 return repo.scalar()
81
84
82 def get_default_create_personal_repo_group(self):
85 def get_default_create_personal_repo_group(self):
83 value = SettingsModel().get_setting_by_name(
86 value = SettingsModel().get_setting_by_name(
84 'create_personal_repo_group')
87 'create_personal_repo_group')
85 return value.app_settings_value if value else None or False
88 return value.app_settings_value if value else None or False
86
89
87 def get_personal_group_name_pattern(self):
90 def get_personal_group_name_pattern(self):
88 value = SettingsModel().get_setting_by_name(
91 value = SettingsModel().get_setting_by_name(
89 'personal_repo_group_pattern')
92 'personal_repo_group_pattern')
90 val = value.app_settings_value if value else None
93 val = value.app_settings_value if value else None
91 group_template = val or self.PERSONAL_GROUP_PATTERN
94 group_template = val or self.PERSONAL_GROUP_PATTERN
92
95
93 group_template = group_template.lstrip('/')
96 group_template = group_template.lstrip('/')
94 return group_template
97 return group_template
95
98
96 def get_personal_group_name(self, user):
99 def get_personal_group_name(self, user):
97 template = self.get_personal_group_name_pattern()
100 template = self.get_personal_group_name_pattern()
98 return string.Template(template).safe_substitute(
101 return string.Template(template).safe_substitute(
99 username=user.username,
102 username=user.username,
100 user_id=user.user_id,
103 user_id=user.user_id,
101 first_name=user.first_name,
104 first_name=user.first_name,
102 last_name=user.last_name,
105 last_name=user.last_name,
103 )
106 )
104
107
105 def create_personal_repo_group(self, user, commit_early=True):
108 def create_personal_repo_group(self, user, commit_early=True):
106 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
109 desc = self.PERSONAL_GROUP_DESC % {'username': user.username}
107 personal_repo_group_name = self.get_personal_group_name(user)
110 personal_repo_group_name = self.get_personal_group_name(user)
108
111
109 # create a new one
112 # create a new one
110 RepoGroupModel().create(
113 RepoGroupModel().create(
111 group_name=personal_repo_group_name,
114 group_name=personal_repo_group_name,
112 group_description=desc,
115 group_description=desc,
113 owner=user.username,
116 owner=user.username,
114 personal=True,
117 personal=True,
115 commit_early=commit_early)
118 commit_early=commit_early)
116
119
117 def _create_default_perms(self, new_group):
120 def _create_default_perms(self, new_group):
118 # create default permission
121 # create default permission
119 default_perm = 'group.read'
122 default_perm = 'group.read'
120 def_user = User.get_default_user()
123 def_user = User.get_default_user()
121 for p in def_user.user_perms:
124 for p in def_user.user_perms:
122 if p.permission.permission_name.startswith('group.'):
125 if p.permission.permission_name.startswith('group.'):
123 default_perm = p.permission.permission_name
126 default_perm = p.permission.permission_name
124 break
127 break
125
128
126 repo_group_to_perm = UserRepoGroupToPerm()
129 repo_group_to_perm = UserRepoGroupToPerm()
127 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
130 repo_group_to_perm.permission = Permission.get_by_key(default_perm)
128
131
129 repo_group_to_perm.group = new_group
132 repo_group_to_perm.group = new_group
130 repo_group_to_perm.user_id = def_user.user_id
133 repo_group_to_perm.user_id = def_user.user_id
131 return repo_group_to_perm
134 return repo_group_to_perm
132
135
133 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
136 def _get_group_name_and_parent(self, group_name_full, repo_in_path=False,
134 get_object=False):
137 get_object=False):
135 """
138 """
136 Get's the group name and a parent group name from given group name.
139 Get's the group name and a parent group name from given group name.
137 If repo_in_path is set to truth, we asume the full path also includes
140 If repo_in_path is set to truth, we asume the full path also includes
138 repo name, in such case we clean the last element.
141 repo name, in such case we clean the last element.
139
142
140 :param group_name_full:
143 :param group_name_full:
141 """
144 """
142 split_paths = 1
145 split_paths = 1
143 if repo_in_path:
146 if repo_in_path:
144 split_paths = 2
147 split_paths = 2
145 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
148 _parts = group_name_full.rsplit(RepoGroup.url_sep(), split_paths)
146
149
147 if repo_in_path and len(_parts) > 1:
150 if repo_in_path and len(_parts) > 1:
148 # such case last element is the repo_name
151 # such case last element is the repo_name
149 _parts.pop(-1)
152 _parts.pop(-1)
150 group_name_cleaned = _parts[-1] # just the group name
153 group_name_cleaned = _parts[-1] # just the group name
151 parent_repo_group_name = None
154 parent_repo_group_name = None
152
155
153 if len(_parts) > 1:
156 if len(_parts) > 1:
154 parent_repo_group_name = _parts[0]
157 parent_repo_group_name = _parts[0]
155
158
156 parent_group = None
159 parent_group = None
157 if parent_repo_group_name:
160 if parent_repo_group_name:
158 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
161 parent_group = RepoGroup.get_by_group_name(parent_repo_group_name)
159
162
160 if get_object:
163 if get_object:
161 return group_name_cleaned, parent_repo_group_name, parent_group
164 return group_name_cleaned, parent_repo_group_name, parent_group
162
165
163 return group_name_cleaned, parent_repo_group_name
166 return group_name_cleaned, parent_repo_group_name
164
167
165 def check_exist_filesystem(self, group_name, exc_on_failure=True):
168 def check_exist_filesystem(self, group_name, exc_on_failure=True):
166 create_path = os.path.join(self.repos_path, group_name)
169 create_path = os.path.join(self.repos_path, group_name)
167 log.debug('creating new group in %s', create_path)
170 log.debug('creating new group in %s', create_path)
168
171
169 if os.path.isdir(create_path):
172 if os.path.isdir(create_path):
170 if exc_on_failure:
173 if exc_on_failure:
171 abs_create_path = os.path.abspath(create_path)
174 abs_create_path = os.path.abspath(create_path)
172 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
175 raise Exception('Directory `{}` already exists !'.format(abs_create_path))
173 return False
176 return False
174 return True
177 return True
175
178
176 def _create_group(self, group_name):
179 def _create_group(self, group_name):
177 """
180 """
178 makes repository group on filesystem
181 makes repository group on filesystem
179
182
180 :param repo_name:
183 :param repo_name:
181 :param parent_id:
184 :param parent_id:
182 """
185 """
183
186
184 self.check_exist_filesystem(group_name)
187 self.check_exist_filesystem(group_name)
185 create_path = os.path.join(self.repos_path, group_name)
188 create_path = os.path.join(self.repos_path, group_name)
186 log.debug('creating new group in %s', create_path)
189 log.debug('creating new group in %s', create_path)
187 os.makedirs(create_path, mode=0o755)
190 os.makedirs(create_path, mode=0o755)
188 log.debug('created group in %s', create_path)
191 log.debug('created group in %s', create_path)
189
192
190 def _rename_group(self, old, new):
193 def _rename_group(self, old, new):
191 """
194 """
192 Renames a group on filesystem
195 Renames a group on filesystem
193
196
194 :param group_name:
197 :param group_name:
195 """
198 """
196
199
197 if old == new:
200 if old == new:
198 log.debug('skipping group rename')
201 log.debug('skipping group rename')
199 return
202 return
200
203
201 log.debug('renaming repository group from %s to %s', old, new)
204 log.debug('renaming repository group from %s to %s', old, new)
202
205
203 old_path = os.path.join(self.repos_path, old)
206 old_path = os.path.join(self.repos_path, old)
204 new_path = os.path.join(self.repos_path, new)
207 new_path = os.path.join(self.repos_path, new)
205
208
206 log.debug('renaming repos paths from %s to %s', old_path, new_path)
209 log.debug('renaming repos paths from %s to %s', old_path, new_path)
207
210
208 if os.path.isdir(new_path):
211 if os.path.isdir(new_path):
209 raise Exception('Was trying to rename to already '
212 raise Exception('Was trying to rename to already '
210 'existing dir %s' % new_path)
213 'existing dir %s' % new_path)
211 shutil.move(old_path, new_path)
214 shutil.move(old_path, new_path)
212
215
213 def _delete_filesystem_group(self, group, force_delete=False):
216 def _delete_filesystem_group(self, group, force_delete=False):
214 """
217 """
215 Deletes a group from a filesystem
218 Deletes a group from a filesystem
216
219
217 :param group: instance of group from database
220 :param group: instance of group from database
218 :param force_delete: use shutil rmtree to remove all objects
221 :param force_delete: use shutil rmtree to remove all objects
219 """
222 """
220 paths = group.full_path.split(RepoGroup.url_sep())
223 paths = group.full_path.split(RepoGroup.url_sep())
221 paths = os.sep.join(paths)
224 paths = os.sep.join(paths)
222
225
223 rm_path = os.path.join(self.repos_path, paths)
226 rm_path = os.path.join(self.repos_path, paths)
224 log.info("Removing group %s", rm_path)
227 log.info("Removing group %s", rm_path)
225 # delete only if that path really exists
228 # delete only if that path really exists
226 if os.path.isdir(rm_path):
229 if os.path.isdir(rm_path):
227 if force_delete:
230 if force_delete:
228 shutil.rmtree(rm_path)
231 shutil.rmtree(rm_path)
229 else:
232 else:
230 # archive that group`
233 # archive that group`
231 _now = datetime.datetime.now()
234 _now = datetime.datetime.now()
232 _ms = str(_now.microsecond).rjust(6, '0')
235 _ms = str(_now.microsecond).rjust(6, '0')
233 _d = 'rm__%s_GROUP_%s' % (
236 _d = 'rm__%s_GROUP_%s' % (
234 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
237 _now.strftime('%Y%m%d_%H%M%S_' + _ms), group.name)
235 shutil.move(rm_path, os.path.join(self.repos_path, _d))
238 shutil.move(rm_path, os.path.join(self.repos_path, _d))
236
239
237 def create(self, group_name, group_description, owner, just_db=False,
240 def create(self, group_name, group_description, owner, just_db=False,
238 copy_permissions=False, personal=None, commit_early=True):
241 copy_permissions=False, personal=None, commit_early=True):
239
242
240 (group_name_cleaned,
243 (group_name_cleaned,
241 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
244 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(group_name)
242
245
243 parent_group = None
246 parent_group = None
244 if parent_group_name:
247 if parent_group_name:
245 parent_group = self._get_repo_group(parent_group_name)
248 parent_group = self._get_repo_group(parent_group_name)
246 if not parent_group:
249 if not parent_group:
247 # we tried to create a nested group, but the parent is not
250 # we tried to create a nested group, but the parent is not
248 # existing
251 # existing
249 raise ValueError(
252 raise ValueError(
250 'Parent group `%s` given in `%s` group name '
253 'Parent group `%s` given in `%s` group name '
251 'is not yet existing.' % (parent_group_name, group_name))
254 'is not yet existing.' % (parent_group_name, group_name))
252
255
253 # because we are doing a cleanup, we need to check if such directory
256 # because we are doing a cleanup, we need to check if such directory
254 # already exists. If we don't do that we can accidentally delete
257 # already exists. If we don't do that we can accidentally delete
255 # existing directory via cleanup that can cause data issues, since
258 # existing directory via cleanup that can cause data issues, since
256 # delete does a folder rename to special syntax later cleanup
259 # delete does a folder rename to special syntax later cleanup
257 # functions can delete this
260 # functions can delete this
258 cleanup_group = self.check_exist_filesystem(group_name,
261 cleanup_group = self.check_exist_filesystem(group_name,
259 exc_on_failure=False)
262 exc_on_failure=False)
260 user = self._get_user(owner)
263 user = self._get_user(owner)
261 if not user:
264 if not user:
262 raise ValueError('Owner %s not found as rhodecode user', owner)
265 raise ValueError('Owner %s not found as rhodecode user', owner)
263
266
264 try:
267 try:
265 new_repo_group = RepoGroup()
268 new_repo_group = RepoGroup()
266 new_repo_group.user = user
269 new_repo_group.user = user
267 new_repo_group.group_description = group_description or group_name
270 new_repo_group.group_description = group_description or group_name
268 new_repo_group.parent_group = parent_group
271 new_repo_group.parent_group = parent_group
269 new_repo_group.group_name = group_name
272 new_repo_group.group_name = group_name
270 new_repo_group.personal = personal
273 new_repo_group.personal = personal
271
274
272 self.sa.add(new_repo_group)
275 self.sa.add(new_repo_group)
273
276
274 # create an ADMIN permission for owner except if we're super admin,
277 # create an ADMIN permission for owner except if we're super admin,
275 # later owner should go into the owner field of groups
278 # later owner should go into the owner field of groups
276 if not user.is_admin:
279 if not user.is_admin:
277 self.grant_user_permission(repo_group=new_repo_group,
280 self.grant_user_permission(repo_group=new_repo_group,
278 user=owner, perm='group.admin')
281 user=owner, perm='group.admin')
279
282
280 if parent_group and copy_permissions:
283 if parent_group and copy_permissions:
281 # copy permissions from parent
284 # copy permissions from parent
282 user_perms = UserRepoGroupToPerm.query() \
285 user_perms = UserRepoGroupToPerm.query() \
283 .filter(UserRepoGroupToPerm.group == parent_group).all()
286 .filter(UserRepoGroupToPerm.group == parent_group).all()
284
287
285 group_perms = UserGroupRepoGroupToPerm.query() \
288 group_perms = UserGroupRepoGroupToPerm.query() \
286 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
289 .filter(UserGroupRepoGroupToPerm.group == parent_group).all()
287
290
288 for perm in user_perms:
291 for perm in user_perms:
289 # don't copy over the permission for user who is creating
292 # don't copy over the permission for user who is creating
290 # this group, if he is not super admin he get's admin
293 # this group, if he is not super admin he get's admin
291 # permission set above
294 # permission set above
292 if perm.user != user or user.is_admin:
295 if perm.user != user or user.is_admin:
293 UserRepoGroupToPerm.create(
296 UserRepoGroupToPerm.create(
294 perm.user, new_repo_group, perm.permission)
297 perm.user, new_repo_group, perm.permission)
295
298
296 for perm in group_perms:
299 for perm in group_perms:
297 UserGroupRepoGroupToPerm.create(
300 UserGroupRepoGroupToPerm.create(
298 perm.users_group, new_repo_group, perm.permission)
301 perm.users_group, new_repo_group, perm.permission)
299 else:
302 else:
300 perm_obj = self._create_default_perms(new_repo_group)
303 perm_obj = self._create_default_perms(new_repo_group)
301 self.sa.add(perm_obj)
304 self.sa.add(perm_obj)
302
305
303 # now commit the changes, earlier so we are sure everything is in
306 # now commit the changes, earlier so we are sure everything is in
304 # the database.
307 # the database.
305 if commit_early:
308 if commit_early:
306 self.sa.commit()
309 self.sa.commit()
307 if not just_db:
310 if not just_db:
308 self._create_group(new_repo_group.group_name)
311 self._create_group(new_repo_group.group_name)
309
312
310 # trigger the post hook
313 # trigger the post hook
311 from rhodecode.lib import hooks_base
314 from rhodecode.lib import hooks_base
312 repo_group = RepoGroup.get_by_group_name(group_name)
315 repo_group = RepoGroup.get_by_group_name(group_name)
313
316
314 # update repo group commit caches initially
317 # update repo group commit caches initially
315 repo_group.update_commit_cache()
318 repo_group.update_commit_cache()
316
319
317 hooks_base.create_repository_group(
320 hooks_base.create_repository_group(
318 created_by=user.username, **repo_group.get_dict())
321 created_by=user.username, **repo_group.get_dict())
319
322
320 # Trigger create event.
323 # Trigger create event.
321 events.trigger(events.RepoGroupCreateEvent(repo_group))
324 events.trigger(events.RepoGroupCreateEvent(repo_group))
322
325
323 return new_repo_group
326 return new_repo_group
324 except Exception:
327 except Exception:
325 self.sa.rollback()
328 self.sa.rollback()
326 log.exception('Exception occurred when creating repository group, '
329 log.exception('Exception occurred when creating repository group, '
327 'doing cleanup...')
330 'doing cleanup...')
328 # rollback things manually !
331 # rollback things manually !
329 repo_group = RepoGroup.get_by_group_name(group_name)
332 repo_group = RepoGroup.get_by_group_name(group_name)
330 if repo_group:
333 if repo_group:
331 RepoGroup.delete(repo_group.group_id)
334 RepoGroup.delete(repo_group.group_id)
332 self.sa.commit()
335 self.sa.commit()
333 if cleanup_group:
336 if cleanup_group:
334 RepoGroupModel()._delete_filesystem_group(repo_group)
337 RepoGroupModel()._delete_filesystem_group(repo_group)
335 raise
338 raise
336
339
337 def update_permissions(
340 def update_permissions(
338 self, repo_group, perm_additions=None, perm_updates=None,
341 self, repo_group, perm_additions=None, perm_updates=None,
339 perm_deletions=None, recursive=None, check_perms=True,
342 perm_deletions=None, recursive=None, check_perms=True,
340 cur_user=None):
343 cur_user=None):
341 from rhodecode.model.repo import RepoModel
344 from rhodecode.model.repo import RepoModel
342 from rhodecode.lib.auth import HasUserGroupPermissionAny
345 from rhodecode.lib.auth import HasUserGroupPermissionAny
343
346
344 if not perm_additions:
347 if not perm_additions:
345 perm_additions = []
348 perm_additions = []
346 if not perm_updates:
349 if not perm_updates:
347 perm_updates = []
350 perm_updates = []
348 if not perm_deletions:
351 if not perm_deletions:
349 perm_deletions = []
352 perm_deletions = []
350
353
351 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
354 req_perms = ('usergroup.read', 'usergroup.write', 'usergroup.admin')
352
355
353 changes = {
356 changes = {
354 'added': [],
357 'added': [],
355 'updated': [],
358 'updated': [],
356 'deleted': [],
359 'deleted': [],
357 'default_user_changed': None
360 'default_user_changed': None
358 }
361 }
359
362
360 def _set_perm_user(obj, user, perm):
363 def _set_perm_user(obj, user, perm):
361 if isinstance(obj, RepoGroup):
364 if isinstance(obj, RepoGroup):
362 self.grant_user_permission(
365 self.grant_user_permission(
363 repo_group=obj, user=user, perm=perm)
366 repo_group=obj, user=user, perm=perm)
364 elif isinstance(obj, Repository):
367 elif isinstance(obj, Repository):
365 # private repos will not allow to change the default
368 # private repos will not allow to change the default
366 # permissions using recursive mode
369 # permissions using recursive mode
367 if obj.private and user == User.DEFAULT_USER:
370 if obj.private and user == User.DEFAULT_USER:
368 return
371 return
369
372
370 # we set group permission but we have to switch to repo
373 # we set group permission but we have to switch to repo
371 # permission
374 # permission
372 perm = perm.replace('group.', 'repository.')
375 perm = perm.replace('group.', 'repository.')
373 RepoModel().grant_user_permission(
376 RepoModel().grant_user_permission(
374 repo=obj, user=user, perm=perm)
377 repo=obj, user=user, perm=perm)
375
378
376 def _set_perm_group(obj, users_group, perm):
379 def _set_perm_group(obj, users_group, perm):
377 if isinstance(obj, RepoGroup):
380 if isinstance(obj, RepoGroup):
378 self.grant_user_group_permission(
381 self.grant_user_group_permission(
379 repo_group=obj, group_name=users_group, perm=perm)
382 repo_group=obj, group_name=users_group, perm=perm)
380 elif isinstance(obj, Repository):
383 elif isinstance(obj, Repository):
381 # we set group permission but we have to switch to repo
384 # we set group permission but we have to switch to repo
382 # permission
385 # permission
383 perm = perm.replace('group.', 'repository.')
386 perm = perm.replace('group.', 'repository.')
384 RepoModel().grant_user_group_permission(
387 RepoModel().grant_user_group_permission(
385 repo=obj, group_name=users_group, perm=perm)
388 repo=obj, group_name=users_group, perm=perm)
386
389
387 def _revoke_perm_user(obj, user):
390 def _revoke_perm_user(obj, user):
388 if isinstance(obj, RepoGroup):
391 if isinstance(obj, RepoGroup):
389 self.revoke_user_permission(repo_group=obj, user=user)
392 self.revoke_user_permission(repo_group=obj, user=user)
390 elif isinstance(obj, Repository):
393 elif isinstance(obj, Repository):
391 RepoModel().revoke_user_permission(repo=obj, user=user)
394 RepoModel().revoke_user_permission(repo=obj, user=user)
392
395
393 def _revoke_perm_group(obj, user_group):
396 def _revoke_perm_group(obj, user_group):
394 if isinstance(obj, RepoGroup):
397 if isinstance(obj, RepoGroup):
395 self.revoke_user_group_permission(
398 self.revoke_user_group_permission(
396 repo_group=obj, group_name=user_group)
399 repo_group=obj, group_name=user_group)
397 elif isinstance(obj, Repository):
400 elif isinstance(obj, Repository):
398 RepoModel().revoke_user_group_permission(
401 RepoModel().revoke_user_group_permission(
399 repo=obj, group_name=user_group)
402 repo=obj, group_name=user_group)
400
403
401 # start updates
404 # start updates
402 log.debug('Now updating permissions for %s in recursive mode:%s',
405 log.debug('Now updating permissions for %s in recursive mode:%s',
403 repo_group, recursive)
406 repo_group, recursive)
404
407
405 # initialize check function, we'll call that multiple times
408 # initialize check function, we'll call that multiple times
406 has_group_perm = HasUserGroupPermissionAny(*req_perms)
409 has_group_perm = HasUserGroupPermissionAny(*req_perms)
407
410
408 for obj in repo_group.recursive_groups_and_repos():
411 for obj in repo_group.recursive_groups_and_repos():
409 # iterated obj is an instance of a repos group or repository in
412 # iterated obj is an instance of a repos group or repository in
410 # that group, recursive option can be: none, repos, groups, all
413 # that group, recursive option can be: none, repos, groups, all
411 if recursive == 'all':
414 if recursive == 'all':
412 obj = obj
415 obj = obj
413 elif recursive == 'repos':
416 elif recursive == 'repos':
414 # skip groups, other than this one
417 # skip groups, other than this one
415 if isinstance(obj, RepoGroup) and not obj == repo_group:
418 if isinstance(obj, RepoGroup) and not obj == repo_group:
416 continue
419 continue
417 elif recursive == 'groups':
420 elif recursive == 'groups':
418 # skip repos
421 # skip repos
419 if isinstance(obj, Repository):
422 if isinstance(obj, Repository):
420 continue
423 continue
421 else: # recursive == 'none':
424 else: # recursive == 'none':
422 # DEFAULT option - don't apply to iterated objects
425 # DEFAULT option - don't apply to iterated objects
423 # also we do a break at the end of this loop. if we are not
426 # also we do a break at the end of this loop. if we are not
424 # in recursive mode
427 # in recursive mode
425 obj = repo_group
428 obj = repo_group
426
429
427 change_obj = obj.get_api_data()
430 change_obj = obj.get_api_data()
428
431
429 # update permissions
432 # update permissions
430 for member_id, perm, member_type in perm_updates:
433 for member_id, perm, member_type in perm_updates:
431 member_id = int(member_id)
434 member_id = int(member_id)
432 if member_type == 'user':
435 if member_type == 'user':
433 member_name = User.get(member_id).username
436 member_name = User.get(member_id).username
434 if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER:
437 if isinstance(obj, RepoGroup) and obj == repo_group and member_name == User.DEFAULT_USER:
435 # NOTE(dan): detect if we changed permissions for default user
438 # NOTE(dan): detect if we changed permissions for default user
436 perm_obj = self.sa.query(UserRepoGroupToPerm) \
439 perm_obj = self.sa.query(UserRepoGroupToPerm) \
437 .filter(UserRepoGroupToPerm.user_id == member_id) \
440 .filter(UserRepoGroupToPerm.user_id == member_id) \
438 .filter(UserRepoGroupToPerm.group == repo_group) \
441 .filter(UserRepoGroupToPerm.group == repo_group) \
439 .scalar()
442 .scalar()
440 if perm_obj and perm_obj.permission.permission_name != perm:
443 if perm_obj and perm_obj.permission.permission_name != perm:
441 changes['default_user_changed'] = True
444 changes['default_user_changed'] = True
442
445
443 # this updates also current one if found
446 # this updates also current one if found
444 _set_perm_user(obj, user=member_id, perm=perm)
447 _set_perm_user(obj, user=member_id, perm=perm)
445 elif member_type == 'user_group':
448 elif member_type == 'user_group':
446 member_name = UserGroup.get(member_id).users_group_name
449 member_name = UserGroup.get(member_id).users_group_name
447 if not check_perms or has_group_perm(member_name,
450 if not check_perms or has_group_perm(member_name,
448 user=cur_user):
451 user=cur_user):
449 _set_perm_group(obj, users_group=member_id, perm=perm)
452 _set_perm_group(obj, users_group=member_id, perm=perm)
450 else:
453 else:
451 raise ValueError("member_type must be 'user' or 'user_group' "
454 raise ValueError("member_type must be 'user' or 'user_group' "
452 "got {} instead".format(member_type))
455 "got {} instead".format(member_type))
453
456
454 changes['updated'].append(
457 changes['updated'].append(
455 {'change_obj': change_obj, 'type': member_type,
458 {'change_obj': change_obj, 'type': member_type,
456 'id': member_id, 'name': member_name, 'new_perm': perm})
459 'id': member_id, 'name': member_name, 'new_perm': perm})
457
460
458 # set new permissions
461 # set new permissions
459 for member_id, perm, member_type in perm_additions:
462 for member_id, perm, member_type in perm_additions:
460 member_id = int(member_id)
463 member_id = int(member_id)
461 if member_type == 'user':
464 if member_type == 'user':
462 member_name = User.get(member_id).username
465 member_name = User.get(member_id).username
463 _set_perm_user(obj, user=member_id, perm=perm)
466 _set_perm_user(obj, user=member_id, perm=perm)
464 elif member_type == 'user_group':
467 elif member_type == 'user_group':
465 # check if we have permissions to alter this usergroup
468 # check if we have permissions to alter this usergroup
466 member_name = UserGroup.get(member_id).users_group_name
469 member_name = UserGroup.get(member_id).users_group_name
467 if not check_perms or has_group_perm(member_name,
470 if not check_perms or has_group_perm(member_name,
468 user=cur_user):
471 user=cur_user):
469 _set_perm_group(obj, users_group=member_id, perm=perm)
472 _set_perm_group(obj, users_group=member_id, perm=perm)
470 else:
473 else:
471 raise ValueError("member_type must be 'user' or 'user_group' "
474 raise ValueError("member_type must be 'user' or 'user_group' "
472 "got {} instead".format(member_type))
475 "got {} instead".format(member_type))
473
476
474 changes['added'].append(
477 changes['added'].append(
475 {'change_obj': change_obj, 'type': member_type,
478 {'change_obj': change_obj, 'type': member_type,
476 'id': member_id, 'name': member_name, 'new_perm': perm})
479 'id': member_id, 'name': member_name, 'new_perm': perm})
477
480
478 # delete permissions
481 # delete permissions
479 for member_id, perm, member_type in perm_deletions:
482 for member_id, perm, member_type in perm_deletions:
480 member_id = int(member_id)
483 member_id = int(member_id)
481 if member_type == 'user':
484 if member_type == 'user':
482 member_name = User.get(member_id).username
485 member_name = User.get(member_id).username
483 _revoke_perm_user(obj, user=member_id)
486 _revoke_perm_user(obj, user=member_id)
484 elif member_type == 'user_group':
487 elif member_type == 'user_group':
485 # check if we have permissions to alter this usergroup
488 # check if we have permissions to alter this usergroup
486 member_name = UserGroup.get(member_id).users_group_name
489 member_name = UserGroup.get(member_id).users_group_name
487 if not check_perms or has_group_perm(member_name,
490 if not check_perms or has_group_perm(member_name,
488 user=cur_user):
491 user=cur_user):
489 _revoke_perm_group(obj, user_group=member_id)
492 _revoke_perm_group(obj, user_group=member_id)
490 else:
493 else:
491 raise ValueError("member_type must be 'user' or 'user_group' "
494 raise ValueError("member_type must be 'user' or 'user_group' "
492 "got {} instead".format(member_type))
495 "got {} instead".format(member_type))
493
496
494 changes['deleted'].append(
497 changes['deleted'].append(
495 {'change_obj': change_obj, 'type': member_type,
498 {'change_obj': change_obj, 'type': member_type,
496 'id': member_id, 'name': member_name, 'new_perm': perm})
499 'id': member_id, 'name': member_name, 'new_perm': perm})
497
500
498 # if it's not recursive call for all,repos,groups
501 # if it's not recursive call for all,repos,groups
499 # break the loop and don't proceed with other changes
502 # break the loop and don't proceed with other changes
500 if recursive not in ['all', 'repos', 'groups']:
503 if recursive not in ['all', 'repos', 'groups']:
501 break
504 break
502
505
503 return changes
506 return changes
504
507
505 def update(self, repo_group, form_data):
508 def update(self, repo_group, form_data):
506 try:
509 try:
507 repo_group = self._get_repo_group(repo_group)
510 repo_group = self._get_repo_group(repo_group)
508 old_path = repo_group.full_path
511 old_path = repo_group.full_path
509
512
510 # change properties
513 # change properties
511 if 'group_description' in form_data:
514 if 'group_description' in form_data:
512 repo_group.group_description = form_data['group_description']
515 repo_group.group_description = form_data['group_description']
513
516
514 if 'enable_locking' in form_data:
517 if 'enable_locking' in form_data:
515 repo_group.enable_locking = form_data['enable_locking']
518 repo_group.enable_locking = form_data['enable_locking']
516
519
517 if 'group_parent_id' in form_data:
520 if 'group_parent_id' in form_data:
518 parent_group = (
521 parent_group = (
519 self._get_repo_group(form_data['group_parent_id']))
522 self._get_repo_group(form_data['group_parent_id']))
520 repo_group.group_parent_id = (
523 repo_group.group_parent_id = (
521 parent_group.group_id if parent_group else None)
524 parent_group.group_id if parent_group else None)
522 repo_group.parent_group = parent_group
525 repo_group.parent_group = parent_group
523
526
524 # mikhail: to update the full_path, we have to explicitly
527 # mikhail: to update the full_path, we have to explicitly
525 # update group_name
528 # update group_name
526 group_name = form_data.get('group_name', repo_group.name)
529 group_name = form_data.get('group_name', repo_group.name)
527 repo_group.group_name = repo_group.get_new_name(group_name)
530 repo_group.group_name = repo_group.get_new_name(group_name)
528
531
529 new_path = repo_group.full_path
532 new_path = repo_group.full_path
530
533
531 if 'user' in form_data:
534 if 'user' in form_data:
532 repo_group.user = User.get_by_username(form_data['user'])
535 repo_group.user = User.get_by_username(form_data['user'])
533
536
534 self.sa.add(repo_group)
537 self.sa.add(repo_group)
535
538
536 # iterate over all members of this groups and do fixes
539 # iterate over all members of this groups and do fixes
537 # set locking if given
540 # set locking if given
538 # if obj is a repoGroup also fix the name of the group according
541 # if obj is a repoGroup also fix the name of the group according
539 # to the parent
542 # to the parent
540 # if obj is a Repo fix it's name
543 # if obj is a Repo fix it's name
541 # this can be potentially heavy operation
544 # this can be potentially heavy operation
542 for obj in repo_group.recursive_groups_and_repos():
545 for obj in repo_group.recursive_groups_and_repos():
543 # set the value from it's parent
546 # set the value from it's parent
544 obj.enable_locking = repo_group.enable_locking
547 obj.enable_locking = repo_group.enable_locking
545 if isinstance(obj, RepoGroup):
548 if isinstance(obj, RepoGroup):
546 new_name = obj.get_new_name(obj.name)
549 new_name = obj.get_new_name(obj.name)
547 log.debug('Fixing group %s to new name %s',
550 log.debug('Fixing group %s to new name %s',
548 obj.group_name, new_name)
551 obj.group_name, new_name)
549 obj.group_name = new_name
552 obj.group_name = new_name
550
553
551 elif isinstance(obj, Repository):
554 elif isinstance(obj, Repository):
552 # we need to get all repositories from this new group and
555 # we need to get all repositories from this new group and
553 # rename them accordingly to new group path
556 # rename them accordingly to new group path
554 new_name = obj.get_new_name(obj.just_name)
557 new_name = obj.get_new_name(obj.just_name)
555 log.debug('Fixing repo %s to new name %s',
558 log.debug('Fixing repo %s to new name %s',
556 obj.repo_name, new_name)
559 obj.repo_name, new_name)
557 obj.repo_name = new_name
560 obj.repo_name = new_name
558
561
559 self.sa.add(obj)
562 self.sa.add(obj)
560
563
561 self._rename_group(old_path, new_path)
564 self._rename_group(old_path, new_path)
562
565
563 # Trigger update event.
566 # Trigger update event.
564 events.trigger(events.RepoGroupUpdateEvent(repo_group))
567 events.trigger(events.RepoGroupUpdateEvent(repo_group))
565
568
566 return repo_group
569 return repo_group
567 except Exception:
570 except Exception:
568 log.error(traceback.format_exc())
571 log.error(traceback.format_exc())
569 raise
572 raise
570
573
571 def delete(self, repo_group, force_delete=False, fs_remove=True):
574 def delete(self, repo_group, force_delete=False, fs_remove=True):
572 repo_group = self._get_repo_group(repo_group)
575 repo_group = self._get_repo_group(repo_group)
573 if not repo_group:
576 if not repo_group:
574 return False
577 return False
575 try:
578 try:
576 self.sa.delete(repo_group)
579 self.sa.delete(repo_group)
577 if fs_remove:
580 if fs_remove:
578 self._delete_filesystem_group(repo_group, force_delete)
581 self._delete_filesystem_group(repo_group, force_delete)
579 else:
582 else:
580 log.debug('skipping removal from filesystem')
583 log.debug('skipping removal from filesystem')
581
584
582 # Trigger delete event.
585 # Trigger delete event.
583 events.trigger(events.RepoGroupDeleteEvent(repo_group))
586 events.trigger(events.RepoGroupDeleteEvent(repo_group))
584 return True
587 return True
585
588
586 except Exception:
589 except Exception:
587 log.error('Error removing repo_group %s', repo_group)
590 log.error('Error removing repo_group %s', repo_group)
588 raise
591 raise
589
592
590 def grant_user_permission(self, repo_group, user, perm):
593 def grant_user_permission(self, repo_group, user, perm):
591 """
594 """
592 Grant permission for user on given repository group, or update
595 Grant permission for user on given repository group, or update
593 existing one if found
596 existing one if found
594
597
595 :param repo_group: Instance of RepoGroup, repositories_group_id,
598 :param repo_group: Instance of RepoGroup, repositories_group_id,
596 or repositories_group name
599 or repositories_group name
597 :param user: Instance of User, user_id or username
600 :param user: Instance of User, user_id or username
598 :param perm: Instance of Permission, or permission_name
601 :param perm: Instance of Permission, or permission_name
599 """
602 """
600
603
601 repo_group = self._get_repo_group(repo_group)
604 repo_group = self._get_repo_group(repo_group)
602 user = self._get_user(user)
605 user = self._get_user(user)
603 permission = self._get_perm(perm)
606 permission = self._get_perm(perm)
604
607
605 # check if we have that permission already
608 # check if we have that permission already
606 obj = self.sa.query(UserRepoGroupToPerm)\
609 obj = self.sa.query(UserRepoGroupToPerm)\
607 .filter(UserRepoGroupToPerm.user == user)\
610 .filter(UserRepoGroupToPerm.user == user)\
608 .filter(UserRepoGroupToPerm.group == repo_group)\
611 .filter(UserRepoGroupToPerm.group == repo_group)\
609 .scalar()
612 .scalar()
610 if obj is None:
613 if obj is None:
611 # create new !
614 # create new !
612 obj = UserRepoGroupToPerm()
615 obj = UserRepoGroupToPerm()
613 obj.group = repo_group
616 obj.group = repo_group
614 obj.user = user
617 obj.user = user
615 obj.permission = permission
618 obj.permission = permission
616 self.sa.add(obj)
619 self.sa.add(obj)
617 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
620 log.debug('Granted perm %s to %s on %s', perm, user, repo_group)
618 action_logger_generic(
621 action_logger_generic(
619 'granted permission: {} to user: {} on repogroup: {}'.format(
622 'granted permission: {} to user: {} on repogroup: {}'.format(
620 perm, user, repo_group), namespace='security.repogroup')
623 perm, user, repo_group), namespace='security.repogroup')
621 return obj
624 return obj
622
625
623 def revoke_user_permission(self, repo_group, user):
626 def revoke_user_permission(self, repo_group, user):
624 """
627 """
625 Revoke permission for user on given repository group
628 Revoke permission for user on given repository group
626
629
627 :param repo_group: Instance of RepoGroup, repositories_group_id,
630 :param repo_group: Instance of RepoGroup, repositories_group_id,
628 or repositories_group name
631 or repositories_group name
629 :param user: Instance of User, user_id or username
632 :param user: Instance of User, user_id or username
630 """
633 """
631
634
632 repo_group = self._get_repo_group(repo_group)
635 repo_group = self._get_repo_group(repo_group)
633 user = self._get_user(user)
636 user = self._get_user(user)
634
637
635 obj = self.sa.query(UserRepoGroupToPerm)\
638 obj = self.sa.query(UserRepoGroupToPerm)\
636 .filter(UserRepoGroupToPerm.user == user)\
639 .filter(UserRepoGroupToPerm.user == user)\
637 .filter(UserRepoGroupToPerm.group == repo_group)\
640 .filter(UserRepoGroupToPerm.group == repo_group)\
638 .scalar()
641 .scalar()
639 if obj:
642 if obj:
640 self.sa.delete(obj)
643 self.sa.delete(obj)
641 log.debug('Revoked perm on %s on %s', repo_group, user)
644 log.debug('Revoked perm on %s on %s', repo_group, user)
642 action_logger_generic(
645 action_logger_generic(
643 'revoked permission from user: {} on repogroup: {}'.format(
646 'revoked permission from user: {} on repogroup: {}'.format(
644 user, repo_group), namespace='security.repogroup')
647 user, repo_group), namespace='security.repogroup')
645
648
646 def grant_user_group_permission(self, repo_group, group_name, perm):
649 def grant_user_group_permission(self, repo_group, group_name, perm):
647 """
650 """
648 Grant permission for user group on given repository group, or update
651 Grant permission for user group on given repository group, or update
649 existing one if found
652 existing one if found
650
653
651 :param repo_group: Instance of RepoGroup, repositories_group_id,
654 :param repo_group: Instance of RepoGroup, repositories_group_id,
652 or repositories_group name
655 or repositories_group name
653 :param group_name: Instance of UserGroup, users_group_id,
656 :param group_name: Instance of UserGroup, users_group_id,
654 or user group name
657 or user group name
655 :param perm: Instance of Permission, or permission_name
658 :param perm: Instance of Permission, or permission_name
656 """
659 """
657 repo_group = self._get_repo_group(repo_group)
660 repo_group = self._get_repo_group(repo_group)
658 group_name = self._get_user_group(group_name)
661 group_name = self._get_user_group(group_name)
659 permission = self._get_perm(perm)
662 permission = self._get_perm(perm)
660
663
661 # check if we have that permission already
664 # check if we have that permission already
662 obj = self.sa.query(UserGroupRepoGroupToPerm)\
665 obj = self.sa.query(UserGroupRepoGroupToPerm)\
663 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
666 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
664 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
667 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
665 .scalar()
668 .scalar()
666
669
667 if obj is None:
670 if obj is None:
668 # create new
671 # create new
669 obj = UserGroupRepoGroupToPerm()
672 obj = UserGroupRepoGroupToPerm()
670
673
671 obj.group = repo_group
674 obj.group = repo_group
672 obj.users_group = group_name
675 obj.users_group = group_name
673 obj.permission = permission
676 obj.permission = permission
674 self.sa.add(obj)
677 self.sa.add(obj)
675 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
678 log.debug('Granted perm %s to %s on %s', perm, group_name, repo_group)
676 action_logger_generic(
679 action_logger_generic(
677 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
680 'granted permission: {} to usergroup: {} on repogroup: {}'.format(
678 perm, group_name, repo_group), namespace='security.repogroup')
681 perm, group_name, repo_group), namespace='security.repogroup')
679 return obj
682 return obj
680
683
681 def revoke_user_group_permission(self, repo_group, group_name):
684 def revoke_user_group_permission(self, repo_group, group_name):
682 """
685 """
683 Revoke permission for user group on given repository group
686 Revoke permission for user group on given repository group
684
687
685 :param repo_group: Instance of RepoGroup, repositories_group_id,
688 :param repo_group: Instance of RepoGroup, repositories_group_id,
686 or repositories_group name
689 or repositories_group name
687 :param group_name: Instance of UserGroup, users_group_id,
690 :param group_name: Instance of UserGroup, users_group_id,
688 or user group name
691 or user group name
689 """
692 """
690 repo_group = self._get_repo_group(repo_group)
693 repo_group = self._get_repo_group(repo_group)
691 group_name = self._get_user_group(group_name)
694 group_name = self._get_user_group(group_name)
692
695
693 obj = self.sa.query(UserGroupRepoGroupToPerm)\
696 obj = self.sa.query(UserGroupRepoGroupToPerm)\
694 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
697 .filter(UserGroupRepoGroupToPerm.group == repo_group)\
695 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
698 .filter(UserGroupRepoGroupToPerm.users_group == group_name)\
696 .scalar()
699 .scalar()
697 if obj:
700 if obj:
698 self.sa.delete(obj)
701 self.sa.delete(obj)
699 log.debug('Revoked perm to %s on %s', repo_group, group_name)
702 log.debug('Revoked perm to %s on %s', repo_group, group_name)
700 action_logger_generic(
703 action_logger_generic(
701 'revoked permission from usergroup: {} on repogroup: {}'.format(
704 'revoked permission from usergroup: {} on repogroup: {}'.format(
702 group_name, repo_group), namespace='security.repogroup')
705 group_name, repo_group), namespace='security.repogroup')
703
706
704 @classmethod
707 @classmethod
705 def update_commit_cache(cls, repo_groups=None):
708 def update_commit_cache(cls, repo_groups=None):
706 if not repo_groups:
709 if not repo_groups:
707 repo_groups = RepoGroup.getAll()
710 repo_groups = RepoGroup.getAll()
708 for repo_group in repo_groups:
711 for repo_group in repo_groups:
709 repo_group.update_commit_cache()
712 repo_group.update_commit_cache()
710
713
711 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
714 def get_repo_groups_as_dict(self, repo_group_list=None, admin=False,
712 super_user_actions=False):
715 super_user_actions=False):
713
716
714 from pyramid.threadlocal import get_current_request
717 from pyramid.threadlocal import get_current_request
715 _render = get_current_request().get_partial_renderer(
718 _render = get_current_request().get_partial_renderer(
716 'rhodecode:templates/data_table/_dt_elements.mako')
719 'rhodecode:templates/data_table/_dt_elements.mako')
717 c = _render.get_call_context()
720 c = _render.get_call_context()
718 h = _render.get_helpers()
721 h = _render.get_helpers()
719
722
720 def quick_menu(repo_group_name):
723 def quick_menu(repo_group_name):
721 return _render('quick_repo_group_menu', repo_group_name)
724 return _render('quick_repo_group_menu', repo_group_name)
722
725
723 def repo_group_lnk(repo_group_name):
726 def repo_group_lnk(repo_group_name):
724 return _render('repo_group_name', repo_group_name)
727 return _render('repo_group_name', repo_group_name)
725
728
726 def last_change(last_change):
729 def last_change(last_change):
727 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
730 if admin and isinstance(last_change, datetime.datetime) and not last_change.tzinfo:
728 ts = time.time()
731 ts = time.time()
729 utc_offset = (datetime.datetime.fromtimestamp(ts)
732 utc_offset = (datetime.datetime.fromtimestamp(ts)
730 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
733 - datetime.datetime.utcfromtimestamp(ts)).total_seconds()
731 last_change = last_change + datetime.timedelta(seconds=utc_offset)
734 last_change = last_change + datetime.timedelta(seconds=utc_offset)
732 return _render("last_change", last_change)
735 return _render("last_change", last_change)
733
736
734 def desc(desc, personal):
737 def desc(desc, personal):
735 return _render(
738 return _render(
736 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
739 'repo_group_desc', desc, personal, c.visual.stylify_metatags)
737
740
738 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
741 def repo_group_actions(repo_group_id, repo_group_name, gr_count):
739 return _render(
742 return _render(
740 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
743 'repo_group_actions', repo_group_id, repo_group_name, gr_count)
741
744
742 def repo_group_name(repo_group_name, children_groups):
745 def repo_group_name(repo_group_name, children_groups):
743 return _render("repo_group_name", repo_group_name, children_groups)
746 return _render("repo_group_name", repo_group_name, children_groups)
744
747
745 def user_profile(username):
748 def user_profile(username):
746 return _render('user_profile', username)
749 return _render('user_profile', username)
747
750
748 repo_group_data = []
751 repo_group_data = []
749 for group in repo_group_list:
752 for group in repo_group_list:
750 # NOTE(marcink): because we use only raw column we need to load it like that
753 # NOTE(marcink): because we use only raw column we need to load it like that
751 changeset_cache = RepoGroup._load_changeset_cache(
754 changeset_cache = RepoGroup._load_changeset_cache(
752 '', group._changeset_cache)
755 '', group._changeset_cache)
753 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
756 last_commit_change = RepoGroup._load_commit_change(changeset_cache)
754 row = {
757 row = {
755 "menu": quick_menu(group.group_name),
758 "menu": quick_menu(group.group_name),
756 "name": repo_group_lnk(group.group_name),
759 "name": repo_group_lnk(group.group_name),
757 "name_raw": group.group_name,
760 "name_raw": group.group_name,
758
761
759 "last_change": last_change(last_commit_change),
762 "last_change": last_change(last_commit_change),
760
763
761 "last_changeset": "",
764 "last_changeset": "",
762 "last_changeset_raw": "",
765 "last_changeset_raw": "",
763
766
764 "desc": desc(h.escape(group.group_description), group.personal),
767 "desc": desc(h.escape(group.group_description), group.personal),
765 "top_level_repos": 0,
768 "top_level_repos": 0,
766 "owner": user_profile(group.User.username)
769 "owner": user_profile(group.User.username)
767 }
770 }
768 if admin:
771 if admin:
769 repo_count = group.repositories.count()
772 repo_count = group.repositories.count()
770 children_groups = map(
773 children_groups = map(
771 h.safe_unicode,
774 h.safe_unicode,
772 itertools.chain((g.name for g in group.parents),
775 itertools.chain((g.name for g in group.parents),
773 (x.name for x in [group])))
776 (x.name for x in [group])))
774 row.update({
777 row.update({
775 "action": repo_group_actions(
778 "action": repo_group_actions(
776 group.group_id, group.group_name, repo_count),
779 group.group_id, group.group_name, repo_count),
777 "top_level_repos": repo_count,
780 "top_level_repos": repo_count,
778 "name": repo_group_name(group.group_name, children_groups),
781 "name": repo_group_name(group.group_name, children_groups),
779
782
780 })
783 })
781 repo_group_data.append(row)
784 repo_group_data.append(row)
782
785
783 return repo_group_data
786 return repo_group_data
784
787
785 def get_repo_groups_data_table(
788 def get_repo_groups_data_table(
786 self, draw, start, limit,
789 self, draw, start, limit,
787 search_q, order_by, order_dir,
790 search_q, order_by, order_dir,
788 auth_user, repo_group_id):
791 auth_user, repo_group_id):
789 from rhodecode.model.scm import RepoGroupList
792 from rhodecode.model.scm import RepoGroupList
790
793
791 _perms = ['group.read', 'group.write', 'group.admin']
794 _perms = ['group.read', 'group.write', 'group.admin']
792 repo_groups = RepoGroup.query() \
795 repo_groups = RepoGroup.query() \
793 .filter(RepoGroup.group_parent_id == repo_group_id) \
796 .filter(RepoGroup.group_parent_id == repo_group_id) \
794 .all()
797 .all()
795 auth_repo_group_list = RepoGroupList(
798 auth_repo_group_list = RepoGroupList(
796 repo_groups, perm_set=_perms,
799 repo_groups, perm_set=_perms,
797 extra_kwargs=dict(user=auth_user))
800 extra_kwargs=dict(user=auth_user))
798
801
799 allowed_ids = [-1]
802 allowed_ids = [-1]
800 for repo_group in auth_repo_group_list:
803 for repo_group in auth_repo_group_list:
801 allowed_ids.append(repo_group.group_id)
804 allowed_ids.append(repo_group.group_id)
802
805
803 repo_groups_data_total_count = RepoGroup.query() \
806 repo_groups_data_total_count = RepoGroup.query() \
804 .filter(RepoGroup.group_parent_id == repo_group_id) \
807 .filter(RepoGroup.group_parent_id == repo_group_id) \
805 .filter(or_(
808 .filter(or_(
806 # generate multiple IN to fix limitation problems
809 # generate multiple IN to fix limitation problems
807 *in_filter_generator(RepoGroup.group_id, allowed_ids))
810 *in_filter_generator(RepoGroup.group_id, allowed_ids))
808 ) \
811 ) \
809 .count()
812 .count()
810
813
811 base_q = Session.query(
814 base_q = Session.query(
812 RepoGroup.group_name,
815 RepoGroup.group_name,
813 RepoGroup.group_name_hash,
816 RepoGroup.group_name_hash,
814 RepoGroup.group_description,
817 RepoGroup.group_description,
815 RepoGroup.group_id,
818 RepoGroup.group_id,
816 RepoGroup.personal,
819 RepoGroup.personal,
817 RepoGroup.updated_on,
820 RepoGroup.updated_on,
818 RepoGroup._changeset_cache,
821 RepoGroup._changeset_cache,
819 User,
822 User,
820 ) \
823 ) \
821 .filter(RepoGroup.group_parent_id == repo_group_id) \
824 .filter(RepoGroup.group_parent_id == repo_group_id) \
822 .filter(or_(
825 .filter(or_(
823 # generate multiple IN to fix limitation problems
826 # generate multiple IN to fix limitation problems
824 *in_filter_generator(RepoGroup.group_id, allowed_ids))
827 *in_filter_generator(RepoGroup.group_id, allowed_ids))
825 ) \
828 ) \
826 .join(User, User.user_id == RepoGroup.user_id) \
829 .join(User, User.user_id == RepoGroup.user_id) \
827 .group_by(RepoGroup, User)
830 .group_by(RepoGroup, User)
828
831
829 repo_groups_data_total_filtered_count = base_q.count()
832 repo_groups_data_total_filtered_count = base_q.count()
830
833
831 sort_defined = False
834 sort_defined = False
832
835
833 if order_by == 'group_name':
836 if order_by == 'group_name':
834 sort_col = func.lower(RepoGroup.group_name)
837 sort_col = func.lower(RepoGroup.group_name)
835 sort_defined = True
838 sort_defined = True
836 elif order_by == 'user_username':
839 elif order_by == 'user_username':
837 sort_col = User.username
840 sort_col = User.username
838 else:
841 else:
839 sort_col = getattr(RepoGroup, order_by, None)
842 sort_col = getattr(RepoGroup, order_by, None)
840
843
841 if sort_defined or sort_col:
844 if sort_defined or sort_col:
842 if order_dir == 'asc':
845 if order_dir == 'asc':
843 sort_col = sort_col.asc()
846 sort_col = sort_col.asc()
844 else:
847 else:
845 sort_col = sort_col.desc()
848 sort_col = sort_col.desc()
846
849
847 base_q = base_q.order_by(sort_col)
850 base_q = base_q.order_by(sort_col)
848 base_q = base_q.offset(start).limit(limit)
851 base_q = base_q.offset(start).limit(limit)
849
852
850 repo_group_list = base_q.all()
853 repo_group_list = base_q.all()
851
854
852 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
855 repo_groups_data = RepoGroupModel().get_repo_groups_as_dict(
853 repo_group_list=repo_group_list, admin=False)
856 repo_group_list=repo_group_list, admin=False)
854
857
855 data = ({
858 data = ({
856 'draw': draw,
859 'draw': draw,
857 'data': repo_groups_data,
860 'data': repo_groups_data,
858 'recordsTotal': repo_groups_data_total_count,
861 'recordsTotal': repo_groups_data_total_count,
859 'recordsFiltered': repo_groups_data_total_filtered_count,
862 'recordsFiltered': repo_groups_data_total_filtered_count,
860 })
863 })
861 return data
864 return data
862
865
863 def _get_defaults(self, repo_group_name):
866 def _get_defaults(self, repo_group_name):
864 repo_group = RepoGroup.get_by_group_name(repo_group_name)
867 repo_group = RepoGroup.get_by_group_name(repo_group_name)
865
868
866 if repo_group is None:
869 if repo_group is None:
867 return None
870 return None
868
871
869 defaults = repo_group.get_dict()
872 defaults = repo_group.get_dict()
870 defaults['repo_group_name'] = repo_group.name
873 defaults['repo_group_name'] = repo_group.name
871 defaults['repo_group_description'] = repo_group.group_description
874 defaults['repo_group_description'] = repo_group.group_description
872 defaults['repo_group_enable_locking'] = repo_group.enable_locking
875 defaults['repo_group_enable_locking'] = repo_group.enable_locking
873
876
874 # we use -1 as this is how in HTML, we mark an empty group
877 # we use -1 as this is how in HTML, we mark an empty group
875 defaults['repo_group'] = defaults['group_parent_id'] or -1
878 defaults['repo_group'] = defaults['group_parent_id'] or -1
876
879
877 # fill owner
880 # fill owner
878 if repo_group.user:
881 if repo_group.user:
879 defaults.update({'user': repo_group.user.username})
882 defaults.update({'user': repo_group.user.username})
880 else:
883 else:
881 replacement_user = User.get_first_super_admin().username
884 replacement_user = User.get_first_super_admin().username
882 defaults.update({'user': replacement_user})
885 defaults.update({'user': replacement_user})
883
886
884 return defaults
887 return defaults
@@ -1,403 +1,405 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
15 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
16 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
16 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
17 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
17 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
18 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
18 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
19 pyroutes.register('admin_home', '/_admin', []);
19 pyroutes.register('admin_home', '/_admin', []);
20 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
20 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
21 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
21 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
22 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
22 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
23 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
23 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
24 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
24 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
25 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
25 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
26 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
26 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
27 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
27 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
28 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
28 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
29 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
29 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
30 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
30 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
31 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
31 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
32 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
32 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
33 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
33 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
34 pyroutes.register('admin_settings', '/_admin/settings', []);
34 pyroutes.register('admin_settings', '/_admin/settings', []);
35 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
35 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
36 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
36 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
37 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
37 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
38 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
38 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
39 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
39 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
40 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
40 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
41 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
41 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
42 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
42 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
43 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
43 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
44 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
44 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
45 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
45 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
46 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
46 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
47 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
47 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
48 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
48 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
49 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
49 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
50 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
50 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
51 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
51 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
52 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
52 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
53 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
53 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
54 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
54 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
55 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
55 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
56 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
56 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
57 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
57 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
58 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
58 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
59 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
60 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
60 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
61 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
61 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
62 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
62 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
63 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
63 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
64 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
64 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
65 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
65 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
66 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
66 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
67 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
67 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
68 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
68 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
69 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
69 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
70 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
70 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
71 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
71 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
72 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
72 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
73 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
73 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
74 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
74 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
75 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
75 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
76 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
76 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
77 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
77 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
78 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
78 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
79 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
79 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
80 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
80 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
81 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
81 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
82 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
82 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
83 pyroutes.register('apiv2', '/_admin/api', []);
83 pyroutes.register('apiv2', '/_admin/api', []);
84 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
84 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
85 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
85 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
86 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
86 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
87 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
87 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
88 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
88 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
89 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
89 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
90 pyroutes.register('channelstream_proxy', '/_channelstream', []);
90 pyroutes.register('channelstream_proxy', '/_channelstream', []);
91 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
91 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
92 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
92 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
93 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
93 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
94 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
94 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
95 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
95 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
96 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
96 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
97 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
97 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
98 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
98 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
99 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
99 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
100 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
100 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
101 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
101 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
102 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
102 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
103 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
103 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
104 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
104 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
105 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
105 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
106 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
106 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
107 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
107 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
108 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
108 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
109 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
109 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
110 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
110 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
111 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
111 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
112 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
112 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
113 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
113 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
114 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
114 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
115 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
115 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
116 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
116 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
117 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
117 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
118 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
118 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
119 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
119 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
120 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
120 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
121 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
121 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
122 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
122 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
123 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
123 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
124 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
124 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
125 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
125 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
126 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
126 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
127 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
127 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
128 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
128 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
129 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
129 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
130 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
130 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
131 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
131 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
132 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
132 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
133 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
133 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
134 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
134 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
135 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
135 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
136 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
136 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
137 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
137 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
138 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
138 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
139 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
139 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
140 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
140 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
141 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
141 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
142 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
142 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
143 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
143 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
144 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
144 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
145 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
145 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
146 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
146 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
147 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
147 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
148 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
148 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
149 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
149 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
150 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
150 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
151 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
151 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
152 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
152 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
153 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
153 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
154 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
154 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
155 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
155 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
156 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
156 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
157 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
157 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
158 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
158 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
159 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
159 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
160 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
160 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
161 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
161 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
162 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
162 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
163 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
163 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
164 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
164 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
165 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
165 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
166 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
166 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
167 pyroutes.register('favicon', '/favicon.ico', []);
167 pyroutes.register('favicon', '/favicon.ico', []);
168 pyroutes.register('file_preview', '/_file_preview', []);
168 pyroutes.register('file_preview', '/_file_preview', []);
169 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
169 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
170 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
170 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
171 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
171 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
172 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
172 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
173 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
173 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
174 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
174 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
175 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
175 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
176 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
176 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
177 pyroutes.register('gists_create', '/_admin/gists/create', []);
177 pyroutes.register('gists_create', '/_admin/gists/create', []);
178 pyroutes.register('gists_new', '/_admin/gists/new', []);
178 pyroutes.register('gists_new', '/_admin/gists/new', []);
179 pyroutes.register('gists_show', '/_admin/gists', []);
179 pyroutes.register('gists_show', '/_admin/gists', []);
180 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
180 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
181 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
181 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
182 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
182 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
183 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
183 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
184 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
184 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
185 pyroutes.register('goto_switcher_data', '/_goto_data', []);
185 pyroutes.register('goto_switcher_data', '/_goto_data', []);
186 pyroutes.register('home', '/', []);
186 pyroutes.register('home', '/', []);
187 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
187 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
188 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
188 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
189 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
189 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
190 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
190 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
191 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
191 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
192 pyroutes.register('journal', '/_admin/journal', []);
192 pyroutes.register('journal', '/_admin/journal', []);
193 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
193 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
194 pyroutes.register('journal_public', '/_admin/public_journal', []);
194 pyroutes.register('journal_public', '/_admin/public_journal', []);
195 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
195 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
196 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
196 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
197 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
197 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
198 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
198 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
199 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
199 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
200 pyroutes.register('login', '/_admin/login', []);
200 pyroutes.register('login', '/_admin/login', []);
201 pyroutes.register('logout', '/_admin/logout', []);
201 pyroutes.register('logout', '/_admin/logout', []);
202 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
202 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
203 pyroutes.register('main_page_repos_data', '/_home_repos', []);
203 pyroutes.register('main_page_repos_data', '/_home_repos', []);
204 pyroutes.register('markup_preview', '/_markup_preview', []);
204 pyroutes.register('markup_preview', '/_markup_preview', []);
205 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
205 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
206 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
206 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
207 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
207 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
208 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
208 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
209 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
209 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
210 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
210 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
211 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
211 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
212 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
212 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
213 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
213 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
214 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
214 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
215 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
215 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
216 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
216 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
217 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
217 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
218 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
218 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
219 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
219 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
220 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
220 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
221 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
221 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
222 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
222 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
223 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
223 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
224 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
224 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
225 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
225 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
226 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
226 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
227 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
227 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
228 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
228 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
229 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
229 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
230 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
230 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
231 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
231 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
232 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
232 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
233 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
233 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
234 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
234 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
235 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
235 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
236 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
236 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
237 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
237 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
238 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
238 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
239 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
239 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
240 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
240 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
241 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
241 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
242 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
242 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
243 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
243 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
244 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
244 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
245 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
245 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
246 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
246 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
247 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
247 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
248 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
248 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
249 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
249 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
250 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
250 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
251 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
251 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
252 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
252 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
253 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
253 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
254 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
254 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
255 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
255 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
256 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
256 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
257 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
257 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
258 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
258 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
259 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
259 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
260 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
260 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
261 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
261 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
262 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
262 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
263 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
263 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
264 pyroutes.register('register', '/_admin/register', []);
264 pyroutes.register('register', '/_admin/register', []);
265 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
265 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
266 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
266 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
267 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
267 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
268 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
268 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
269 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
269 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
270 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
270 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
271 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
271 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
272 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
272 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
273 pyroutes.register('repo_artifacts_stream_script', '/_file_store/stream-upload-script', []);
274 pyroutes.register('repo_artifacts_stream_store', '/_file_store/stream-upload', []);
273 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
275 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
274 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
276 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
275 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
277 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
276 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
278 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
277 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
279 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
278 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
280 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
279 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
281 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
280 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
282 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
281 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
283 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
282 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
284 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
283 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
285 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
284 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_history_id)s/history_view', ['repo_name', 'commit_id', 'comment_history_id']);
286 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_history_id)s/history_view', ['repo_name', 'commit_id', 'comment_history_id']);
285 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
287 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
286 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
288 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
287 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
289 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
288 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
290 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
289 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
291 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
290 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
292 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
291 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
293 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
292 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
294 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
293 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
295 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
294 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
296 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
295 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
297 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
296 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
298 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
297 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
299 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
298 pyroutes.register('repo_create', '/_admin/repos/create', []);
300 pyroutes.register('repo_create', '/_admin/repos/create', []);
299 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
301 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
300 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
302 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
301 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
303 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
302 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
304 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
303 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
305 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
304 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
306 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
305 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
307 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
306 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
308 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
307 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
309 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
308 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
310 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
309 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
311 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
310 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
312 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
311 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
313 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
312 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
314 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
313 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
315 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
314 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
316 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
315 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
316 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
319 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
320 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
319 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
321 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
320 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
322 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
321 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
322 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
324 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
325 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
324 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
326 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
325 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
327 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
326 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
328 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
327 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
329 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
328 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
330 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
329 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
331 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
330 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
332 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
331 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
333 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
332 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
334 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
333 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
335 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
334 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
336 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
335 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
337 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
336 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
338 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
337 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
339 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
338 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
340 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
339 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
341 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
340 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
342 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
341 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
343 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
342 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
344 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
343 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
345 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
344 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
346 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
345 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
347 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
346 pyroutes.register('repo_list_data', '/_repos', []);
348 pyroutes.register('repo_list_data', '/_repos', []);
347 pyroutes.register('repo_new', '/_admin/repos/new', []);
349 pyroutes.register('repo_new', '/_admin/repos/new', []);
348 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
350 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
349 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
351 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
350 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
352 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
351 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
353 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
352 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
354 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
353 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
355 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
354 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
356 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
355 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
357 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
356 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
358 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
357 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
359 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
358 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
360 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
359 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
361 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
360 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
362 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
361 pyroutes.register('repos', '/_admin/repos', []);
363 pyroutes.register('repos', '/_admin/repos', []);
362 pyroutes.register('repos_data', '/_admin/repos_data', []);
364 pyroutes.register('repos_data', '/_admin/repos_data', []);
363 pyroutes.register('reset_password', '/_admin/password_reset', []);
365 pyroutes.register('reset_password', '/_admin/password_reset', []);
364 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
366 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
365 pyroutes.register('robots', '/robots.txt', []);
367 pyroutes.register('robots', '/robots.txt', []);
366 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
368 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
367 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
369 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
368 pyroutes.register('search', '/_admin/search', []);
370 pyroutes.register('search', '/_admin/search', []);
369 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
371 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
370 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
372 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
371 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
373 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
372 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
374 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
373 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
375 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
374 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
376 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
375 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
377 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
376 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
378 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
377 pyroutes.register('upload_file', '/_file_store/upload', []);
379 pyroutes.register('upload_file', '/_file_store/upload', []);
378 pyroutes.register('user_autocomplete_data', '/_users', []);
380 pyroutes.register('user_autocomplete_data', '/_users', []);
379 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
381 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
380 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
382 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
381 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
383 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
382 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
384 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
383 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
385 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
384 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
386 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
385 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
387 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
386 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
388 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
387 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
389 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
388 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
390 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
389 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
391 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
390 pyroutes.register('user_groups', '/_admin/user_groups', []);
392 pyroutes.register('user_groups', '/_admin/user_groups', []);
391 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
393 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
392 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
394 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
393 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
395 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
394 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
396 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
395 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
397 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
396 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
398 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
397 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
399 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
398 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
400 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
399 pyroutes.register('users', '/_admin/users', []);
401 pyroutes.register('users', '/_admin/users', []);
400 pyroutes.register('users_create', '/_admin/users/create', []);
402 pyroutes.register('users_create', '/_admin/users/create', []);
401 pyroutes.register('users_data', '/_admin/users_data', []);
403 pyroutes.register('users_data', '/_admin/users_data', []);
402 pyroutes.register('users_new', '/_admin/users/new', []);
404 pyroutes.register('users_new', '/_admin/users/new', []);
403 }
405 }
General Comments 0
You need to be logged in to leave comments. Login now