##// END OF EJS Templates
added caching layer into RSS/ATOM feeds...
marcink -
r3018:023f7873 beta
parent child Browse files
Show More
@@ -1,147 +1,175 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.feed
3 rhodecode.controllers.feed
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Feed controller for rhodecode
6 Feed controller for rhodecode
7
7
8 :created_on: Apr 23, 2010
8 :created_on: Apr 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from pylons import url, response, tmpl_context as c
28 from pylons import url, response, tmpl_context as c
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30
30
31 from beaker.cache import cache_region, region_invalidate
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32
33
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseRepoController
36 from rhodecode.lib.base import BaseRepoController
36 from rhodecode.lib.diffs import DiffProcessor
37 from rhodecode.lib.diffs import DiffProcessor, LimitedDiffContainer
38 from rhodecode.model.db import CacheInvalidation
37
39
38 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
39
41
40
42
41 class FeedController(BaseRepoController):
43 class FeedController(BaseRepoController):
42
44
43 @LoginRequired(api_access=True)
45 @LoginRequired(api_access=True)
44 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
46 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
45 'repository.admin')
47 'repository.admin')
46 def __before__(self):
48 def __before__(self):
47 super(FeedController, self).__before__()
49 super(FeedController, self).__before__()
48 #common values for feeds
50 #common values for feeds
49 self.description = _('Changes on %s repository')
51 self.description = _('Changes on %s repository')
50 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
52 self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s')
51 self.language = 'en-us'
53 self.language = 'en-us'
52 self.ttl = "5"
54 self.ttl = "5"
53 self.feed_nr = 20
55 self.feed_nr = 20
56 # we need to protect from parsing huge diffs here other way
57 # we can kill the server, 32*1024 chars is a reasonable limit
58 self.feed_diff_limit = 32 * 1024
54
59
55 def _get_title(self, cs):
60 def _get_title(self, cs):
56 return "%s" % (
61 return "%s" % (
57 h.shorter(cs.message, 160)
62 h.shorter(cs.message, 160)
58 )
63 )
59
64
60 def __changes(self, cs):
65 def __changes(self, cs):
61 changes = []
66 changes = []
62 _diff = cs.diff()
67 diff_processor = DiffProcessor(cs.diff(),
63 # we need to protect from parsing huge diffs here other way
68 diff_limit=self.feed_diff_limit)
64 # we can kill the server, 32*1024 chars is a reasonable limit
69 _parsed = diff_processor.prepare(inline_diff=False)
65 HUGE_DIFF = 32 * 1024
70 limited_diff = False
66 if len(_diff) > HUGE_DIFF:
71 if isinstance(_parsed, LimitedDiffContainer):
67 changes = ['\n ' + _('Changeset was too big and was cut off...')]
72 limited_diff = True
68 return changes
73
69 diffprocessor = DiffProcessor(_diff)
74 for st in _parsed:
70 stats = diffprocessor.prepare(inline_diff=False)
71 for st in stats:
72 st.update({'added': st['stats'][0],
75 st.update({'added': st['stats'][0],
73 'removed': st['stats'][1]})
76 'removed': st['stats'][1]})
74 changes.append('\n %(operation)s %(filename)s '
77 changes.append('\n %(operation)s %(filename)s '
75 '(%(added)s lines added, %(removed)s lines removed)'
78 '(%(added)s lines added, %(removed)s lines removed)'
76 % st)
79 % st)
80 if limited_diff:
81 changes = changes + ['\n ' +
82 _('Changeset was too big and was cut off...')]
77 return changes
83 return changes
78
84
79 def __get_desc(self, cs):
85 def __get_desc(self, cs):
80 desc_msg = []
86 desc_msg = []
81 desc_msg.append('%s %s %s:<br/>' % (cs.author, _('commited on'),
87 desc_msg.append('%s %s %s<br/>' % (h.person(cs.author),
88 _('commited on'),
82 h.fmt_date(cs.date)))
89 h.fmt_date(cs.date)))
83 #branches, tags, bookmarks
90 #branches, tags, bookmarks
84 if cs.branch:
91 if cs.branch:
85 desc_msg.append('branch: %s<br/>' % cs.branch)
92 desc_msg.append('branch: %s<br/>' % cs.branch)
86 if h.is_hg(c.rhodecode_repo):
93 if h.is_hg(c.rhodecode_repo):
87 for book in cs.bookmarks:
94 for book in cs.bookmarks:
88 desc_msg.append('bookmark: %s<br/>' % book)
95 desc_msg.append('bookmark: %s<br/>' % book)
89 for tag in cs.tags:
96 for tag in cs.tags:
90 desc_msg.append('tag: %s<br/>' % tag)
97 desc_msg.append('tag: %s<br/>' % tag)
91 # rev link
98 # rev link
92 _url = url('changeset_home', repo_name=cs.repository.name,
99 _url = url('changeset_home', repo_name=cs.repository.name,
93 revision=cs.raw_id, qualified=True)
100 revision=cs.raw_id, qualified=True)
94 desc_msg.append('changesest: <a href="%s">%s</a>' % (_url, cs.raw_id[:8]))
101 desc_msg.append('changesest: <a href="%s">%s</a>' % (_url, cs.raw_id[:8]))
95
102
96 desc_msg.append('<pre>')
103 desc_msg.append('<pre>')
97 desc_msg.append(cs.message)
104 desc_msg.append(cs.message)
98 desc_msg.append('\n')
105 desc_msg.append('\n')
99 desc_msg.extend(self.__changes(cs))
106 desc_msg.extend(self.__changes(cs))
100 desc_msg.append('</pre>')
107 desc_msg.append('</pre>')
101 return desc_msg
108 return desc_msg
102
109
103 def atom(self, repo_name):
110 def atom(self, repo_name):
104 """Produce an atom-1.0 feed via feedgenerator module"""
111 """Produce an atom-1.0 feed via feedgenerator module"""
112
113 @cache_region('long_term')
114 def _get_feed_from_cache(key):
105 feed = Atom1Feed(
115 feed = Atom1Feed(
106 title=self.title % repo_name,
116 title=self.title % repo_name,
107 link=url('summary_home', repo_name=repo_name,
117 link=url('summary_home', repo_name=repo_name,
108 qualified=True),
118 qualified=True),
109 description=self.description % repo_name,
119 description=self.description % repo_name,
110 language=self.language,
120 language=self.language,
111 ttl=self.ttl
121 ttl=self.ttl
112 )
122 )
113
123
114 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
124 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
115 feed.add_item(title=self._get_title(cs),
125 feed.add_item(title=self._get_title(cs),
116 link=url('changeset_home', repo_name=repo_name,
126 link=url('changeset_home', repo_name=repo_name,
117 revision=cs.raw_id, qualified=True),
127 revision=cs.raw_id, qualified=True),
118 author_name=cs.author,
128 author_name=cs.author,
119 description=''.join(self.__get_desc(cs)),
129 description=''.join(self.__get_desc(cs)),
120 pubdate=cs.date,
130 pubdate=cs.date,
121 )
131 )
122
132
123 response.content_type = feed.mime_type
133 response.content_type = feed.mime_type
124 return feed.writeString('utf-8')
134 return feed.writeString('utf-8')
125
135
136 key = repo_name + '_ATOM'
137 inv = CacheInvalidation.invalidate(key)
138 if inv is not None:
139 region_invalidate(_get_feed_from_cache, None, key)
140 CacheInvalidation.set_valid(inv.cache_key)
141 return _get_feed_from_cache(key)
142
126 def rss(self, repo_name):
143 def rss(self, repo_name):
127 """Produce an rss2 feed via feedgenerator module"""
144 """Produce an rss2 feed via feedgenerator module"""
145
146 @cache_region('long_term')
147 def _get_feed_from_cache(key):
128 feed = Rss201rev2Feed(
148 feed = Rss201rev2Feed(
129 title=self.title % repo_name,
149 title=self.title % repo_name,
130 link=url('summary_home', repo_name=repo_name,
150 link=url('summary_home', repo_name=repo_name,
131 qualified=True),
151 qualified=True),
132 description=self.description % repo_name,
152 description=self.description % repo_name,
133 language=self.language,
153 language=self.language,
134 ttl=self.ttl
154 ttl=self.ttl
135 )
155 )
136
156
137 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
157 for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
138 feed.add_item(title=self._get_title(cs),
158 feed.add_item(title=self._get_title(cs),
139 link=url('changeset_home', repo_name=repo_name,
159 link=url('changeset_home', repo_name=repo_name,
140 revision=cs.raw_id, qualified=True),
160 revision=cs.raw_id, qualified=True),
141 author_name=cs.author,
161 author_name=cs.author,
142 description=''.join(self.__get_desc(cs)),
162 description=''.join(self.__get_desc(cs)),
143 pubdate=cs.date,
163 pubdate=cs.date,
144 )
164 )
145
165
146 response.content_type = feed.mime_type
166 response.content_type = feed.mime_type
147 return feed.writeString('utf-8')
167 return feed.writeString('utf-8')
168
169 key = repo_name + '_RSS'
170 inv = CacheInvalidation.invalidate(key)
171 if inv is not None:
172 region_invalidate(_get_feed_from_cache, None, key)
173 CacheInvalidation.set_valid(inv.cache_key)
174 return _get_feed_from_cache(key)
175
@@ -1,557 +1,569 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import re
26 import re
27 import time
27 import time
28 import datetime
28 import datetime
29 import webob
29 import webob
30
30
31 from pylons.i18n.translation import _, ungettext
31 from pylons.i18n.translation import _, ungettext
32 from rhodecode.lib.vcs.utils.lazy import LazyProperty
32 from rhodecode.lib.vcs.utils.lazy import LazyProperty
33
33
34
34
35 def __get_lem():
35 def __get_lem():
36 """
36 """
37 Get language extension map based on what's inside pygments lexers
37 Get language extension map based on what's inside pygments lexers
38 """
38 """
39 from pygments import lexers
39 from pygments import lexers
40 from string import lower
40 from string import lower
41 from collections import defaultdict
41 from collections import defaultdict
42
42
43 d = defaultdict(lambda: [])
43 d = defaultdict(lambda: [])
44
44
45 def __clean(s):
45 def __clean(s):
46 s = s.lstrip('*')
46 s = s.lstrip('*')
47 s = s.lstrip('.')
47 s = s.lstrip('.')
48
48
49 if s.find('[') != -1:
49 if s.find('[') != -1:
50 exts = []
50 exts = []
51 start, stop = s.find('['), s.find(']')
51 start, stop = s.find('['), s.find(']')
52
52
53 for suffix in s[start + 1:stop]:
53 for suffix in s[start + 1:stop]:
54 exts.append(s[:s.find('[')] + suffix)
54 exts.append(s[:s.find('[')] + suffix)
55 return map(lower, exts)
55 return map(lower, exts)
56 else:
56 else:
57 return map(lower, [s])
57 return map(lower, [s])
58
58
59 for lx, t in sorted(lexers.LEXERS.items()):
59 for lx, t in sorted(lexers.LEXERS.items()):
60 m = map(__clean, t[-2])
60 m = map(__clean, t[-2])
61 if m:
61 if m:
62 m = reduce(lambda x, y: x + y, m)
62 m = reduce(lambda x, y: x + y, m)
63 for ext in m:
63 for ext in m:
64 desc = lx.replace('Lexer', '')
64 desc = lx.replace('Lexer', '')
65 d[ext].append(desc)
65 d[ext].append(desc)
66
66
67 return dict(d)
67 return dict(d)
68
68
69
69
70 def str2bool(_str):
70 def str2bool(_str):
71 """
71 """
72 returs True/False value from given string, it tries to translate the
72 returs True/False value from given string, it tries to translate the
73 string into boolean
73 string into boolean
74
74
75 :param _str: string value to translate into boolean
75 :param _str: string value to translate into boolean
76 :rtype: boolean
76 :rtype: boolean
77 :returns: boolean from given string
77 :returns: boolean from given string
78 """
78 """
79 if _str is None:
79 if _str is None:
80 return False
80 return False
81 if _str in (True, False):
81 if _str in (True, False):
82 return _str
82 return _str
83 _str = str(_str).strip().lower()
83 _str = str(_str).strip().lower()
84 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
84 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
85
85
86
86
87 def aslist(obj, sep=None, strip=True):
87 def aslist(obj, sep=None, strip=True):
88 """
88 """
89 Returns given string separated by sep as list
89 Returns given string separated by sep as list
90
90
91 :param obj:
91 :param obj:
92 :param sep:
92 :param sep:
93 :param strip:
93 :param strip:
94 """
94 """
95 if isinstance(obj, (basestring)):
95 if isinstance(obj, (basestring)):
96 lst = obj.split(sep)
96 lst = obj.split(sep)
97 if strip:
97 if strip:
98 lst = [v.strip() for v in lst]
98 lst = [v.strip() for v in lst]
99 return lst
99 return lst
100 elif isinstance(obj, (list, tuple)):
100 elif isinstance(obj, (list, tuple)):
101 return obj
101 return obj
102 elif obj is None:
102 elif obj is None:
103 return []
103 return []
104 else:
104 else:
105 return [obj]
105 return [obj]
106
106
107
107
108 def convert_line_endings(line, mode):
108 def convert_line_endings(line, mode):
109 """
109 """
110 Converts a given line "line end" accordingly to given mode
110 Converts a given line "line end" accordingly to given mode
111
111
112 Available modes are::
112 Available modes are::
113 0 - Unix
113 0 - Unix
114 1 - Mac
114 1 - Mac
115 2 - DOS
115 2 - DOS
116
116
117 :param line: given line to convert
117 :param line: given line to convert
118 :param mode: mode to convert to
118 :param mode: mode to convert to
119 :rtype: str
119 :rtype: str
120 :return: converted line according to mode
120 :return: converted line according to mode
121 """
121 """
122 from string import replace
122 from string import replace
123
123
124 if mode == 0:
124 if mode == 0:
125 line = replace(line, '\r\n', '\n')
125 line = replace(line, '\r\n', '\n')
126 line = replace(line, '\r', '\n')
126 line = replace(line, '\r', '\n')
127 elif mode == 1:
127 elif mode == 1:
128 line = replace(line, '\r\n', '\r')
128 line = replace(line, '\r\n', '\r')
129 line = replace(line, '\n', '\r')
129 line = replace(line, '\n', '\r')
130 elif mode == 2:
130 elif mode == 2:
131 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
131 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
132 return line
132 return line
133
133
134
134
135 def detect_mode(line, default):
135 def detect_mode(line, default):
136 """
136 """
137 Detects line break for given line, if line break couldn't be found
137 Detects line break for given line, if line break couldn't be found
138 given default value is returned
138 given default value is returned
139
139
140 :param line: str line
140 :param line: str line
141 :param default: default
141 :param default: default
142 :rtype: int
142 :rtype: int
143 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
143 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
144 """
144 """
145 if line.endswith('\r\n'):
145 if line.endswith('\r\n'):
146 return 2
146 return 2
147 elif line.endswith('\n'):
147 elif line.endswith('\n'):
148 return 0
148 return 0
149 elif line.endswith('\r'):
149 elif line.endswith('\r'):
150 return 1
150 return 1
151 else:
151 else:
152 return default
152 return default
153
153
154
154
155 def generate_api_key(username, salt=None):
155 def generate_api_key(username, salt=None):
156 """
156 """
157 Generates unique API key for given username, if salt is not given
157 Generates unique API key for given username, if salt is not given
158 it'll be generated from some random string
158 it'll be generated from some random string
159
159
160 :param username: username as string
160 :param username: username as string
161 :param salt: salt to hash generate KEY
161 :param salt: salt to hash generate KEY
162 :rtype: str
162 :rtype: str
163 :returns: sha1 hash from username+salt
163 :returns: sha1 hash from username+salt
164 """
164 """
165 from tempfile import _RandomNameSequence
165 from tempfile import _RandomNameSequence
166 import hashlib
166 import hashlib
167
167
168 if salt is None:
168 if salt is None:
169 salt = _RandomNameSequence().next()
169 salt = _RandomNameSequence().next()
170
170
171 return hashlib.sha1(username + salt).hexdigest()
171 return hashlib.sha1(username + salt).hexdigest()
172
172
173
173
174 def safe_int(val, default=None):
174 def safe_int(val, default=None):
175 """
175 """
176 Returns int() of val if val is not convertable to int use default
176 Returns int() of val if val is not convertable to int use default
177 instead
177 instead
178
178
179 :param val:
179 :param val:
180 :param default:
180 :param default:
181 """
181 """
182
182
183 try:
183 try:
184 val = int(val)
184 val = int(val)
185 except ValueError:
185 except ValueError:
186 val = default
186 val = default
187
187
188 return val
188 return val
189
189
190
190
191 def safe_unicode(str_, from_encoding=None):
191 def safe_unicode(str_, from_encoding=None):
192 """
192 """
193 safe unicode function. Does few trick to turn str_ into unicode
193 safe unicode function. Does few trick to turn str_ into unicode
194
194
195 In case of UnicodeDecode error we try to return it with encoding detected
195 In case of UnicodeDecode error we try to return it with encoding detected
196 by chardet library if it fails fallback to unicode with errors replaced
196 by chardet library if it fails fallback to unicode with errors replaced
197
197
198 :param str_: string to decode
198 :param str_: string to decode
199 :rtype: unicode
199 :rtype: unicode
200 :returns: unicode object
200 :returns: unicode object
201 """
201 """
202 if isinstance(str_, unicode):
202 if isinstance(str_, unicode):
203 return str_
203 return str_
204
204
205 if not from_encoding:
205 if not from_encoding:
206 import rhodecode
206 import rhodecode
207 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
207 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
208 'utf8'), sep=',')
208 'utf8'), sep=',')
209 from_encoding = DEFAULT_ENCODINGS
209 from_encoding = DEFAULT_ENCODINGS
210
210
211 if not isinstance(from_encoding, (list, tuple)):
211 if not isinstance(from_encoding, (list, tuple)):
212 from_encoding = [from_encoding]
212 from_encoding = [from_encoding]
213
213
214 try:
214 try:
215 return unicode(str_)
215 return unicode(str_)
216 except UnicodeDecodeError:
216 except UnicodeDecodeError:
217 pass
217 pass
218
218
219 for enc in from_encoding:
219 for enc in from_encoding:
220 try:
220 try:
221 return unicode(str_, enc)
221 return unicode(str_, enc)
222 except UnicodeDecodeError:
222 except UnicodeDecodeError:
223 pass
223 pass
224
224
225 try:
225 try:
226 import chardet
226 import chardet
227 encoding = chardet.detect(str_)['encoding']
227 encoding = chardet.detect(str_)['encoding']
228 if encoding is None:
228 if encoding is None:
229 raise Exception()
229 raise Exception()
230 return str_.decode(encoding)
230 return str_.decode(encoding)
231 except (ImportError, UnicodeDecodeError, Exception):
231 except (ImportError, UnicodeDecodeError, Exception):
232 return unicode(str_, from_encoding[0], 'replace')
232 return unicode(str_, from_encoding[0], 'replace')
233
233
234
234
235 def safe_str(unicode_, to_encoding=None):
235 def safe_str(unicode_, to_encoding=None):
236 """
236 """
237 safe str function. Does few trick to turn unicode_ into string
237 safe str function. Does few trick to turn unicode_ into string
238
238
239 In case of UnicodeEncodeError we try to return it with encoding detected
239 In case of UnicodeEncodeError we try to return it with encoding detected
240 by chardet library if it fails fallback to string with errors replaced
240 by chardet library if it fails fallback to string with errors replaced
241
241
242 :param unicode_: unicode to encode
242 :param unicode_: unicode to encode
243 :rtype: str
243 :rtype: str
244 :returns: str object
244 :returns: str object
245 """
245 """
246
246
247 # if it's not basestr cast to str
247 # if it's not basestr cast to str
248 if not isinstance(unicode_, basestring):
248 if not isinstance(unicode_, basestring):
249 return str(unicode_)
249 return str(unicode_)
250
250
251 if isinstance(unicode_, str):
251 if isinstance(unicode_, str):
252 return unicode_
252 return unicode_
253
253
254 if not to_encoding:
254 if not to_encoding:
255 import rhodecode
255 import rhodecode
256 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
256 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
257 'utf8'), sep=',')
257 'utf8'), sep=',')
258 to_encoding = DEFAULT_ENCODINGS
258 to_encoding = DEFAULT_ENCODINGS
259
259
260 if not isinstance(to_encoding, (list, tuple)):
260 if not isinstance(to_encoding, (list, tuple)):
261 to_encoding = [to_encoding]
261 to_encoding = [to_encoding]
262
262
263 for enc in to_encoding:
263 for enc in to_encoding:
264 try:
264 try:
265 return unicode_.encode(enc)
265 return unicode_.encode(enc)
266 except UnicodeEncodeError:
266 except UnicodeEncodeError:
267 pass
267 pass
268
268
269 try:
269 try:
270 import chardet
270 import chardet
271 encoding = chardet.detect(unicode_)['encoding']
271 encoding = chardet.detect(unicode_)['encoding']
272 if encoding is None:
272 if encoding is None:
273 raise UnicodeEncodeError()
273 raise UnicodeEncodeError()
274
274
275 return unicode_.encode(encoding)
275 return unicode_.encode(encoding)
276 except (ImportError, UnicodeEncodeError):
276 except (ImportError, UnicodeEncodeError):
277 return unicode_.encode(to_encoding[0], 'replace')
277 return unicode_.encode(to_encoding[0], 'replace')
278
278
279 return safe_str
279 return safe_str
280
280
281
281
282 def remove_suffix(s, suffix):
283 if s.endswith(suffix):
284 s = s[:-1 * len(suffix)]
285 return s
286
287
288 def remove_prefix(s, prefix):
289 if s.startswith(prefix):
290 s = s[:-1 * len(prefix)]
291 return s
292
293
282 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
294 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
283 """
295 """
284 Custom engine_from_config functions that makes sure we use NullPool for
296 Custom engine_from_config functions that makes sure we use NullPool for
285 file based sqlite databases. This prevents errors on sqlite. This only
297 file based sqlite databases. This prevents errors on sqlite. This only
286 applies to sqlalchemy versions < 0.7.0
298 applies to sqlalchemy versions < 0.7.0
287
299
288 """
300 """
289 import sqlalchemy
301 import sqlalchemy
290 from sqlalchemy import engine_from_config as efc
302 from sqlalchemy import engine_from_config as efc
291 import logging
303 import logging
292
304
293 if int(sqlalchemy.__version__.split('.')[1]) < 7:
305 if int(sqlalchemy.__version__.split('.')[1]) < 7:
294
306
295 # This solution should work for sqlalchemy < 0.7.0, and should use
307 # This solution should work for sqlalchemy < 0.7.0, and should use
296 # proxy=TimerProxy() for execution time profiling
308 # proxy=TimerProxy() for execution time profiling
297
309
298 from sqlalchemy.pool import NullPool
310 from sqlalchemy.pool import NullPool
299 url = configuration[prefix + 'url']
311 url = configuration[prefix + 'url']
300
312
301 if url.startswith('sqlite'):
313 if url.startswith('sqlite'):
302 kwargs.update({'poolclass': NullPool})
314 kwargs.update({'poolclass': NullPool})
303 return efc(configuration, prefix, **kwargs)
315 return efc(configuration, prefix, **kwargs)
304 else:
316 else:
305 import time
317 import time
306 from sqlalchemy import event
318 from sqlalchemy import event
307 from sqlalchemy.engine import Engine
319 from sqlalchemy.engine import Engine
308
320
309 log = logging.getLogger('sqlalchemy.engine')
321 log = logging.getLogger('sqlalchemy.engine')
310 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
322 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
311 engine = efc(configuration, prefix, **kwargs)
323 engine = efc(configuration, prefix, **kwargs)
312
324
313 def color_sql(sql):
325 def color_sql(sql):
314 COLOR_SEQ = "\033[1;%dm"
326 COLOR_SEQ = "\033[1;%dm"
315 COLOR_SQL = YELLOW
327 COLOR_SQL = YELLOW
316 normal = '\x1b[0m'
328 normal = '\x1b[0m'
317 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
329 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
318
330
319 if configuration['debug']:
331 if configuration['debug']:
320 #attach events only for debug configuration
332 #attach events only for debug configuration
321
333
322 def before_cursor_execute(conn, cursor, statement,
334 def before_cursor_execute(conn, cursor, statement,
323 parameters, context, executemany):
335 parameters, context, executemany):
324 context._query_start_time = time.time()
336 context._query_start_time = time.time()
325 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
337 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
326
338
327 def after_cursor_execute(conn, cursor, statement,
339 def after_cursor_execute(conn, cursor, statement,
328 parameters, context, executemany):
340 parameters, context, executemany):
329 total = time.time() - context._query_start_time
341 total = time.time() - context._query_start_time
330 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
342 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
331
343
332 event.listen(engine, "before_cursor_execute",
344 event.listen(engine, "before_cursor_execute",
333 before_cursor_execute)
345 before_cursor_execute)
334 event.listen(engine, "after_cursor_execute",
346 event.listen(engine, "after_cursor_execute",
335 after_cursor_execute)
347 after_cursor_execute)
336
348
337 return engine
349 return engine
338
350
339
351
340 def age(prevdate):
352 def age(prevdate):
341 """
353 """
342 turns a datetime into an age string.
354 turns a datetime into an age string.
343
355
344 :param prevdate: datetime object
356 :param prevdate: datetime object
345 :rtype: unicode
357 :rtype: unicode
346 :returns: unicode words describing age
358 :returns: unicode words describing age
347 """
359 """
348
360
349 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
361 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
350 deltas = {}
362 deltas = {}
351 future = False
363 future = False
352
364
353 # Get date parts deltas
365 # Get date parts deltas
354 now = datetime.datetime.now()
366 now = datetime.datetime.now()
355 if prevdate > now:
367 if prevdate > now:
356 now, prevdate = prevdate, now
368 now, prevdate = prevdate, now
357 future = True
369 future = True
358
370
359 for part in order:
371 for part in order:
360 deltas[part] = getattr(now, part) - getattr(prevdate, part)
372 deltas[part] = getattr(now, part) - getattr(prevdate, part)
361
373
362 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
374 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
363 # not 1 hour, -59 minutes and -59 seconds)
375 # not 1 hour, -59 minutes and -59 seconds)
364
376
365 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
377 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
366 part = order[num]
378 part = order[num]
367 carry_part = order[num - 1]
379 carry_part = order[num - 1]
368
380
369 if deltas[part] < 0:
381 if deltas[part] < 0:
370 deltas[part] += length
382 deltas[part] += length
371 deltas[carry_part] -= 1
383 deltas[carry_part] -= 1
372
384
373 # Same thing for days except that the increment depends on the (variable)
385 # Same thing for days except that the increment depends on the (variable)
374 # number of days in the month
386 # number of days in the month
375 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
387 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
376 if deltas['day'] < 0:
388 if deltas['day'] < 0:
377 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
389 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
378 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
390 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
379 deltas['day'] += 29
391 deltas['day'] += 29
380 else:
392 else:
381 deltas['day'] += month_lengths[prevdate.month - 1]
393 deltas['day'] += month_lengths[prevdate.month - 1]
382
394
383 deltas['month'] -= 1
395 deltas['month'] -= 1
384
396
385 if deltas['month'] < 0:
397 if deltas['month'] < 0:
386 deltas['month'] += 12
398 deltas['month'] += 12
387 deltas['year'] -= 1
399 deltas['year'] -= 1
388
400
389 # Format the result
401 # Format the result
390 fmt_funcs = {
402 fmt_funcs = {
391 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
403 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
392 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
404 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
393 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
405 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
394 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
406 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
395 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
407 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
396 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
408 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
397 }
409 }
398
410
399 for i, part in enumerate(order):
411 for i, part in enumerate(order):
400 value = deltas[part]
412 value = deltas[part]
401 if value == 0:
413 if value == 0:
402 continue
414 continue
403
415
404 if i < 5:
416 if i < 5:
405 sub_part = order[i + 1]
417 sub_part = order[i + 1]
406 sub_value = deltas[sub_part]
418 sub_value = deltas[sub_part]
407 else:
419 else:
408 sub_value = 0
420 sub_value = 0
409
421
410 if sub_value == 0:
422 if sub_value == 0:
411 if future:
423 if future:
412 return _(u'in %s') % fmt_funcs[part](value)
424 return _(u'in %s') % fmt_funcs[part](value)
413 else:
425 else:
414 return _(u'%s ago') % fmt_funcs[part](value)
426 return _(u'%s ago') % fmt_funcs[part](value)
415 if future:
427 if future:
416 return _(u'in %s and %s') % (fmt_funcs[part](value),
428 return _(u'in %s and %s') % (fmt_funcs[part](value),
417 fmt_funcs[sub_part](sub_value))
429 fmt_funcs[sub_part](sub_value))
418 else:
430 else:
419 return _(u'%s and %s ago') % (fmt_funcs[part](value),
431 return _(u'%s and %s ago') % (fmt_funcs[part](value),
420 fmt_funcs[sub_part](sub_value))
432 fmt_funcs[sub_part](sub_value))
421
433
422 return _(u'just now')
434 return _(u'just now')
423
435
424
436
425 def uri_filter(uri):
437 def uri_filter(uri):
426 """
438 """
427 Removes user:password from given url string
439 Removes user:password from given url string
428
440
429 :param uri:
441 :param uri:
430 :rtype: unicode
442 :rtype: unicode
431 :returns: filtered list of strings
443 :returns: filtered list of strings
432 """
444 """
433 if not uri:
445 if not uri:
434 return ''
446 return ''
435
447
436 proto = ''
448 proto = ''
437
449
438 for pat in ('https://', 'http://'):
450 for pat in ('https://', 'http://'):
439 if uri.startswith(pat):
451 if uri.startswith(pat):
440 uri = uri[len(pat):]
452 uri = uri[len(pat):]
441 proto = pat
453 proto = pat
442 break
454 break
443
455
444 # remove passwords and username
456 # remove passwords and username
445 uri = uri[uri.find('@') + 1:]
457 uri = uri[uri.find('@') + 1:]
446
458
447 # get the port
459 # get the port
448 cred_pos = uri.find(':')
460 cred_pos = uri.find(':')
449 if cred_pos == -1:
461 if cred_pos == -1:
450 host, port = uri, None
462 host, port = uri, None
451 else:
463 else:
452 host, port = uri[:cred_pos], uri[cred_pos + 1:]
464 host, port = uri[:cred_pos], uri[cred_pos + 1:]
453
465
454 return filter(None, [proto, host, port])
466 return filter(None, [proto, host, port])
455
467
456
468
457 def credentials_filter(uri):
469 def credentials_filter(uri):
458 """
470 """
459 Returns a url with removed credentials
471 Returns a url with removed credentials
460
472
461 :param uri:
473 :param uri:
462 """
474 """
463
475
464 uri = uri_filter(uri)
476 uri = uri_filter(uri)
465 #check if we have port
477 #check if we have port
466 if len(uri) > 2 and uri[2]:
478 if len(uri) > 2 and uri[2]:
467 uri[2] = ':' + uri[2]
479 uri[2] = ':' + uri[2]
468
480
469 return ''.join(uri)
481 return ''.join(uri)
470
482
471
483
472 def get_changeset_safe(repo, rev):
484 def get_changeset_safe(repo, rev):
473 """
485 """
474 Safe version of get_changeset if this changeset doesn't exists for a
486 Safe version of get_changeset if this changeset doesn't exists for a
475 repo it returns a Dummy one instead
487 repo it returns a Dummy one instead
476
488
477 :param repo:
489 :param repo:
478 :param rev:
490 :param rev:
479 """
491 """
480 from rhodecode.lib.vcs.backends.base import BaseRepository
492 from rhodecode.lib.vcs.backends.base import BaseRepository
481 from rhodecode.lib.vcs.exceptions import RepositoryError
493 from rhodecode.lib.vcs.exceptions import RepositoryError
482 from rhodecode.lib.vcs.backends.base import EmptyChangeset
494 from rhodecode.lib.vcs.backends.base import EmptyChangeset
483 if not isinstance(repo, BaseRepository):
495 if not isinstance(repo, BaseRepository):
484 raise Exception('You must pass an Repository '
496 raise Exception('You must pass an Repository '
485 'object as first argument got %s', type(repo))
497 'object as first argument got %s', type(repo))
486
498
487 try:
499 try:
488 cs = repo.get_changeset(rev)
500 cs = repo.get_changeset(rev)
489 except RepositoryError:
501 except RepositoryError:
490 cs = EmptyChangeset(requested_revision=rev)
502 cs = EmptyChangeset(requested_revision=rev)
491 return cs
503 return cs
492
504
493
505
494 def datetime_to_time(dt):
506 def datetime_to_time(dt):
495 if dt:
507 if dt:
496 return time.mktime(dt.timetuple())
508 return time.mktime(dt.timetuple())
497
509
498
510
499 def time_to_datetime(tm):
511 def time_to_datetime(tm):
500 if tm:
512 if tm:
501 if isinstance(tm, basestring):
513 if isinstance(tm, basestring):
502 try:
514 try:
503 tm = float(tm)
515 tm = float(tm)
504 except ValueError:
516 except ValueError:
505 return
517 return
506 return datetime.datetime.fromtimestamp(tm)
518 return datetime.datetime.fromtimestamp(tm)
507
519
508 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
520 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
509
521
510
522
511 def extract_mentioned_users(s):
523 def extract_mentioned_users(s):
512 """
524 """
513 Returns unique usernames from given string s that have @mention
525 Returns unique usernames from given string s that have @mention
514
526
515 :param s: string to get mentions
527 :param s: string to get mentions
516 """
528 """
517 usrs = set()
529 usrs = set()
518 for username in re.findall(MENTIONS_REGEX, s):
530 for username in re.findall(MENTIONS_REGEX, s):
519 usrs.add(username)
531 usrs.add(username)
520
532
521 return sorted(list(usrs), key=lambda k: k.lower())
533 return sorted(list(usrs), key=lambda k: k.lower())
522
534
523
535
524 class AttributeDict(dict):
536 class AttributeDict(dict):
525 def __getattr__(self, attr):
537 def __getattr__(self, attr):
526 return self.get(attr, None)
538 return self.get(attr, None)
527 __setattr__ = dict.__setitem__
539 __setattr__ = dict.__setitem__
528 __delattr__ = dict.__delitem__
540 __delattr__ = dict.__delitem__
529
541
530
542
531 def fix_PATH(os_=None):
543 def fix_PATH(os_=None):
532 """
544 """
533 Get current active python path, and append it to PATH variable to fix issues
545 Get current active python path, and append it to PATH variable to fix issues
534 of subprocess calls and different python versions
546 of subprocess calls and different python versions
535 """
547 """
536 import sys
548 import sys
537 if os_ is None:
549 if os_ is None:
538 import os
550 import os
539 else:
551 else:
540 os = os_
552 os = os_
541
553
542 cur_path = os.path.split(sys.executable)[0]
554 cur_path = os.path.split(sys.executable)[0]
543 if not os.environ['PATH'].startswith(cur_path):
555 if not os.environ['PATH'].startswith(cur_path):
544 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
556 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
545
557
546
558
547 def obfuscate_url_pw(engine):
559 def obfuscate_url_pw(engine):
548 from sqlalchemy.engine import url
560 from sqlalchemy.engine import url
549 url = url.make_url(engine)
561 url = url.make_url(engine)
550 if url.password:
562 if url.password:
551 url.password = 'XXXXX'
563 url.password = 'XXXXX'
552 return str(url)
564 return str(url)
553
565
554
566
555 def get_server_url(environ):
567 def get_server_url(environ):
556 req = webob.Request(environ)
568 req = webob.Request(environ)
557 return req.host_url + req.script_name
569 return req.host_url + req.script_name
@@ -1,1797 +1,1802 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47
47
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
48 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 safe_unicode
49 safe_unicode, remove_suffix
50 from rhodecode.lib.compat import json
50 from rhodecode.lib.compat import json
51 from rhodecode.lib.caching_query import FromCache
51 from rhodecode.lib.caching_query import FromCache
52
52
53 from rhodecode.model.meta import Base, Session
53 from rhodecode.model.meta import Base, Session
54
54
55 URL_SEP = '/'
55 URL_SEP = '/'
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58 #==============================================================================
58 #==============================================================================
59 # BASE CLASSES
59 # BASE CLASSES
60 #==============================================================================
60 #==============================================================================
61
61
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
62 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63
63
64
64
65 class BaseModel(object):
65 class BaseModel(object):
66 """
66 """
67 Base Model for all classess
67 Base Model for all classess
68 """
68 """
69
69
70 @classmethod
70 @classmethod
71 def _get_keys(cls):
71 def _get_keys(cls):
72 """return column names for this model """
72 """return column names for this model """
73 return class_mapper(cls).c.keys()
73 return class_mapper(cls).c.keys()
74
74
75 def get_dict(self):
75 def get_dict(self):
76 """
76 """
77 return dict with keys and values corresponding
77 return dict with keys and values corresponding
78 to this model data """
78 to this model data """
79
79
80 d = {}
80 d = {}
81 for k in self._get_keys():
81 for k in self._get_keys():
82 d[k] = getattr(self, k)
82 d[k] = getattr(self, k)
83
83
84 # also use __json__() if present to get additional fields
84 # also use __json__() if present to get additional fields
85 _json_attr = getattr(self, '__json__', None)
85 _json_attr = getattr(self, '__json__', None)
86 if _json_attr:
86 if _json_attr:
87 # update with attributes from __json__
87 # update with attributes from __json__
88 if callable(_json_attr):
88 if callable(_json_attr):
89 _json_attr = _json_attr()
89 _json_attr = _json_attr()
90 for k, val in _json_attr.iteritems():
90 for k, val in _json_attr.iteritems():
91 d[k] = val
91 d[k] = val
92 return d
92 return d
93
93
94 def get_appstruct(self):
94 def get_appstruct(self):
95 """return list with keys and values tupples corresponding
95 """return list with keys and values tupples corresponding
96 to this model data """
96 to this model data """
97
97
98 l = []
98 l = []
99 for k in self._get_keys():
99 for k in self._get_keys():
100 l.append((k, getattr(self, k),))
100 l.append((k, getattr(self, k),))
101 return l
101 return l
102
102
103 def populate_obj(self, populate_dict):
103 def populate_obj(self, populate_dict):
104 """populate model with data from given populate_dict"""
104 """populate model with data from given populate_dict"""
105
105
106 for k in self._get_keys():
106 for k in self._get_keys():
107 if k in populate_dict:
107 if k in populate_dict:
108 setattr(self, k, populate_dict[k])
108 setattr(self, k, populate_dict[k])
109
109
110 @classmethod
110 @classmethod
111 def query(cls):
111 def query(cls):
112 return Session().query(cls)
112 return Session().query(cls)
113
113
114 @classmethod
114 @classmethod
115 def get(cls, id_):
115 def get(cls, id_):
116 if id_:
116 if id_:
117 return cls.query().get(id_)
117 return cls.query().get(id_)
118
118
119 @classmethod
119 @classmethod
120 def get_or_404(cls, id_):
120 def get_or_404(cls, id_):
121 try:
121 try:
122 id_ = int(id_)
122 id_ = int(id_)
123 except (TypeError, ValueError):
123 except (TypeError, ValueError):
124 raise HTTPNotFound
124 raise HTTPNotFound
125
125
126 res = cls.query().get(id_)
126 res = cls.query().get(id_)
127 if not res:
127 if not res:
128 raise HTTPNotFound
128 raise HTTPNotFound
129 return res
129 return res
130
130
131 @classmethod
131 @classmethod
132 def getAll(cls):
132 def getAll(cls):
133 return cls.query().all()
133 return cls.query().all()
134
134
135 @classmethod
135 @classmethod
136 def delete(cls, id_):
136 def delete(cls, id_):
137 obj = cls.query().get(id_)
137 obj = cls.query().get(id_)
138 Session().delete(obj)
138 Session().delete(obj)
139
139
140 def __repr__(self):
140 def __repr__(self):
141 if hasattr(self, '__unicode__'):
141 if hasattr(self, '__unicode__'):
142 # python repr needs to return str
142 # python repr needs to return str
143 return safe_str(self.__unicode__())
143 return safe_str(self.__unicode__())
144 return '<DB:%s>' % (self.__class__.__name__)
144 return '<DB:%s>' % (self.__class__.__name__)
145
145
146
146
147 class RhodeCodeSetting(Base, BaseModel):
147 class RhodeCodeSetting(Base, BaseModel):
148 __tablename__ = 'rhodecode_settings'
148 __tablename__ = 'rhodecode_settings'
149 __table_args__ = (
149 __table_args__ = (
150 UniqueConstraint('app_settings_name'),
150 UniqueConstraint('app_settings_name'),
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
151 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 'mysql_charset': 'utf8'}
152 'mysql_charset': 'utf8'}
153 )
153 )
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
154 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
155 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157
157
158 def __init__(self, k='', v=''):
158 def __init__(self, k='', v=''):
159 self.app_settings_name = k
159 self.app_settings_name = k
160 self.app_settings_value = v
160 self.app_settings_value = v
161
161
162 @validates('_app_settings_value')
162 @validates('_app_settings_value')
163 def validate_settings_value(self, key, val):
163 def validate_settings_value(self, key, val):
164 assert type(val) == unicode
164 assert type(val) == unicode
165 return val
165 return val
166
166
167 @hybrid_property
167 @hybrid_property
168 def app_settings_value(self):
168 def app_settings_value(self):
169 v = self._app_settings_value
169 v = self._app_settings_value
170 if self.app_settings_name == 'ldap_active':
170 if self.app_settings_name == 'ldap_active':
171 v = str2bool(v)
171 v = str2bool(v)
172 return v
172 return v
173
173
174 @app_settings_value.setter
174 @app_settings_value.setter
175 def app_settings_value(self, val):
175 def app_settings_value(self, val):
176 """
176 """
177 Setter that will always make sure we use unicode in app_settings_value
177 Setter that will always make sure we use unicode in app_settings_value
178
178
179 :param val:
179 :param val:
180 """
180 """
181 self._app_settings_value = safe_unicode(val)
181 self._app_settings_value = safe_unicode(val)
182
182
183 def __unicode__(self):
183 def __unicode__(self):
184 return u"<%s('%s:%s')>" % (
184 return u"<%s('%s:%s')>" % (
185 self.__class__.__name__,
185 self.__class__.__name__,
186 self.app_settings_name, self.app_settings_value
186 self.app_settings_name, self.app_settings_value
187 )
187 )
188
188
189 @classmethod
189 @classmethod
190 def get_by_name(cls, key):
190 def get_by_name(cls, key):
191 return cls.query()\
191 return cls.query()\
192 .filter(cls.app_settings_name == key).scalar()
192 .filter(cls.app_settings_name == key).scalar()
193
193
194 @classmethod
194 @classmethod
195 def get_by_name_or_create(cls, key):
195 def get_by_name_or_create(cls, key):
196 res = cls.get_by_name(key)
196 res = cls.get_by_name(key)
197 if not res:
197 if not res:
198 res = cls(key)
198 res = cls(key)
199 return res
199 return res
200
200
201 @classmethod
201 @classmethod
202 def get_app_settings(cls, cache=False):
202 def get_app_settings(cls, cache=False):
203
203
204 ret = cls.query()
204 ret = cls.query()
205
205
206 if cache:
206 if cache:
207 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
207 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
208
208
209 if not ret:
209 if not ret:
210 raise Exception('Could not get application settings !')
210 raise Exception('Could not get application settings !')
211 settings = {}
211 settings = {}
212 for each in ret:
212 for each in ret:
213 settings['rhodecode_' + each.app_settings_name] = \
213 settings['rhodecode_' + each.app_settings_name] = \
214 each.app_settings_value
214 each.app_settings_value
215
215
216 return settings
216 return settings
217
217
218 @classmethod
218 @classmethod
219 def get_ldap_settings(cls, cache=False):
219 def get_ldap_settings(cls, cache=False):
220 ret = cls.query()\
220 ret = cls.query()\
221 .filter(cls.app_settings_name.startswith('ldap_')).all()
221 .filter(cls.app_settings_name.startswith('ldap_')).all()
222 fd = {}
222 fd = {}
223 for row in ret:
223 for row in ret:
224 fd.update({row.app_settings_name: row.app_settings_value})
224 fd.update({row.app_settings_name: row.app_settings_value})
225
225
226 return fd
226 return fd
227
227
228
228
229 class RhodeCodeUi(Base, BaseModel):
229 class RhodeCodeUi(Base, BaseModel):
230 __tablename__ = 'rhodecode_ui'
230 __tablename__ = 'rhodecode_ui'
231 __table_args__ = (
231 __table_args__ = (
232 UniqueConstraint('ui_key'),
232 UniqueConstraint('ui_key'),
233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
233 {'extend_existing': True, 'mysql_engine': 'InnoDB',
234 'mysql_charset': 'utf8'}
234 'mysql_charset': 'utf8'}
235 )
235 )
236
236
237 HOOK_UPDATE = 'changegroup.update'
237 HOOK_UPDATE = 'changegroup.update'
238 HOOK_REPO_SIZE = 'changegroup.repo_size'
238 HOOK_REPO_SIZE = 'changegroup.repo_size'
239 HOOK_PUSH = 'changegroup.push_logger'
239 HOOK_PUSH = 'changegroup.push_logger'
240 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
240 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
241 HOOK_PULL = 'outgoing.pull_logger'
241 HOOK_PULL = 'outgoing.pull_logger'
242 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
242 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
243
243
244 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
248 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
249
249
250 @classmethod
250 @classmethod
251 def get_by_key(cls, key):
251 def get_by_key(cls, key):
252 return cls.query().filter(cls.ui_key == key).scalar()
252 return cls.query().filter(cls.ui_key == key).scalar()
253
253
254 @classmethod
254 @classmethod
255 def get_builtin_hooks(cls):
255 def get_builtin_hooks(cls):
256 q = cls.query()
256 q = cls.query()
257 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
257 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
258 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
258 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
259 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
259 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
260 return q.all()
260 return q.all()
261
261
262 @classmethod
262 @classmethod
263 def get_custom_hooks(cls):
263 def get_custom_hooks(cls):
264 q = cls.query()
264 q = cls.query()
265 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
265 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
266 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
266 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
267 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
267 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
268 q = q.filter(cls.ui_section == 'hooks')
268 q = q.filter(cls.ui_section == 'hooks')
269 return q.all()
269 return q.all()
270
270
271 @classmethod
271 @classmethod
272 def get_repos_location(cls):
272 def get_repos_location(cls):
273 return cls.get_by_key('/').ui_value
273 return cls.get_by_key('/').ui_value
274
274
275 @classmethod
275 @classmethod
276 def create_or_update_hook(cls, key, val):
276 def create_or_update_hook(cls, key, val):
277 new_ui = cls.get_by_key(key) or cls()
277 new_ui = cls.get_by_key(key) or cls()
278 new_ui.ui_section = 'hooks'
278 new_ui.ui_section = 'hooks'
279 new_ui.ui_active = True
279 new_ui.ui_active = True
280 new_ui.ui_key = key
280 new_ui.ui_key = key
281 new_ui.ui_value = val
281 new_ui.ui_value = val
282
282
283 Session().add(new_ui)
283 Session().add(new_ui)
284
284
285 def __repr__(self):
285 def __repr__(self):
286 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
286 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
287 self.ui_value)
287 self.ui_value)
288
288
289
289
290 class User(Base, BaseModel):
290 class User(Base, BaseModel):
291 __tablename__ = 'users'
291 __tablename__ = 'users'
292 __table_args__ = (
292 __table_args__ = (
293 UniqueConstraint('username'), UniqueConstraint('email'),
293 UniqueConstraint('username'), UniqueConstraint('email'),
294 Index('u_username_idx', 'username'),
294 Index('u_username_idx', 'username'),
295 Index('u_email_idx', 'email'),
295 Index('u_email_idx', 'email'),
296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
296 {'extend_existing': True, 'mysql_engine': 'InnoDB',
297 'mysql_charset': 'utf8'}
297 'mysql_charset': 'utf8'}
298 )
298 )
299 DEFAULT_USER = 'default'
299 DEFAULT_USER = 'default'
300 DEFAULT_PERMISSIONS = [
300 DEFAULT_PERMISSIONS = [
301 'hg.register.manual_activate', 'hg.create.repository',
301 'hg.register.manual_activate', 'hg.create.repository',
302 'hg.fork.repository', 'repository.read'
302 'hg.fork.repository', 'repository.read'
303 ]
303 ]
304 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
304 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
305 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
305 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
306 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
307 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
307 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
308 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
308 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
309 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
309 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
310 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
310 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
311 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
312 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
312 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
313 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
313 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
314 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
314 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
315 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
315 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
316
316
317 user_log = relationship('UserLog', cascade='all')
317 user_log = relationship('UserLog', cascade='all')
318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
318 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
319
319
320 repositories = relationship('Repository')
320 repositories = relationship('Repository')
321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
321 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
322 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
323 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
324
324
325 group_member = relationship('UsersGroupMember', cascade='all')
325 group_member = relationship('UsersGroupMember', cascade='all')
326
326
327 notifications = relationship('UserNotification', cascade='all')
327 notifications = relationship('UserNotification', cascade='all')
328 # notifications assigned to this user
328 # notifications assigned to this user
329 user_created_notifications = relationship('Notification', cascade='all')
329 user_created_notifications = relationship('Notification', cascade='all')
330 # comments created by this user
330 # comments created by this user
331 user_comments = relationship('ChangesetComment', cascade='all')
331 user_comments = relationship('ChangesetComment', cascade='all')
332 #extra emails for this user
332 #extra emails for this user
333 user_emails = relationship('UserEmailMap', cascade='all')
333 user_emails = relationship('UserEmailMap', cascade='all')
334
334
335 @hybrid_property
335 @hybrid_property
336 def email(self):
336 def email(self):
337 return self._email
337 return self._email
338
338
339 @email.setter
339 @email.setter
340 def email(self, val):
340 def email(self, val):
341 self._email = val.lower() if val else None
341 self._email = val.lower() if val else None
342
342
343 @property
343 @property
344 def firstname(self):
344 def firstname(self):
345 # alias for future
345 # alias for future
346 return self.name
346 return self.name
347
347
348 @property
348 @property
349 def emails(self):
349 def emails(self):
350 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
350 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
351 return [self.email] + [x.email for x in other]
351 return [self.email] + [x.email for x in other]
352
352
353 @property
353 @property
354 def username_and_name(self):
354 def username_and_name(self):
355 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
355 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
356
356
357 @property
357 @property
358 def full_name(self):
358 def full_name(self):
359 return '%s %s' % (self.firstname, self.lastname)
359 return '%s %s' % (self.firstname, self.lastname)
360
360
361 @property
361 @property
362 def full_name_or_username(self):
362 def full_name_or_username(self):
363 return ('%s %s' % (self.firstname, self.lastname)
363 return ('%s %s' % (self.firstname, self.lastname)
364 if (self.firstname and self.lastname) else self.username)
364 if (self.firstname and self.lastname) else self.username)
365
365
366 @property
366 @property
367 def full_contact(self):
367 def full_contact(self):
368 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
368 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
369
369
370 @property
370 @property
371 def short_contact(self):
371 def short_contact(self):
372 return '%s %s' % (self.firstname, self.lastname)
372 return '%s %s' % (self.firstname, self.lastname)
373
373
374 @property
374 @property
375 def is_admin(self):
375 def is_admin(self):
376 return self.admin
376 return self.admin
377
377
378 def __unicode__(self):
378 def __unicode__(self):
379 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
379 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
380 self.user_id, self.username)
380 self.user_id, self.username)
381
381
382 @classmethod
382 @classmethod
383 def get_by_username(cls, username, case_insensitive=False, cache=False):
383 def get_by_username(cls, username, case_insensitive=False, cache=False):
384 if case_insensitive:
384 if case_insensitive:
385 q = cls.query().filter(cls.username.ilike(username))
385 q = cls.query().filter(cls.username.ilike(username))
386 else:
386 else:
387 q = cls.query().filter(cls.username == username)
387 q = cls.query().filter(cls.username == username)
388
388
389 if cache:
389 if cache:
390 q = q.options(FromCache(
390 q = q.options(FromCache(
391 "sql_cache_short",
391 "sql_cache_short",
392 "get_user_%s" % _hash_key(username)
392 "get_user_%s" % _hash_key(username)
393 )
393 )
394 )
394 )
395 return q.scalar()
395 return q.scalar()
396
396
397 @classmethod
397 @classmethod
398 def get_by_api_key(cls, api_key, cache=False):
398 def get_by_api_key(cls, api_key, cache=False):
399 q = cls.query().filter(cls.api_key == api_key)
399 q = cls.query().filter(cls.api_key == api_key)
400
400
401 if cache:
401 if cache:
402 q = q.options(FromCache("sql_cache_short",
402 q = q.options(FromCache("sql_cache_short",
403 "get_api_key_%s" % api_key))
403 "get_api_key_%s" % api_key))
404 return q.scalar()
404 return q.scalar()
405
405
406 @classmethod
406 @classmethod
407 def get_by_email(cls, email, case_insensitive=False, cache=False):
407 def get_by_email(cls, email, case_insensitive=False, cache=False):
408 if case_insensitive:
408 if case_insensitive:
409 q = cls.query().filter(cls.email.ilike(email))
409 q = cls.query().filter(cls.email.ilike(email))
410 else:
410 else:
411 q = cls.query().filter(cls.email == email)
411 q = cls.query().filter(cls.email == email)
412
412
413 if cache:
413 if cache:
414 q = q.options(FromCache("sql_cache_short",
414 q = q.options(FromCache("sql_cache_short",
415 "get_email_key_%s" % email))
415 "get_email_key_%s" % email))
416
416
417 ret = q.scalar()
417 ret = q.scalar()
418 if ret is None:
418 if ret is None:
419 q = UserEmailMap.query()
419 q = UserEmailMap.query()
420 # try fetching in alternate email map
420 # try fetching in alternate email map
421 if case_insensitive:
421 if case_insensitive:
422 q = q.filter(UserEmailMap.email.ilike(email))
422 q = q.filter(UserEmailMap.email.ilike(email))
423 else:
423 else:
424 q = q.filter(UserEmailMap.email == email)
424 q = q.filter(UserEmailMap.email == email)
425 q = q.options(joinedload(UserEmailMap.user))
425 q = q.options(joinedload(UserEmailMap.user))
426 if cache:
426 if cache:
427 q = q.options(FromCache("sql_cache_short",
427 q = q.options(FromCache("sql_cache_short",
428 "get_email_map_key_%s" % email))
428 "get_email_map_key_%s" % email))
429 ret = getattr(q.scalar(), 'user', None)
429 ret = getattr(q.scalar(), 'user', None)
430
430
431 return ret
431 return ret
432
432
433 def update_lastlogin(self):
433 def update_lastlogin(self):
434 """Update user lastlogin"""
434 """Update user lastlogin"""
435 self.last_login = datetime.datetime.now()
435 self.last_login = datetime.datetime.now()
436 Session().add(self)
436 Session().add(self)
437 log.debug('updated user %s lastlogin' % self.username)
437 log.debug('updated user %s lastlogin' % self.username)
438
438
439 def get_api_data(self):
439 def get_api_data(self):
440 """
440 """
441 Common function for generating user related data for API
441 Common function for generating user related data for API
442 """
442 """
443 user = self
443 user = self
444 data = dict(
444 data = dict(
445 user_id=user.user_id,
445 user_id=user.user_id,
446 username=user.username,
446 username=user.username,
447 firstname=user.name,
447 firstname=user.name,
448 lastname=user.lastname,
448 lastname=user.lastname,
449 email=user.email,
449 email=user.email,
450 emails=user.emails,
450 emails=user.emails,
451 api_key=user.api_key,
451 api_key=user.api_key,
452 active=user.active,
452 active=user.active,
453 admin=user.admin,
453 admin=user.admin,
454 ldap_dn=user.ldap_dn,
454 ldap_dn=user.ldap_dn,
455 last_login=user.last_login,
455 last_login=user.last_login,
456 )
456 )
457 return data
457 return data
458
458
459 def __json__(self):
459 def __json__(self):
460 data = dict(
460 data = dict(
461 full_name=self.full_name,
461 full_name=self.full_name,
462 full_name_or_username=self.full_name_or_username,
462 full_name_or_username=self.full_name_or_username,
463 short_contact=self.short_contact,
463 short_contact=self.short_contact,
464 full_contact=self.full_contact
464 full_contact=self.full_contact
465 )
465 )
466 data.update(self.get_api_data())
466 data.update(self.get_api_data())
467 return data
467 return data
468
468
469
469
470 class UserEmailMap(Base, BaseModel):
470 class UserEmailMap(Base, BaseModel):
471 __tablename__ = 'user_email_map'
471 __tablename__ = 'user_email_map'
472 __table_args__ = (
472 __table_args__ = (
473 Index('uem_email_idx', 'email'),
473 Index('uem_email_idx', 'email'),
474 UniqueConstraint('email'),
474 UniqueConstraint('email'),
475 {'extend_existing': True, 'mysql_engine': 'InnoDB',
475 {'extend_existing': True, 'mysql_engine': 'InnoDB',
476 'mysql_charset': 'utf8'}
476 'mysql_charset': 'utf8'}
477 )
477 )
478 __mapper_args__ = {}
478 __mapper_args__ = {}
479
479
480 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
480 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
481 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
481 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
482 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
482 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
483 user = relationship('User', lazy='joined')
483 user = relationship('User', lazy='joined')
484
484
485 @validates('_email')
485 @validates('_email')
486 def validate_email(self, key, email):
486 def validate_email(self, key, email):
487 # check if this email is not main one
487 # check if this email is not main one
488 main_email = Session().query(User).filter(User.email == email).scalar()
488 main_email = Session().query(User).filter(User.email == email).scalar()
489 if main_email is not None:
489 if main_email is not None:
490 raise AttributeError('email %s is present is user table' % email)
490 raise AttributeError('email %s is present is user table' % email)
491 return email
491 return email
492
492
493 @hybrid_property
493 @hybrid_property
494 def email(self):
494 def email(self):
495 return self._email
495 return self._email
496
496
497 @email.setter
497 @email.setter
498 def email(self, val):
498 def email(self, val):
499 self._email = val.lower() if val else None
499 self._email = val.lower() if val else None
500
500
501
501
502 class UserLog(Base, BaseModel):
502 class UserLog(Base, BaseModel):
503 __tablename__ = 'user_logs'
503 __tablename__ = 'user_logs'
504 __table_args__ = (
504 __table_args__ = (
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
505 {'extend_existing': True, 'mysql_engine': 'InnoDB',
506 'mysql_charset': 'utf8'},
506 'mysql_charset': 'utf8'},
507 )
507 )
508 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
508 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
509 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
510 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
510 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
511 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
511 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
512 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
512 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
513 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
513 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
514 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
514 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
515
515
516 @property
516 @property
517 def action_as_day(self):
517 def action_as_day(self):
518 return datetime.date(*self.action_date.timetuple()[:3])
518 return datetime.date(*self.action_date.timetuple()[:3])
519
519
520 user = relationship('User')
520 user = relationship('User')
521 repository = relationship('Repository', cascade='')
521 repository = relationship('Repository', cascade='')
522
522
523
523
524 class UsersGroup(Base, BaseModel):
524 class UsersGroup(Base, BaseModel):
525 __tablename__ = 'users_groups'
525 __tablename__ = 'users_groups'
526 __table_args__ = (
526 __table_args__ = (
527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
527 {'extend_existing': True, 'mysql_engine': 'InnoDB',
528 'mysql_charset': 'utf8'},
528 'mysql_charset': 'utf8'},
529 )
529 )
530
530
531 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
531 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
532 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
532 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
533 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
533 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
534 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
534 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
535
535
536 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
536 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
537 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
537 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
538 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
538 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
539
539
540 def __unicode__(self):
540 def __unicode__(self):
541 return u'<userGroup(%s)>' % (self.users_group_name)
541 return u'<userGroup(%s)>' % (self.users_group_name)
542
542
543 @classmethod
543 @classmethod
544 def get_by_group_name(cls, group_name, cache=False,
544 def get_by_group_name(cls, group_name, cache=False,
545 case_insensitive=False):
545 case_insensitive=False):
546 if case_insensitive:
546 if case_insensitive:
547 q = cls.query().filter(cls.users_group_name.ilike(group_name))
547 q = cls.query().filter(cls.users_group_name.ilike(group_name))
548 else:
548 else:
549 q = cls.query().filter(cls.users_group_name == group_name)
549 q = cls.query().filter(cls.users_group_name == group_name)
550 if cache:
550 if cache:
551 q = q.options(FromCache(
551 q = q.options(FromCache(
552 "sql_cache_short",
552 "sql_cache_short",
553 "get_user_%s" % _hash_key(group_name)
553 "get_user_%s" % _hash_key(group_name)
554 )
554 )
555 )
555 )
556 return q.scalar()
556 return q.scalar()
557
557
558 @classmethod
558 @classmethod
559 def get(cls, users_group_id, cache=False):
559 def get(cls, users_group_id, cache=False):
560 users_group = cls.query()
560 users_group = cls.query()
561 if cache:
561 if cache:
562 users_group = users_group.options(FromCache("sql_cache_short",
562 users_group = users_group.options(FromCache("sql_cache_short",
563 "get_users_group_%s" % users_group_id))
563 "get_users_group_%s" % users_group_id))
564 return users_group.get(users_group_id)
564 return users_group.get(users_group_id)
565
565
566 def get_api_data(self):
566 def get_api_data(self):
567 users_group = self
567 users_group = self
568
568
569 data = dict(
569 data = dict(
570 users_group_id=users_group.users_group_id,
570 users_group_id=users_group.users_group_id,
571 group_name=users_group.users_group_name,
571 group_name=users_group.users_group_name,
572 active=users_group.users_group_active,
572 active=users_group.users_group_active,
573 )
573 )
574
574
575 return data
575 return data
576
576
577
577
578 class UsersGroupMember(Base, BaseModel):
578 class UsersGroupMember(Base, BaseModel):
579 __tablename__ = 'users_groups_members'
579 __tablename__ = 'users_groups_members'
580 __table_args__ = (
580 __table_args__ = (
581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
581 {'extend_existing': True, 'mysql_engine': 'InnoDB',
582 'mysql_charset': 'utf8'},
582 'mysql_charset': 'utf8'},
583 )
583 )
584
584
585 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
585 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
586 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
586 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
587 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
588
588
589 user = relationship('User', lazy='joined')
589 user = relationship('User', lazy='joined')
590 users_group = relationship('UsersGroup')
590 users_group = relationship('UsersGroup')
591
591
592 def __init__(self, gr_id='', u_id=''):
592 def __init__(self, gr_id='', u_id=''):
593 self.users_group_id = gr_id
593 self.users_group_id = gr_id
594 self.user_id = u_id
594 self.user_id = u_id
595
595
596
596
597 class Repository(Base, BaseModel):
597 class Repository(Base, BaseModel):
598 __tablename__ = 'repositories'
598 __tablename__ = 'repositories'
599 __table_args__ = (
599 __table_args__ = (
600 UniqueConstraint('repo_name'),
600 UniqueConstraint('repo_name'),
601 Index('r_repo_name_idx', 'repo_name'),
601 Index('r_repo_name_idx', 'repo_name'),
602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
603 'mysql_charset': 'utf8'},
603 'mysql_charset': 'utf8'},
604 )
604 )
605
605
606 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
606 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
607 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
607 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
608 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
608 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
609 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
609 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
611 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
611 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
612 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
612 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
613 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
613 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
614 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
614 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
615 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
615 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
616 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
616 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
617 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
617 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
618 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
618 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
619 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
619 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
620
620
621 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
621 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
622 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
622 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
623
623
624 user = relationship('User')
624 user = relationship('User')
625 fork = relationship('Repository', remote_side=repo_id)
625 fork = relationship('Repository', remote_side=repo_id)
626 group = relationship('RepoGroup')
626 group = relationship('RepoGroup')
627 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
627 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
628 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
628 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
629 stats = relationship('Statistics', cascade='all', uselist=False)
629 stats = relationship('Statistics', cascade='all', uselist=False)
630
630
631 followers = relationship('UserFollowing',
631 followers = relationship('UserFollowing',
632 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
632 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
633 cascade='all')
633 cascade='all')
634
634
635 logs = relationship('UserLog')
635 logs = relationship('UserLog')
636 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
636 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
637
637
638 pull_requests_org = relationship('PullRequest',
638 pull_requests_org = relationship('PullRequest',
639 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
639 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
640 cascade="all, delete, delete-orphan")
640 cascade="all, delete, delete-orphan")
641
641
642 pull_requests_other = relationship('PullRequest',
642 pull_requests_other = relationship('PullRequest',
643 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
643 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
644 cascade="all, delete, delete-orphan")
644 cascade="all, delete, delete-orphan")
645
645
646 def __unicode__(self):
646 def __unicode__(self):
647 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
647 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
648 self.repo_name)
648 self.repo_name)
649
649
650 @hybrid_property
650 @hybrid_property
651 def locked(self):
651 def locked(self):
652 # always should return [user_id, timelocked]
652 # always should return [user_id, timelocked]
653 if self._locked:
653 if self._locked:
654 _lock_info = self._locked.split(':')
654 _lock_info = self._locked.split(':')
655 return int(_lock_info[0]), _lock_info[1]
655 return int(_lock_info[0]), _lock_info[1]
656 return [None, None]
656 return [None, None]
657
657
658 @locked.setter
658 @locked.setter
659 def locked(self, val):
659 def locked(self, val):
660 if val and isinstance(val, (list, tuple)):
660 if val and isinstance(val, (list, tuple)):
661 self._locked = ':'.join(map(str, val))
661 self._locked = ':'.join(map(str, val))
662 else:
662 else:
663 self._locked = None
663 self._locked = None
664
664
665 @classmethod
665 @classmethod
666 def url_sep(cls):
666 def url_sep(cls):
667 return URL_SEP
667 return URL_SEP
668
668
669 @classmethod
669 @classmethod
670 def get_by_repo_name(cls, repo_name):
670 def get_by_repo_name(cls, repo_name):
671 q = Session().query(cls).filter(cls.repo_name == repo_name)
671 q = Session().query(cls).filter(cls.repo_name == repo_name)
672 q = q.options(joinedload(Repository.fork))\
672 q = q.options(joinedload(Repository.fork))\
673 .options(joinedload(Repository.user))\
673 .options(joinedload(Repository.user))\
674 .options(joinedload(Repository.group))
674 .options(joinedload(Repository.group))
675 return q.scalar()
675 return q.scalar()
676
676
677 @classmethod
677 @classmethod
678 def get_by_full_path(cls, repo_full_path):
678 def get_by_full_path(cls, repo_full_path):
679 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
679 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
680 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
680 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
681
681
682 @classmethod
682 @classmethod
683 def get_repo_forks(cls, repo_id):
683 def get_repo_forks(cls, repo_id):
684 return cls.query().filter(Repository.fork_id == repo_id)
684 return cls.query().filter(Repository.fork_id == repo_id)
685
685
686 @classmethod
686 @classmethod
687 def base_path(cls):
687 def base_path(cls):
688 """
688 """
689 Returns base path when all repos are stored
689 Returns base path when all repos are stored
690
690
691 :param cls:
691 :param cls:
692 """
692 """
693 q = Session().query(RhodeCodeUi)\
693 q = Session().query(RhodeCodeUi)\
694 .filter(RhodeCodeUi.ui_key == cls.url_sep())
694 .filter(RhodeCodeUi.ui_key == cls.url_sep())
695 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
695 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
696 return q.one().ui_value
696 return q.one().ui_value
697
697
698 @property
698 @property
699 def forks(self):
699 def forks(self):
700 """
700 """
701 Return forks of this repo
701 Return forks of this repo
702 """
702 """
703 return Repository.get_repo_forks(self.repo_id)
703 return Repository.get_repo_forks(self.repo_id)
704
704
705 @property
705 @property
706 def parent(self):
706 def parent(self):
707 """
707 """
708 Returns fork parent
708 Returns fork parent
709 """
709 """
710 return self.fork
710 return self.fork
711
711
712 @property
712 @property
713 def just_name(self):
713 def just_name(self):
714 return self.repo_name.split(Repository.url_sep())[-1]
714 return self.repo_name.split(Repository.url_sep())[-1]
715
715
716 @property
716 @property
717 def groups_with_parents(self):
717 def groups_with_parents(self):
718 groups = []
718 groups = []
719 if self.group is None:
719 if self.group is None:
720 return groups
720 return groups
721
721
722 cur_gr = self.group
722 cur_gr = self.group
723 groups.insert(0, cur_gr)
723 groups.insert(0, cur_gr)
724 while 1:
724 while 1:
725 gr = getattr(cur_gr, 'parent_group', None)
725 gr = getattr(cur_gr, 'parent_group', None)
726 cur_gr = cur_gr.parent_group
726 cur_gr = cur_gr.parent_group
727 if gr is None:
727 if gr is None:
728 break
728 break
729 groups.insert(0, gr)
729 groups.insert(0, gr)
730
730
731 return groups
731 return groups
732
732
733 @property
733 @property
734 def groups_and_repo(self):
734 def groups_and_repo(self):
735 return self.groups_with_parents, self.just_name
735 return self.groups_with_parents, self.just_name
736
736
737 @LazyProperty
737 @LazyProperty
738 def repo_path(self):
738 def repo_path(self):
739 """
739 """
740 Returns base full path for that repository means where it actually
740 Returns base full path for that repository means where it actually
741 exists on a filesystem
741 exists on a filesystem
742 """
742 """
743 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
743 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
744 Repository.url_sep())
744 Repository.url_sep())
745 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
745 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
746 return q.one().ui_value
746 return q.one().ui_value
747
747
748 @property
748 @property
749 def repo_full_path(self):
749 def repo_full_path(self):
750 p = [self.repo_path]
750 p = [self.repo_path]
751 # we need to split the name by / since this is how we store the
751 # we need to split the name by / since this is how we store the
752 # names in the database, but that eventually needs to be converted
752 # names in the database, but that eventually needs to be converted
753 # into a valid system path
753 # into a valid system path
754 p += self.repo_name.split(Repository.url_sep())
754 p += self.repo_name.split(Repository.url_sep())
755 return os.path.join(*p)
755 return os.path.join(*p)
756
756
757 @property
757 @property
758 def cache_keys(self):
758 def cache_keys(self):
759 """
759 """
760 Returns associated cache keys for that repo
760 Returns associated cache keys for that repo
761 """
761 """
762 return CacheInvalidation.query()\
762 return CacheInvalidation.query()\
763 .filter(CacheInvalidation.cache_args == self.repo_name)\
763 .filter(CacheInvalidation.cache_args == self.repo_name)\
764 .order_by(CacheInvalidation.cache_key)\
764 .order_by(CacheInvalidation.cache_key)\
765 .all()
765 .all()
766
766
767 def get_new_name(self, repo_name):
767 def get_new_name(self, repo_name):
768 """
768 """
769 returns new full repository name based on assigned group and new new
769 returns new full repository name based on assigned group and new new
770
770
771 :param group_name:
771 :param group_name:
772 """
772 """
773 path_prefix = self.group.full_path_splitted if self.group else []
773 path_prefix = self.group.full_path_splitted if self.group else []
774 return Repository.url_sep().join(path_prefix + [repo_name])
774 return Repository.url_sep().join(path_prefix + [repo_name])
775
775
776 @property
776 @property
777 def _ui(self):
777 def _ui(self):
778 """
778 """
779 Creates an db based ui object for this repository
779 Creates an db based ui object for this repository
780 """
780 """
781 from rhodecode.lib.utils import make_ui
781 from rhodecode.lib.utils import make_ui
782 return make_ui('db', clear_session=False)
782 return make_ui('db', clear_session=False)
783
783
784 @classmethod
784 @classmethod
785 def inject_ui(cls, repo, extras={}):
785 def inject_ui(cls, repo, extras={}):
786 from rhodecode.lib.vcs.backends.hg import MercurialRepository
786 from rhodecode.lib.vcs.backends.hg import MercurialRepository
787 from rhodecode.lib.vcs.backends.git import GitRepository
787 from rhodecode.lib.vcs.backends.git import GitRepository
788 required = (MercurialRepository, GitRepository)
788 required = (MercurialRepository, GitRepository)
789 if not isinstance(repo, required):
789 if not isinstance(repo, required):
790 raise Exception('repo must be instance of %s' % required)
790 raise Exception('repo must be instance of %s' % required)
791
791
792 # inject ui extra param to log this action via push logger
792 # inject ui extra param to log this action via push logger
793 for k, v in extras.items():
793 for k, v in extras.items():
794 repo._repo.ui.setconfig('rhodecode_extras', k, v)
794 repo._repo.ui.setconfig('rhodecode_extras', k, v)
795
795
796 @classmethod
796 @classmethod
797 def is_valid(cls, repo_name):
797 def is_valid(cls, repo_name):
798 """
798 """
799 returns True if given repo name is a valid filesystem repository
799 returns True if given repo name is a valid filesystem repository
800
800
801 :param cls:
801 :param cls:
802 :param repo_name:
802 :param repo_name:
803 """
803 """
804 from rhodecode.lib.utils import is_valid_repo
804 from rhodecode.lib.utils import is_valid_repo
805
805
806 return is_valid_repo(repo_name, cls.base_path())
806 return is_valid_repo(repo_name, cls.base_path())
807
807
808 def get_api_data(self):
808 def get_api_data(self):
809 """
809 """
810 Common function for generating repo api data
810 Common function for generating repo api data
811
811
812 """
812 """
813 repo = self
813 repo = self
814 data = dict(
814 data = dict(
815 repo_id=repo.repo_id,
815 repo_id=repo.repo_id,
816 repo_name=repo.repo_name,
816 repo_name=repo.repo_name,
817 repo_type=repo.repo_type,
817 repo_type=repo.repo_type,
818 clone_uri=repo.clone_uri,
818 clone_uri=repo.clone_uri,
819 private=repo.private,
819 private=repo.private,
820 created_on=repo.created_on,
820 created_on=repo.created_on,
821 description=repo.description,
821 description=repo.description,
822 landing_rev=repo.landing_rev,
822 landing_rev=repo.landing_rev,
823 owner=repo.user.username,
823 owner=repo.user.username,
824 fork_of=repo.fork.repo_name if repo.fork else None
824 fork_of=repo.fork.repo_name if repo.fork else None
825 )
825 )
826
826
827 return data
827 return data
828
828
829 @classmethod
829 @classmethod
830 def lock(cls, repo, user_id):
830 def lock(cls, repo, user_id):
831 repo.locked = [user_id, time.time()]
831 repo.locked = [user_id, time.time()]
832 Session().add(repo)
832 Session().add(repo)
833 Session().commit()
833 Session().commit()
834
834
835 @classmethod
835 @classmethod
836 def unlock(cls, repo):
836 def unlock(cls, repo):
837 repo.locked = None
837 repo.locked = None
838 Session().add(repo)
838 Session().add(repo)
839 Session().commit()
839 Session().commit()
840
840
841 @property
841 @property
842 def last_db_change(self):
842 def last_db_change(self):
843 return self.updated_on
843 return self.updated_on
844
844
845 #==========================================================================
845 #==========================================================================
846 # SCM PROPERTIES
846 # SCM PROPERTIES
847 #==========================================================================
847 #==========================================================================
848
848
849 def get_changeset(self, rev=None):
849 def get_changeset(self, rev=None):
850 return get_changeset_safe(self.scm_instance, rev)
850 return get_changeset_safe(self.scm_instance, rev)
851
851
852 def get_landing_changeset(self):
852 def get_landing_changeset(self):
853 """
853 """
854 Returns landing changeset, or if that doesn't exist returns the tip
854 Returns landing changeset, or if that doesn't exist returns the tip
855 """
855 """
856 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
856 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
857 return cs
857 return cs
858
858
859 def update_last_change(self, last_change=None):
859 def update_last_change(self, last_change=None):
860 if last_change is None:
860 if last_change is None:
861 last_change = datetime.datetime.now()
861 last_change = datetime.datetime.now()
862 if self.updated_on is None or self.updated_on != last_change:
862 if self.updated_on is None or self.updated_on != last_change:
863 log.debug('updated repo %s with new date %s' % (self, last_change))
863 log.debug('updated repo %s with new date %s' % (self, last_change))
864 self.updated_on = last_change
864 self.updated_on = last_change
865 Session().add(self)
865 Session().add(self)
866 Session().commit()
866 Session().commit()
867
867
868 @property
868 @property
869 def tip(self):
869 def tip(self):
870 return self.get_changeset('tip')
870 return self.get_changeset('tip')
871
871
872 @property
872 @property
873 def author(self):
873 def author(self):
874 return self.tip.author
874 return self.tip.author
875
875
876 @property
876 @property
877 def last_change(self):
877 def last_change(self):
878 return self.scm_instance.last_change
878 return self.scm_instance.last_change
879
879
880 def get_comments(self, revisions=None):
880 def get_comments(self, revisions=None):
881 """
881 """
882 Returns comments for this repository grouped by revisions
882 Returns comments for this repository grouped by revisions
883
883
884 :param revisions: filter query by revisions only
884 :param revisions: filter query by revisions only
885 """
885 """
886 cmts = ChangesetComment.query()\
886 cmts = ChangesetComment.query()\
887 .filter(ChangesetComment.repo == self)
887 .filter(ChangesetComment.repo == self)
888 if revisions:
888 if revisions:
889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
889 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
890 grouped = defaultdict(list)
890 grouped = defaultdict(list)
891 for cmt in cmts.all():
891 for cmt in cmts.all():
892 grouped[cmt.revision].append(cmt)
892 grouped[cmt.revision].append(cmt)
893 return grouped
893 return grouped
894
894
895 def statuses(self, revisions=None):
895 def statuses(self, revisions=None):
896 """
896 """
897 Returns statuses for this repository
897 Returns statuses for this repository
898
898
899 :param revisions: list of revisions to get statuses for
899 :param revisions: list of revisions to get statuses for
900 :type revisions: list
900 :type revisions: list
901 """
901 """
902
902
903 statuses = ChangesetStatus.query()\
903 statuses = ChangesetStatus.query()\
904 .filter(ChangesetStatus.repo == self)\
904 .filter(ChangesetStatus.repo == self)\
905 .filter(ChangesetStatus.version == 0)
905 .filter(ChangesetStatus.version == 0)
906 if revisions:
906 if revisions:
907 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
907 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
908 grouped = {}
908 grouped = {}
909
909
910 #maybe we have open new pullrequest without a status ?
910 #maybe we have open new pullrequest without a status ?
911 stat = ChangesetStatus.STATUS_UNDER_REVIEW
911 stat = ChangesetStatus.STATUS_UNDER_REVIEW
912 status_lbl = ChangesetStatus.get_status_lbl(stat)
912 status_lbl = ChangesetStatus.get_status_lbl(stat)
913 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
913 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
914 for rev in pr.revisions:
914 for rev in pr.revisions:
915 pr_id = pr.pull_request_id
915 pr_id = pr.pull_request_id
916 pr_repo = pr.other_repo.repo_name
916 pr_repo = pr.other_repo.repo_name
917 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
917 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
918
918
919 for stat in statuses.all():
919 for stat in statuses.all():
920 pr_id = pr_repo = None
920 pr_id = pr_repo = None
921 if stat.pull_request:
921 if stat.pull_request:
922 pr_id = stat.pull_request.pull_request_id
922 pr_id = stat.pull_request.pull_request_id
923 pr_repo = stat.pull_request.other_repo.repo_name
923 pr_repo = stat.pull_request.other_repo.repo_name
924 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
924 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
925 pr_id, pr_repo]
925 pr_id, pr_repo]
926 return grouped
926 return grouped
927
927
928 #==========================================================================
928 #==========================================================================
929 # SCM CACHE INSTANCE
929 # SCM CACHE INSTANCE
930 #==========================================================================
930 #==========================================================================
931
931
932 @property
932 @property
933 def invalidate(self):
933 def invalidate(self):
934 return CacheInvalidation.invalidate(self.repo_name)
934 return CacheInvalidation.invalidate(self.repo_name)
935
935
936 def set_invalidate(self):
936 def set_invalidate(self):
937 """
937 """
938 set a cache for invalidation for this instance
938 set a cache for invalidation for this instance
939 """
939 """
940 CacheInvalidation.set_invalidate(self.repo_name)
940 CacheInvalidation.set_invalidate(self.repo_name)
941
941
942 @LazyProperty
942 @LazyProperty
943 def scm_instance(self):
943 def scm_instance(self):
944 return self.scm_instance_cached()
944 return self.__get_instance()
945 return self.__get_instance()
945
946
946 def scm_instance_cached(self, cache_map=None):
947 def scm_instance_cached(self, cache_map=None):
947 @cache_region('long_term')
948 @cache_region('long_term')
948 def _c(repo_name):
949 def _c(repo_name):
949 return self.__get_instance()
950 return self.__get_instance()
950 rn = self.repo_name
951 rn = self.repo_name
951 log.debug('Getting cached instance of repo')
952 log.debug('Getting cached instance of repo')
952
953
953 if cache_map:
954 if cache_map:
954 # get using prefilled cache_map
955 # get using prefilled cache_map
955 invalidate_repo = cache_map[self.repo_name]
956 invalidate_repo = cache_map[self.repo_name]
956 if invalidate_repo:
957 if invalidate_repo:
957 invalidate_repo = (None if invalidate_repo.cache_active
958 invalidate_repo = (None if invalidate_repo.cache_active
958 else invalidate_repo)
959 else invalidate_repo)
959 else:
960 else:
960 # get from invalidate
961 # get from invalidate
961 invalidate_repo = self.invalidate
962 invalidate_repo = self.invalidate
962
963
963 if invalidate_repo is not None:
964 if invalidate_repo is not None:
964 region_invalidate(_c, None, rn)
965 region_invalidate(_c, None, rn)
965 # update our cache
966 # update our cache
966 CacheInvalidation.set_valid(invalidate_repo.cache_key)
967 CacheInvalidation.set_valid(invalidate_repo.cache_key)
967 return _c(rn)
968 return _c(rn)
968
969
969 def __get_instance(self):
970 def __get_instance(self):
970 repo_full_path = self.repo_full_path
971 repo_full_path = self.repo_full_path
971 try:
972 try:
972 alias = get_scm(repo_full_path)[0]
973 alias = get_scm(repo_full_path)[0]
973 log.debug('Creating instance of %s repository' % alias)
974 log.debug('Creating instance of %s repository' % alias)
974 backend = get_backend(alias)
975 backend = get_backend(alias)
975 except VCSError:
976 except VCSError:
976 log.error(traceback.format_exc())
977 log.error(traceback.format_exc())
977 log.error('Perhaps this repository is in db and not in '
978 log.error('Perhaps this repository is in db and not in '
978 'filesystem run rescan repositories with '
979 'filesystem run rescan repositories with '
979 '"destroy old data " option from admin panel')
980 '"destroy old data " option from admin panel')
980 return
981 return
981
982
982 if alias == 'hg':
983 if alias == 'hg':
983
984
984 repo = backend(safe_str(repo_full_path), create=False,
985 repo = backend(safe_str(repo_full_path), create=False,
985 baseui=self._ui)
986 baseui=self._ui)
986 # skip hidden web repository
987 # skip hidden web repository
987 if repo._get_hidden():
988 if repo._get_hidden():
988 return
989 return
989 else:
990 else:
990 repo = backend(repo_full_path, create=False)
991 repo = backend(repo_full_path, create=False)
991
992
992 return repo
993 return repo
993
994
994
995
995 class RepoGroup(Base, BaseModel):
996 class RepoGroup(Base, BaseModel):
996 __tablename__ = 'groups'
997 __tablename__ = 'groups'
997 __table_args__ = (
998 __table_args__ = (
998 UniqueConstraint('group_name', 'group_parent_id'),
999 UniqueConstraint('group_name', 'group_parent_id'),
999 CheckConstraint('group_id != group_parent_id'),
1000 CheckConstraint('group_id != group_parent_id'),
1000 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1001 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1001 'mysql_charset': 'utf8'},
1002 'mysql_charset': 'utf8'},
1002 )
1003 )
1003 __mapper_args__ = {'order_by': 'group_name'}
1004 __mapper_args__ = {'order_by': 'group_name'}
1004
1005
1005 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1006 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1006 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1007 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1007 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1008 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1008 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1009 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1009 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1010 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1010
1011
1011 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1012 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1012 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1013 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
1013
1014
1014 parent_group = relationship('RepoGroup', remote_side=group_id)
1015 parent_group = relationship('RepoGroup', remote_side=group_id)
1015
1016
1016 def __init__(self, group_name='', parent_group=None):
1017 def __init__(self, group_name='', parent_group=None):
1017 self.group_name = group_name
1018 self.group_name = group_name
1018 self.parent_group = parent_group
1019 self.parent_group = parent_group
1019
1020
1020 def __unicode__(self):
1021 def __unicode__(self):
1021 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1022 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1022 self.group_name)
1023 self.group_name)
1023
1024
1024 @classmethod
1025 @classmethod
1025 def groups_choices(cls, check_perms=False):
1026 def groups_choices(cls, check_perms=False):
1026 from webhelpers.html import literal as _literal
1027 from webhelpers.html import literal as _literal
1027 from rhodecode.model.scm import ScmModel
1028 from rhodecode.model.scm import ScmModel
1028 groups = cls.query().all()
1029 groups = cls.query().all()
1029 if check_perms:
1030 if check_perms:
1030 #filter group user have access to, it's done
1031 #filter group user have access to, it's done
1031 #magically inside ScmModel based on current user
1032 #magically inside ScmModel based on current user
1032 groups = ScmModel().get_repos_groups(groups)
1033 groups = ScmModel().get_repos_groups(groups)
1033 repo_groups = [('', '')]
1034 repo_groups = [('', '')]
1034 sep = ' &raquo; '
1035 sep = ' &raquo; '
1035 _name = lambda k: _literal(sep.join(k))
1036 _name = lambda k: _literal(sep.join(k))
1036
1037
1037 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1038 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1038 for x in groups])
1039 for x in groups])
1039
1040
1040 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1041 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1041 return repo_groups
1042 return repo_groups
1042
1043
1043 @classmethod
1044 @classmethod
1044 def url_sep(cls):
1045 def url_sep(cls):
1045 return URL_SEP
1046 return URL_SEP
1046
1047
1047 @classmethod
1048 @classmethod
1048 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1049 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1049 if case_insensitive:
1050 if case_insensitive:
1050 gr = cls.query()\
1051 gr = cls.query()\
1051 .filter(cls.group_name.ilike(group_name))
1052 .filter(cls.group_name.ilike(group_name))
1052 else:
1053 else:
1053 gr = cls.query()\
1054 gr = cls.query()\
1054 .filter(cls.group_name == group_name)
1055 .filter(cls.group_name == group_name)
1055 if cache:
1056 if cache:
1056 gr = gr.options(FromCache(
1057 gr = gr.options(FromCache(
1057 "sql_cache_short",
1058 "sql_cache_short",
1058 "get_group_%s" % _hash_key(group_name)
1059 "get_group_%s" % _hash_key(group_name)
1059 )
1060 )
1060 )
1061 )
1061 return gr.scalar()
1062 return gr.scalar()
1062
1063
1063 @property
1064 @property
1064 def parents(self):
1065 def parents(self):
1065 parents_recursion_limit = 5
1066 parents_recursion_limit = 5
1066 groups = []
1067 groups = []
1067 if self.parent_group is None:
1068 if self.parent_group is None:
1068 return groups
1069 return groups
1069 cur_gr = self.parent_group
1070 cur_gr = self.parent_group
1070 groups.insert(0, cur_gr)
1071 groups.insert(0, cur_gr)
1071 cnt = 0
1072 cnt = 0
1072 while 1:
1073 while 1:
1073 cnt += 1
1074 cnt += 1
1074 gr = getattr(cur_gr, 'parent_group', None)
1075 gr = getattr(cur_gr, 'parent_group', None)
1075 cur_gr = cur_gr.parent_group
1076 cur_gr = cur_gr.parent_group
1076 if gr is None:
1077 if gr is None:
1077 break
1078 break
1078 if cnt == parents_recursion_limit:
1079 if cnt == parents_recursion_limit:
1079 # this will prevent accidental infinit loops
1080 # this will prevent accidental infinit loops
1080 log.error('group nested more than %s' %
1081 log.error('group nested more than %s' %
1081 parents_recursion_limit)
1082 parents_recursion_limit)
1082 break
1083 break
1083
1084
1084 groups.insert(0, gr)
1085 groups.insert(0, gr)
1085 return groups
1086 return groups
1086
1087
1087 @property
1088 @property
1088 def children(self):
1089 def children(self):
1089 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1090 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1090
1091
1091 @property
1092 @property
1092 def name(self):
1093 def name(self):
1093 return self.group_name.split(RepoGroup.url_sep())[-1]
1094 return self.group_name.split(RepoGroup.url_sep())[-1]
1094
1095
1095 @property
1096 @property
1096 def full_path(self):
1097 def full_path(self):
1097 return self.group_name
1098 return self.group_name
1098
1099
1099 @property
1100 @property
1100 def full_path_splitted(self):
1101 def full_path_splitted(self):
1101 return self.group_name.split(RepoGroup.url_sep())
1102 return self.group_name.split(RepoGroup.url_sep())
1102
1103
1103 @property
1104 @property
1104 def repositories(self):
1105 def repositories(self):
1105 return Repository.query()\
1106 return Repository.query()\
1106 .filter(Repository.group == self)\
1107 .filter(Repository.group == self)\
1107 .order_by(Repository.repo_name)
1108 .order_by(Repository.repo_name)
1108
1109
1109 @property
1110 @property
1110 def repositories_recursive_count(self):
1111 def repositories_recursive_count(self):
1111 cnt = self.repositories.count()
1112 cnt = self.repositories.count()
1112
1113
1113 def children_count(group):
1114 def children_count(group):
1114 cnt = 0
1115 cnt = 0
1115 for child in group.children:
1116 for child in group.children:
1116 cnt += child.repositories.count()
1117 cnt += child.repositories.count()
1117 cnt += children_count(child)
1118 cnt += children_count(child)
1118 return cnt
1119 return cnt
1119
1120
1120 return cnt + children_count(self)
1121 return cnt + children_count(self)
1121
1122
1122 def recursive_groups_and_repos(self):
1123 def recursive_groups_and_repos(self):
1123 """
1124 """
1124 Recursive return all groups, with repositories in those groups
1125 Recursive return all groups, with repositories in those groups
1125 """
1126 """
1126 all_ = []
1127 all_ = []
1127
1128
1128 def _get_members(root_gr):
1129 def _get_members(root_gr):
1129 for r in root_gr.repositories:
1130 for r in root_gr.repositories:
1130 all_.append(r)
1131 all_.append(r)
1131 childs = root_gr.children.all()
1132 childs = root_gr.children.all()
1132 if childs:
1133 if childs:
1133 for gr in childs:
1134 for gr in childs:
1134 all_.append(gr)
1135 all_.append(gr)
1135 _get_members(gr)
1136 _get_members(gr)
1136
1137
1137 _get_members(self)
1138 _get_members(self)
1138 return [self] + all_
1139 return [self] + all_
1139
1140
1140 def get_new_name(self, group_name):
1141 def get_new_name(self, group_name):
1141 """
1142 """
1142 returns new full group name based on parent and new name
1143 returns new full group name based on parent and new name
1143
1144
1144 :param group_name:
1145 :param group_name:
1145 """
1146 """
1146 path_prefix = (self.parent_group.full_path_splitted if
1147 path_prefix = (self.parent_group.full_path_splitted if
1147 self.parent_group else [])
1148 self.parent_group else [])
1148 return RepoGroup.url_sep().join(path_prefix + [group_name])
1149 return RepoGroup.url_sep().join(path_prefix + [group_name])
1149
1150
1150
1151
1151 class Permission(Base, BaseModel):
1152 class Permission(Base, BaseModel):
1152 __tablename__ = 'permissions'
1153 __tablename__ = 'permissions'
1153 __table_args__ = (
1154 __table_args__ = (
1154 Index('p_perm_name_idx', 'permission_name'),
1155 Index('p_perm_name_idx', 'permission_name'),
1155 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1156 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1156 'mysql_charset': 'utf8'},
1157 'mysql_charset': 'utf8'},
1157 )
1158 )
1158 PERMS = [
1159 PERMS = [
1159 ('repository.none', _('Repository no access')),
1160 ('repository.none', _('Repository no access')),
1160 ('repository.read', _('Repository read access')),
1161 ('repository.read', _('Repository read access')),
1161 ('repository.write', _('Repository write access')),
1162 ('repository.write', _('Repository write access')),
1162 ('repository.admin', _('Repository admin access')),
1163 ('repository.admin', _('Repository admin access')),
1163
1164
1164 ('group.none', _('Repositories Group no access')),
1165 ('group.none', _('Repositories Group no access')),
1165 ('group.read', _('Repositories Group read access')),
1166 ('group.read', _('Repositories Group read access')),
1166 ('group.write', _('Repositories Group write access')),
1167 ('group.write', _('Repositories Group write access')),
1167 ('group.admin', _('Repositories Group admin access')),
1168 ('group.admin', _('Repositories Group admin access')),
1168
1169
1169 ('hg.admin', _('RhodeCode Administrator')),
1170 ('hg.admin', _('RhodeCode Administrator')),
1170 ('hg.create.none', _('Repository creation disabled')),
1171 ('hg.create.none', _('Repository creation disabled')),
1171 ('hg.create.repository', _('Repository creation enabled')),
1172 ('hg.create.repository', _('Repository creation enabled')),
1172 ('hg.fork.none', _('Repository forking disabled')),
1173 ('hg.fork.none', _('Repository forking disabled')),
1173 ('hg.fork.repository', _('Repository forking enabled')),
1174 ('hg.fork.repository', _('Repository forking enabled')),
1174 ('hg.register.none', _('Register disabled')),
1175 ('hg.register.none', _('Register disabled')),
1175 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1176 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1176 'with manual activation')),
1177 'with manual activation')),
1177
1178
1178 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1179 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1179 'with auto activation')),
1180 'with auto activation')),
1180 ]
1181 ]
1181
1182
1182 # defines which permissions are more important higher the more important
1183 # defines which permissions are more important higher the more important
1183 PERM_WEIGHTS = {
1184 PERM_WEIGHTS = {
1184 'repository.none': 0,
1185 'repository.none': 0,
1185 'repository.read': 1,
1186 'repository.read': 1,
1186 'repository.write': 3,
1187 'repository.write': 3,
1187 'repository.admin': 4,
1188 'repository.admin': 4,
1188
1189
1189 'group.none': 0,
1190 'group.none': 0,
1190 'group.read': 1,
1191 'group.read': 1,
1191 'group.write': 3,
1192 'group.write': 3,
1192 'group.admin': 4,
1193 'group.admin': 4,
1193
1194
1194 'hg.fork.none': 0,
1195 'hg.fork.none': 0,
1195 'hg.fork.repository': 1,
1196 'hg.fork.repository': 1,
1196 'hg.create.none': 0,
1197 'hg.create.none': 0,
1197 'hg.create.repository':1
1198 'hg.create.repository':1
1198 }
1199 }
1199
1200
1200 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1201 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1201 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1202 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1202 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1203 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1203
1204
1204 def __unicode__(self):
1205 def __unicode__(self):
1205 return u"<%s('%s:%s')>" % (
1206 return u"<%s('%s:%s')>" % (
1206 self.__class__.__name__, self.permission_id, self.permission_name
1207 self.__class__.__name__, self.permission_id, self.permission_name
1207 )
1208 )
1208
1209
1209 @classmethod
1210 @classmethod
1210 def get_by_key(cls, key):
1211 def get_by_key(cls, key):
1211 return cls.query().filter(cls.permission_name == key).scalar()
1212 return cls.query().filter(cls.permission_name == key).scalar()
1212
1213
1213 @classmethod
1214 @classmethod
1214 def get_default_perms(cls, default_user_id):
1215 def get_default_perms(cls, default_user_id):
1215 q = Session().query(UserRepoToPerm, Repository, cls)\
1216 q = Session().query(UserRepoToPerm, Repository, cls)\
1216 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1217 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1217 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1218 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1218 .filter(UserRepoToPerm.user_id == default_user_id)
1219 .filter(UserRepoToPerm.user_id == default_user_id)
1219
1220
1220 return q.all()
1221 return q.all()
1221
1222
1222 @classmethod
1223 @classmethod
1223 def get_default_group_perms(cls, default_user_id):
1224 def get_default_group_perms(cls, default_user_id):
1224 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1225 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1225 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1226 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1226 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1227 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1227 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1228 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1228
1229
1229 return q.all()
1230 return q.all()
1230
1231
1231
1232
1232 class UserRepoToPerm(Base, BaseModel):
1233 class UserRepoToPerm(Base, BaseModel):
1233 __tablename__ = 'repo_to_perm'
1234 __tablename__ = 'repo_to_perm'
1234 __table_args__ = (
1235 __table_args__ = (
1235 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1236 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1237 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1237 'mysql_charset': 'utf8'}
1238 'mysql_charset': 'utf8'}
1238 )
1239 )
1239 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1240 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1240 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1241 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1241 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1242 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1242 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1243 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1243
1244
1244 user = relationship('User')
1245 user = relationship('User')
1245 repository = relationship('Repository')
1246 repository = relationship('Repository')
1246 permission = relationship('Permission')
1247 permission = relationship('Permission')
1247
1248
1248 @classmethod
1249 @classmethod
1249 def create(cls, user, repository, permission):
1250 def create(cls, user, repository, permission):
1250 n = cls()
1251 n = cls()
1251 n.user = user
1252 n.user = user
1252 n.repository = repository
1253 n.repository = repository
1253 n.permission = permission
1254 n.permission = permission
1254 Session().add(n)
1255 Session().add(n)
1255 return n
1256 return n
1256
1257
1257 def __unicode__(self):
1258 def __unicode__(self):
1258 return u'<user:%s => %s >' % (self.user, self.repository)
1259 return u'<user:%s => %s >' % (self.user, self.repository)
1259
1260
1260
1261
1261 class UserToPerm(Base, BaseModel):
1262 class UserToPerm(Base, BaseModel):
1262 __tablename__ = 'user_to_perm'
1263 __tablename__ = 'user_to_perm'
1263 __table_args__ = (
1264 __table_args__ = (
1264 UniqueConstraint('user_id', 'permission_id'),
1265 UniqueConstraint('user_id', 'permission_id'),
1265 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1266 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1266 'mysql_charset': 'utf8'}
1267 'mysql_charset': 'utf8'}
1267 )
1268 )
1268 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1269 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1269 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1270 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1270 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1271 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1271
1272
1272 user = relationship('User')
1273 user = relationship('User')
1273 permission = relationship('Permission', lazy='joined')
1274 permission = relationship('Permission', lazy='joined')
1274
1275
1275
1276
1276 class UsersGroupRepoToPerm(Base, BaseModel):
1277 class UsersGroupRepoToPerm(Base, BaseModel):
1277 __tablename__ = 'users_group_repo_to_perm'
1278 __tablename__ = 'users_group_repo_to_perm'
1278 __table_args__ = (
1279 __table_args__ = (
1279 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1280 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1280 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1281 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1281 'mysql_charset': 'utf8'}
1282 'mysql_charset': 'utf8'}
1282 )
1283 )
1283 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1284 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1284 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1285 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1285 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1286 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1286 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1287 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1287
1288
1288 users_group = relationship('UsersGroup')
1289 users_group = relationship('UsersGroup')
1289 permission = relationship('Permission')
1290 permission = relationship('Permission')
1290 repository = relationship('Repository')
1291 repository = relationship('Repository')
1291
1292
1292 @classmethod
1293 @classmethod
1293 def create(cls, users_group, repository, permission):
1294 def create(cls, users_group, repository, permission):
1294 n = cls()
1295 n = cls()
1295 n.users_group = users_group
1296 n.users_group = users_group
1296 n.repository = repository
1297 n.repository = repository
1297 n.permission = permission
1298 n.permission = permission
1298 Session().add(n)
1299 Session().add(n)
1299 return n
1300 return n
1300
1301
1301 def __unicode__(self):
1302 def __unicode__(self):
1302 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1303 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1303
1304
1304
1305
1305 class UsersGroupToPerm(Base, BaseModel):
1306 class UsersGroupToPerm(Base, BaseModel):
1306 __tablename__ = 'users_group_to_perm'
1307 __tablename__ = 'users_group_to_perm'
1307 __table_args__ = (
1308 __table_args__ = (
1308 UniqueConstraint('users_group_id', 'permission_id',),
1309 UniqueConstraint('users_group_id', 'permission_id',),
1309 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1310 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1310 'mysql_charset': 'utf8'}
1311 'mysql_charset': 'utf8'}
1311 )
1312 )
1312 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1313 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1313 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1314 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1314 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1315 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1315
1316
1316 users_group = relationship('UsersGroup')
1317 users_group = relationship('UsersGroup')
1317 permission = relationship('Permission')
1318 permission = relationship('Permission')
1318
1319
1319
1320
1320 class UserRepoGroupToPerm(Base, BaseModel):
1321 class UserRepoGroupToPerm(Base, BaseModel):
1321 __tablename__ = 'user_repo_group_to_perm'
1322 __tablename__ = 'user_repo_group_to_perm'
1322 __table_args__ = (
1323 __table_args__ = (
1323 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1324 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1324 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1325 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1325 'mysql_charset': 'utf8'}
1326 'mysql_charset': 'utf8'}
1326 )
1327 )
1327
1328
1328 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1329 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1329 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1330 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1330 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1331 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1331 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1332 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1332
1333
1333 user = relationship('User')
1334 user = relationship('User')
1334 group = relationship('RepoGroup')
1335 group = relationship('RepoGroup')
1335 permission = relationship('Permission')
1336 permission = relationship('Permission')
1336
1337
1337
1338
1338 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1339 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1339 __tablename__ = 'users_group_repo_group_to_perm'
1340 __tablename__ = 'users_group_repo_group_to_perm'
1340 __table_args__ = (
1341 __table_args__ = (
1341 UniqueConstraint('users_group_id', 'group_id'),
1342 UniqueConstraint('users_group_id', 'group_id'),
1342 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1343 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1343 'mysql_charset': 'utf8'}
1344 'mysql_charset': 'utf8'}
1344 )
1345 )
1345
1346
1346 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)
1347 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)
1347 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1348 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1348 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1349 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1349 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1350 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1350
1351
1351 users_group = relationship('UsersGroup')
1352 users_group = relationship('UsersGroup')
1352 permission = relationship('Permission')
1353 permission = relationship('Permission')
1353 group = relationship('RepoGroup')
1354 group = relationship('RepoGroup')
1354
1355
1355
1356
1356 class Statistics(Base, BaseModel):
1357 class Statistics(Base, BaseModel):
1357 __tablename__ = 'statistics'
1358 __tablename__ = 'statistics'
1358 __table_args__ = (
1359 __table_args__ = (
1359 UniqueConstraint('repository_id'),
1360 UniqueConstraint('repository_id'),
1360 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1361 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1361 'mysql_charset': 'utf8'}
1362 'mysql_charset': 'utf8'}
1362 )
1363 )
1363 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1364 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1364 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1365 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1365 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1366 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1366 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1367 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1367 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1368 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1368 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1369 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1369
1370
1370 repository = relationship('Repository', single_parent=True)
1371 repository = relationship('Repository', single_parent=True)
1371
1372
1372
1373
1373 class UserFollowing(Base, BaseModel):
1374 class UserFollowing(Base, BaseModel):
1374 __tablename__ = 'user_followings'
1375 __tablename__ = 'user_followings'
1375 __table_args__ = (
1376 __table_args__ = (
1376 UniqueConstraint('user_id', 'follows_repository_id'),
1377 UniqueConstraint('user_id', 'follows_repository_id'),
1377 UniqueConstraint('user_id', 'follows_user_id'),
1378 UniqueConstraint('user_id', 'follows_user_id'),
1378 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1379 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1379 'mysql_charset': 'utf8'}
1380 'mysql_charset': 'utf8'}
1380 )
1381 )
1381
1382
1382 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1383 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1383 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1384 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1384 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1385 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1385 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1386 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1386 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1387 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1387
1388
1388 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1389 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1389
1390
1390 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1391 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1391 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1392 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1392
1393
1393 @classmethod
1394 @classmethod
1394 def get_repo_followers(cls, repo_id):
1395 def get_repo_followers(cls, repo_id):
1395 return cls.query().filter(cls.follows_repo_id == repo_id)
1396 return cls.query().filter(cls.follows_repo_id == repo_id)
1396
1397
1397
1398
1398 class CacheInvalidation(Base, BaseModel):
1399 class CacheInvalidation(Base, BaseModel):
1399 __tablename__ = 'cache_invalidation'
1400 __tablename__ = 'cache_invalidation'
1400 __table_args__ = (
1401 __table_args__ = (
1401 UniqueConstraint('cache_key'),
1402 UniqueConstraint('cache_key'),
1402 Index('key_idx', 'cache_key'),
1403 Index('key_idx', 'cache_key'),
1403 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1404 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1404 'mysql_charset': 'utf8'},
1405 'mysql_charset': 'utf8'},
1405 )
1406 )
1406 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1407 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1407 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1408 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1408 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1409 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1409 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1410 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1410
1411
1411 def __init__(self, cache_key, cache_args=''):
1412 def __init__(self, cache_key, cache_args=''):
1412 self.cache_key = cache_key
1413 self.cache_key = cache_key
1413 self.cache_args = cache_args
1414 self.cache_args = cache_args
1414 self.cache_active = False
1415 self.cache_active = False
1415
1416
1416 def __unicode__(self):
1417 def __unicode__(self):
1417 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1418 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1418 self.cache_id, self.cache_key)
1419 self.cache_id, self.cache_key)
1419
1420
1420 @property
1421 @property
1421 def prefix(self):
1422 def prefix(self):
1422 _split = self.cache_key.split(self.cache_args, 1)
1423 _split = self.cache_key.split(self.cache_args, 1)
1423 if _split and len(_split) == 2:
1424 if _split and len(_split) == 2:
1424 return _split[0]
1425 return _split[0]
1425 return ''
1426 return ''
1426
1427
1427 @classmethod
1428 @classmethod
1428 def clear_cache(cls):
1429 def clear_cache(cls):
1429 cls.query().delete()
1430 cls.query().delete()
1430
1431
1431 @classmethod
1432 @classmethod
1432 def _get_key(cls, key):
1433 def _get_key(cls, key):
1433 """
1434 """
1434 Wrapper for generating a key, together with a prefix
1435 Wrapper for generating a key, together with a prefix
1435
1436
1436 :param key:
1437 :param key:
1437 """
1438 """
1438 import rhodecode
1439 import rhodecode
1439 prefix = ''
1440 prefix = ''
1440 iid = rhodecode.CONFIG.get('instance_id')
1441 iid = rhodecode.CONFIG.get('instance_id')
1441 if iid:
1442 if iid:
1442 prefix = iid
1443 prefix = iid
1443 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1444 #remove specific suffixes like _README or _RSS
1445 key = remove_suffix(key, '_README')
1446 key = remove_suffix(key, '_RSS')
1447 key = remove_suffix(key, '_ATOM')
1448 return "%s%s" % (prefix, key), prefix, key
1444
1449
1445 @classmethod
1450 @classmethod
1446 def get_by_key(cls, key):
1451 def get_by_key(cls, key):
1447 return cls.query().filter(cls.cache_key == key).scalar()
1452 return cls.query().filter(cls.cache_key == key).scalar()
1448
1453
1449 @classmethod
1454 @classmethod
1450 def _get_or_create_key(cls, key, prefix, org_key, commit=True):
1455 def _get_or_create_key(cls, key, prefix, org_key, commit=True):
1451 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1456 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1452 if not inv_obj:
1457 if not inv_obj:
1453 try:
1458 try:
1454 inv_obj = CacheInvalidation(key, org_key)
1459 inv_obj = CacheInvalidation(key, org_key)
1455 Session().add(inv_obj)
1460 Session().add(inv_obj)
1456 if commit:
1461 if commit:
1457 Session().commit()
1462 Session().commit()
1458 except Exception:
1463 except Exception:
1459 log.error(traceback.format_exc())
1464 log.error(traceback.format_exc())
1460 Session().rollback()
1465 Session().rollback()
1461 return inv_obj
1466 return inv_obj
1462
1467
1463 @classmethod
1468 @classmethod
1464 def invalidate(cls, key):
1469 def invalidate(cls, key):
1465 """
1470 """
1466 Returns Invalidation object if this given key should be invalidated
1471 Returns Invalidation object if this given key should be invalidated
1467 None otherwise. `cache_active = False` means that this cache
1472 None otherwise. `cache_active = False` means that this cache
1468 state is not valid and needs to be invalidated
1473 state is not valid and needs to be invalidated
1469
1474
1470 :param key:
1475 :param key:
1471 """
1476 """
1472
1477
1473 key, _prefix, _org_key = cls._get_key(key)
1478 key, _prefix, _org_key = cls._get_key(key)
1474 inv = cls._get_or_create_key(key, _prefix, _org_key)
1479 inv = cls._get_or_create_key(key, _prefix, _org_key)
1475
1480
1476 if inv and inv.cache_active is False:
1481 if inv and inv.cache_active is False:
1477 return inv
1482 return inv
1478
1483
1479 @classmethod
1484 @classmethod
1480 def set_invalidate(cls, key):
1485 def set_invalidate(cls, key):
1481 """
1486 """
1482 Mark this Cache key for invalidation
1487 Mark this Cache key for invalidation
1483
1488
1484 :param key:
1489 :param key:
1485 """
1490 """
1486
1491
1487 key, _prefix, _org_key = cls._get_key(key)
1492 key, _prefix, _org_key = cls._get_key(key)
1488 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1493 inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all()
1489 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1494 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1490 _org_key))
1495 _org_key))
1491 try:
1496 try:
1492 for inv_obj in inv_objs:
1497 for inv_obj in inv_objs:
1493 if inv_obj:
1498 if inv_obj:
1494 inv_obj.cache_active = False
1499 inv_obj.cache_active = False
1495
1500
1496 Session().add(inv_obj)
1501 Session().add(inv_obj)
1497 Session().commit()
1502 Session().commit()
1498 except Exception:
1503 except Exception:
1499 log.error(traceback.format_exc())
1504 log.error(traceback.format_exc())
1500 Session().rollback()
1505 Session().rollback()
1501
1506
1502 @classmethod
1507 @classmethod
1503 def set_valid(cls, key):
1508 def set_valid(cls, key):
1504 """
1509 """
1505 Mark this cache key as active and currently cached
1510 Mark this cache key as active and currently cached
1506
1511
1507 :param key:
1512 :param key:
1508 """
1513 """
1509 inv_obj = cls.get_by_key(key)
1514 inv_obj = cls.get_by_key(key)
1510 inv_obj.cache_active = True
1515 inv_obj.cache_active = True
1511 Session().add(inv_obj)
1516 Session().add(inv_obj)
1512 Session().commit()
1517 Session().commit()
1513
1518
1514 @classmethod
1519 @classmethod
1515 def get_cache_map(cls):
1520 def get_cache_map(cls):
1516
1521
1517 class cachemapdict(dict):
1522 class cachemapdict(dict):
1518
1523
1519 def __init__(self, *args, **kwargs):
1524 def __init__(self, *args, **kwargs):
1520 fixkey = kwargs.get('fixkey')
1525 fixkey = kwargs.get('fixkey')
1521 if fixkey:
1526 if fixkey:
1522 del kwargs['fixkey']
1527 del kwargs['fixkey']
1523 self.fixkey = fixkey
1528 self.fixkey = fixkey
1524 super(cachemapdict, self).__init__(*args, **kwargs)
1529 super(cachemapdict, self).__init__(*args, **kwargs)
1525
1530
1526 def __getattr__(self, name):
1531 def __getattr__(self, name):
1527 key = name
1532 key = name
1528 if self.fixkey:
1533 if self.fixkey:
1529 key, _prefix, _org_key = cls._get_key(key)
1534 key, _prefix, _org_key = cls._get_key(key)
1530 if key in self.__dict__:
1535 if key in self.__dict__:
1531 return self.__dict__[key]
1536 return self.__dict__[key]
1532 else:
1537 else:
1533 return self[key]
1538 return self[key]
1534
1539
1535 def __getitem__(self, key):
1540 def __getitem__(self, key):
1536 if self.fixkey:
1541 if self.fixkey:
1537 key, _prefix, _org_key = cls._get_key(key)
1542 key, _prefix, _org_key = cls._get_key(key)
1538 try:
1543 try:
1539 return super(cachemapdict, self).__getitem__(key)
1544 return super(cachemapdict, self).__getitem__(key)
1540 except KeyError:
1545 except KeyError:
1541 return
1546 return
1542
1547
1543 cache_map = cachemapdict(fixkey=True)
1548 cache_map = cachemapdict(fixkey=True)
1544 for obj in cls.query().all():
1549 for obj in cls.query().all():
1545 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1550 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1546 return cache_map
1551 return cache_map
1547
1552
1548
1553
1549 class ChangesetComment(Base, BaseModel):
1554 class ChangesetComment(Base, BaseModel):
1550 __tablename__ = 'changeset_comments'
1555 __tablename__ = 'changeset_comments'
1551 __table_args__ = (
1556 __table_args__ = (
1552 Index('cc_revision_idx', 'revision'),
1557 Index('cc_revision_idx', 'revision'),
1553 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1558 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1554 'mysql_charset': 'utf8'},
1559 'mysql_charset': 'utf8'},
1555 )
1560 )
1556 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1561 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1557 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1562 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1558 revision = Column('revision', String(40), nullable=True)
1563 revision = Column('revision', String(40), nullable=True)
1559 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1564 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1560 line_no = Column('line_no', Unicode(10), nullable=True)
1565 line_no = Column('line_no', Unicode(10), nullable=True)
1561 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1566 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1562 f_path = Column('f_path', Unicode(1000), nullable=True)
1567 f_path = Column('f_path', Unicode(1000), nullable=True)
1563 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1568 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1564 text = Column('text', UnicodeText(25000), nullable=False)
1569 text = Column('text', UnicodeText(25000), nullable=False)
1565 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1570 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1566 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1571 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1567
1572
1568 author = relationship('User', lazy='joined')
1573 author = relationship('User', lazy='joined')
1569 repo = relationship('Repository')
1574 repo = relationship('Repository')
1570 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1575 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1571 pull_request = relationship('PullRequest', lazy='joined')
1576 pull_request = relationship('PullRequest', lazy='joined')
1572
1577
1573 @classmethod
1578 @classmethod
1574 def get_users(cls, revision=None, pull_request_id=None):
1579 def get_users(cls, revision=None, pull_request_id=None):
1575 """
1580 """
1576 Returns user associated with this ChangesetComment. ie those
1581 Returns user associated with this ChangesetComment. ie those
1577 who actually commented
1582 who actually commented
1578
1583
1579 :param cls:
1584 :param cls:
1580 :param revision:
1585 :param revision:
1581 """
1586 """
1582 q = Session().query(User)\
1587 q = Session().query(User)\
1583 .join(ChangesetComment.author)
1588 .join(ChangesetComment.author)
1584 if revision:
1589 if revision:
1585 q = q.filter(cls.revision == revision)
1590 q = q.filter(cls.revision == revision)
1586 elif pull_request_id:
1591 elif pull_request_id:
1587 q = q.filter(cls.pull_request_id == pull_request_id)
1592 q = q.filter(cls.pull_request_id == pull_request_id)
1588 return q.all()
1593 return q.all()
1589
1594
1590
1595
1591 class ChangesetStatus(Base, BaseModel):
1596 class ChangesetStatus(Base, BaseModel):
1592 __tablename__ = 'changeset_statuses'
1597 __tablename__ = 'changeset_statuses'
1593 __table_args__ = (
1598 __table_args__ = (
1594 Index('cs_revision_idx', 'revision'),
1599 Index('cs_revision_idx', 'revision'),
1595 Index('cs_version_idx', 'version'),
1600 Index('cs_version_idx', 'version'),
1596 UniqueConstraint('repo_id', 'revision', 'version'),
1601 UniqueConstraint('repo_id', 'revision', 'version'),
1597 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1602 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1598 'mysql_charset': 'utf8'}
1603 'mysql_charset': 'utf8'}
1599 )
1604 )
1600 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1605 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1601 STATUS_APPROVED = 'approved'
1606 STATUS_APPROVED = 'approved'
1602 STATUS_REJECTED = 'rejected'
1607 STATUS_REJECTED = 'rejected'
1603 STATUS_UNDER_REVIEW = 'under_review'
1608 STATUS_UNDER_REVIEW = 'under_review'
1604
1609
1605 STATUSES = [
1610 STATUSES = [
1606 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1611 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1607 (STATUS_APPROVED, _("Approved")),
1612 (STATUS_APPROVED, _("Approved")),
1608 (STATUS_REJECTED, _("Rejected")),
1613 (STATUS_REJECTED, _("Rejected")),
1609 (STATUS_UNDER_REVIEW, _("Under Review")),
1614 (STATUS_UNDER_REVIEW, _("Under Review")),
1610 ]
1615 ]
1611
1616
1612 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1617 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1613 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1618 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1614 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1619 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1615 revision = Column('revision', String(40), nullable=False)
1620 revision = Column('revision', String(40), nullable=False)
1616 status = Column('status', String(128), nullable=False, default=DEFAULT)
1621 status = Column('status', String(128), nullable=False, default=DEFAULT)
1617 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1622 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1618 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1623 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1619 version = Column('version', Integer(), nullable=False, default=0)
1624 version = Column('version', Integer(), nullable=False, default=0)
1620 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1625 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1621
1626
1622 author = relationship('User', lazy='joined')
1627 author = relationship('User', lazy='joined')
1623 repo = relationship('Repository')
1628 repo = relationship('Repository')
1624 comment = relationship('ChangesetComment', lazy='joined')
1629 comment = relationship('ChangesetComment', lazy='joined')
1625 pull_request = relationship('PullRequest', lazy='joined')
1630 pull_request = relationship('PullRequest', lazy='joined')
1626
1631
1627 def __unicode__(self):
1632 def __unicode__(self):
1628 return u"<%s('%s:%s')>" % (
1633 return u"<%s('%s:%s')>" % (
1629 self.__class__.__name__,
1634 self.__class__.__name__,
1630 self.status, self.author
1635 self.status, self.author
1631 )
1636 )
1632
1637
1633 @classmethod
1638 @classmethod
1634 def get_status_lbl(cls, value):
1639 def get_status_lbl(cls, value):
1635 return dict(cls.STATUSES).get(value)
1640 return dict(cls.STATUSES).get(value)
1636
1641
1637 @property
1642 @property
1638 def status_lbl(self):
1643 def status_lbl(self):
1639 return ChangesetStatus.get_status_lbl(self.status)
1644 return ChangesetStatus.get_status_lbl(self.status)
1640
1645
1641
1646
1642 class PullRequest(Base, BaseModel):
1647 class PullRequest(Base, BaseModel):
1643 __tablename__ = 'pull_requests'
1648 __tablename__ = 'pull_requests'
1644 __table_args__ = (
1649 __table_args__ = (
1645 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1650 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1646 'mysql_charset': 'utf8'},
1651 'mysql_charset': 'utf8'},
1647 )
1652 )
1648
1653
1649 STATUS_NEW = u'new'
1654 STATUS_NEW = u'new'
1650 STATUS_OPEN = u'open'
1655 STATUS_OPEN = u'open'
1651 STATUS_CLOSED = u'closed'
1656 STATUS_CLOSED = u'closed'
1652
1657
1653 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1658 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1654 title = Column('title', Unicode(256), nullable=True)
1659 title = Column('title', Unicode(256), nullable=True)
1655 description = Column('description', UnicodeText(10240), nullable=True)
1660 description = Column('description', UnicodeText(10240), nullable=True)
1656 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1661 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1657 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1662 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1658 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1663 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1659 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1664 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1660 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1665 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1661 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1666 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1662 org_ref = Column('org_ref', Unicode(256), nullable=False)
1667 org_ref = Column('org_ref', Unicode(256), nullable=False)
1663 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1668 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1664 other_ref = Column('other_ref', Unicode(256), nullable=False)
1669 other_ref = Column('other_ref', Unicode(256), nullable=False)
1665
1670
1666 @hybrid_property
1671 @hybrid_property
1667 def revisions(self):
1672 def revisions(self):
1668 return self._revisions.split(':')
1673 return self._revisions.split(':')
1669
1674
1670 @revisions.setter
1675 @revisions.setter
1671 def revisions(self, val):
1676 def revisions(self, val):
1672 self._revisions = ':'.join(val)
1677 self._revisions = ':'.join(val)
1673
1678
1674 author = relationship('User', lazy='joined')
1679 author = relationship('User', lazy='joined')
1675 reviewers = relationship('PullRequestReviewers',
1680 reviewers = relationship('PullRequestReviewers',
1676 cascade="all, delete, delete-orphan")
1681 cascade="all, delete, delete-orphan")
1677 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1682 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1678 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1683 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1679 statuses = relationship('ChangesetStatus')
1684 statuses = relationship('ChangesetStatus')
1680 comments = relationship('ChangesetComment',
1685 comments = relationship('ChangesetComment',
1681 cascade="all, delete, delete-orphan")
1686 cascade="all, delete, delete-orphan")
1682
1687
1683 def is_closed(self):
1688 def is_closed(self):
1684 return self.status == self.STATUS_CLOSED
1689 return self.status == self.STATUS_CLOSED
1685
1690
1686 def __json__(self):
1691 def __json__(self):
1687 return dict(
1692 return dict(
1688 revisions=self.revisions
1693 revisions=self.revisions
1689 )
1694 )
1690
1695
1691
1696
1692 class PullRequestReviewers(Base, BaseModel):
1697 class PullRequestReviewers(Base, BaseModel):
1693 __tablename__ = 'pull_request_reviewers'
1698 __tablename__ = 'pull_request_reviewers'
1694 __table_args__ = (
1699 __table_args__ = (
1695 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1700 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1696 'mysql_charset': 'utf8'},
1701 'mysql_charset': 'utf8'},
1697 )
1702 )
1698
1703
1699 def __init__(self, user=None, pull_request=None):
1704 def __init__(self, user=None, pull_request=None):
1700 self.user = user
1705 self.user = user
1701 self.pull_request = pull_request
1706 self.pull_request = pull_request
1702
1707
1703 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1708 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1704 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1709 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1705 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1710 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1706
1711
1707 user = relationship('User')
1712 user = relationship('User')
1708 pull_request = relationship('PullRequest')
1713 pull_request = relationship('PullRequest')
1709
1714
1710
1715
1711 class Notification(Base, BaseModel):
1716 class Notification(Base, BaseModel):
1712 __tablename__ = 'notifications'
1717 __tablename__ = 'notifications'
1713 __table_args__ = (
1718 __table_args__ = (
1714 Index('notification_type_idx', 'type'),
1719 Index('notification_type_idx', 'type'),
1715 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1720 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1716 'mysql_charset': 'utf8'},
1721 'mysql_charset': 'utf8'},
1717 )
1722 )
1718
1723
1719 TYPE_CHANGESET_COMMENT = u'cs_comment'
1724 TYPE_CHANGESET_COMMENT = u'cs_comment'
1720 TYPE_MESSAGE = u'message'
1725 TYPE_MESSAGE = u'message'
1721 TYPE_MENTION = u'mention'
1726 TYPE_MENTION = u'mention'
1722 TYPE_REGISTRATION = u'registration'
1727 TYPE_REGISTRATION = u'registration'
1723 TYPE_PULL_REQUEST = u'pull_request'
1728 TYPE_PULL_REQUEST = u'pull_request'
1724 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1729 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1725
1730
1726 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1731 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1727 subject = Column('subject', Unicode(512), nullable=True)
1732 subject = Column('subject', Unicode(512), nullable=True)
1728 body = Column('body', UnicodeText(50000), nullable=True)
1733 body = Column('body', UnicodeText(50000), nullable=True)
1729 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1734 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1730 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1735 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1731 type_ = Column('type', Unicode(256))
1736 type_ = Column('type', Unicode(256))
1732
1737
1733 created_by_user = relationship('User')
1738 created_by_user = relationship('User')
1734 notifications_to_users = relationship('UserNotification', lazy='joined',
1739 notifications_to_users = relationship('UserNotification', lazy='joined',
1735 cascade="all, delete, delete-orphan")
1740 cascade="all, delete, delete-orphan")
1736
1741
1737 @property
1742 @property
1738 def recipients(self):
1743 def recipients(self):
1739 return [x.user for x in UserNotification.query()\
1744 return [x.user for x in UserNotification.query()\
1740 .filter(UserNotification.notification == self)\
1745 .filter(UserNotification.notification == self)\
1741 .order_by(UserNotification.user_id.asc()).all()]
1746 .order_by(UserNotification.user_id.asc()).all()]
1742
1747
1743 @classmethod
1748 @classmethod
1744 def create(cls, created_by, subject, body, recipients, type_=None):
1749 def create(cls, created_by, subject, body, recipients, type_=None):
1745 if type_ is None:
1750 if type_ is None:
1746 type_ = Notification.TYPE_MESSAGE
1751 type_ = Notification.TYPE_MESSAGE
1747
1752
1748 notification = cls()
1753 notification = cls()
1749 notification.created_by_user = created_by
1754 notification.created_by_user = created_by
1750 notification.subject = subject
1755 notification.subject = subject
1751 notification.body = body
1756 notification.body = body
1752 notification.type_ = type_
1757 notification.type_ = type_
1753 notification.created_on = datetime.datetime.now()
1758 notification.created_on = datetime.datetime.now()
1754
1759
1755 for u in recipients:
1760 for u in recipients:
1756 assoc = UserNotification()
1761 assoc = UserNotification()
1757 assoc.notification = notification
1762 assoc.notification = notification
1758 u.notifications.append(assoc)
1763 u.notifications.append(assoc)
1759 Session().add(notification)
1764 Session().add(notification)
1760 return notification
1765 return notification
1761
1766
1762 @property
1767 @property
1763 def description(self):
1768 def description(self):
1764 from rhodecode.model.notification import NotificationModel
1769 from rhodecode.model.notification import NotificationModel
1765 return NotificationModel().make_description(self)
1770 return NotificationModel().make_description(self)
1766
1771
1767
1772
1768 class UserNotification(Base, BaseModel):
1773 class UserNotification(Base, BaseModel):
1769 __tablename__ = 'user_to_notification'
1774 __tablename__ = 'user_to_notification'
1770 __table_args__ = (
1775 __table_args__ = (
1771 UniqueConstraint('user_id', 'notification_id'),
1776 UniqueConstraint('user_id', 'notification_id'),
1772 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1777 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1773 'mysql_charset': 'utf8'}
1778 'mysql_charset': 'utf8'}
1774 )
1779 )
1775 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1780 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1776 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1781 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1777 read = Column('read', Boolean, default=False)
1782 read = Column('read', Boolean, default=False)
1778 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1783 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1779
1784
1780 user = relationship('User', lazy="joined")
1785 user = relationship('User', lazy="joined")
1781 notification = relationship('Notification', lazy="joined",
1786 notification = relationship('Notification', lazy="joined",
1782 order_by=lambda: Notification.created_on.desc(),)
1787 order_by=lambda: Notification.created_on.desc(),)
1783
1788
1784 def mark_as_read(self):
1789 def mark_as_read(self):
1785 self.read = True
1790 self.read = True
1786 Session().add(self)
1791 Session().add(self)
1787
1792
1788
1793
1789 class DbMigrateVersion(Base, BaseModel):
1794 class DbMigrateVersion(Base, BaseModel):
1790 __tablename__ = 'db_migrate_version'
1795 __tablename__ = 'db_migrate_version'
1791 __table_args__ = (
1796 __table_args__ = (
1792 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1793 'mysql_charset': 'utf8'},
1798 'mysql_charset': 'utf8'},
1794 )
1799 )
1795 repository_id = Column('repository_id', String(250), primary_key=True)
1800 repository_id = Column('repository_id', String(250), primary_key=True)
1796 repository_path = Column('repository_path', Text)
1801 repository_path = Column('repository_path', Text)
1797 version = Column('version', Integer)
1802 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now