##// END OF EJS Templates
fixed some unicode problems with archive downloads
marcink -
r3488:1b4fc339 beta
parent child Browse files
Show More
@@ -1,609 +1,610
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.files
3 rhodecode.controllers.files
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Files controller for RhodeCode
6 Files controller for RhodeCode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 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 from __future__ import with_statement
25 from __future__ import with_statement
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import tempfile
29 import tempfile
30
30
31 from pylons import request, response, tmpl_context as c, url
31 from pylons import request, response, tmpl_context as c, url
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from rhodecode.lib.utils import jsonify
34 from rhodecode.lib.utils import jsonify
35
35
36 from rhodecode.lib import diffs
36 from rhodecode.lib import diffs
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38
38
39 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.compat import OrderedDict
40 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
40 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
41 str2bool
41 str2bool
42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.base import BaseRepoController, render
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.conf import settings
45 from rhodecode.lib.vcs.conf import settings
46 from rhodecode.lib.vcs.exceptions import RepositoryError, \
46 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 ChangesetDoesNotExistError, EmptyRepositoryError, \
47 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
48 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
49 NodeDoesNotExistError, ChangesetError, NodeError
49 NodeDoesNotExistError, ChangesetError, NodeError
50 from rhodecode.lib.vcs.nodes import FileNode
50 from rhodecode.lib.vcs.nodes import FileNode
51
51
52 from rhodecode.model.repo import RepoModel
52 from rhodecode.model.repo import RepoModel
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.db import Repository
54 from rhodecode.model.db import Repository
55
55
56 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
56 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
57 _context_url, get_line_ctx, get_ignore_ws
57 _context_url, get_line_ctx, get_ignore_ws
58
58
59
59
60 log = logging.getLogger(__name__)
60 log = logging.getLogger(__name__)
61
61
62
62
63 class FilesController(BaseRepoController):
63 class FilesController(BaseRepoController):
64
64
65 def __before__(self):
65 def __before__(self):
66 super(FilesController, self).__before__()
66 super(FilesController, self).__before__()
67 c.cut_off_limit = self.cut_off_limit
67 c.cut_off_limit = self.cut_off_limit
68
68
69 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
69 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
70 """
70 """
71 Safe way to get changeset if error occur it redirects to tip with
71 Safe way to get changeset if error occur it redirects to tip with
72 proper message
72 proper message
73
73
74 :param rev: revision to fetch
74 :param rev: revision to fetch
75 :param repo_name: repo name to redirect after
75 :param repo_name: repo name to redirect after
76 """
76 """
77
77
78 try:
78 try:
79 return c.rhodecode_repo.get_changeset(rev)
79 return c.rhodecode_repo.get_changeset(rev)
80 except EmptyRepositoryError, e:
80 except EmptyRepositoryError, e:
81 if not redirect_after:
81 if not redirect_after:
82 return None
82 return None
83 url_ = url('files_add_home',
83 url_ = url('files_add_home',
84 repo_name=c.repo_name,
84 repo_name=c.repo_name,
85 revision=0, f_path='')
85 revision=0, f_path='')
86 add_new = '<a href="%s">[%s]</a>' % (url_, _('click here to add new file'))
86 add_new = '<a href="%s">[%s]</a>' % (url_, _('click here to add new file'))
87 h.flash(h.literal(_('There are no files yet %s') % add_new),
87 h.flash(h.literal(_('There are no files yet %s') % add_new),
88 category='warning')
88 category='warning')
89 redirect(h.url('summary_home', repo_name=repo_name))
89 redirect(h.url('summary_home', repo_name=repo_name))
90
90
91 except RepositoryError, e:
91 except RepositoryError, e:
92 h.flash(str(e), category='warning')
92 h.flash(str(e), category='warning')
93 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
93 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
94
94
95 def __get_filenode_or_redirect(self, repo_name, cs, path):
95 def __get_filenode_or_redirect(self, repo_name, cs, path):
96 """
96 """
97 Returns file_node, if error occurs or given path is directory,
97 Returns file_node, if error occurs or given path is directory,
98 it'll redirect to top level path
98 it'll redirect to top level path
99
99
100 :param repo_name: repo_name
100 :param repo_name: repo_name
101 :param cs: given changeset
101 :param cs: given changeset
102 :param path: path to lookup
102 :param path: path to lookup
103 """
103 """
104
104
105 try:
105 try:
106 file_node = cs.get_node(path)
106 file_node = cs.get_node(path)
107 if file_node.is_dir():
107 if file_node.is_dir():
108 raise RepositoryError('given path is a directory')
108 raise RepositoryError('given path is a directory')
109 except RepositoryError, e:
109 except RepositoryError, e:
110 h.flash(str(e), category='warning')
110 h.flash(str(e), category='warning')
111 redirect(h.url('files_home', repo_name=repo_name,
111 redirect(h.url('files_home', repo_name=repo_name,
112 revision=cs.raw_id))
112 revision=cs.raw_id))
113
113
114 return file_node
114 return file_node
115
115
116 @LoginRequired()
116 @LoginRequired()
117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 'repository.admin')
118 'repository.admin')
119 def index(self, repo_name, revision, f_path, annotate=False):
119 def index(self, repo_name, revision, f_path, annotate=False):
120 # redirect to given revision from form if given
120 # redirect to given revision from form if given
121 post_revision = request.POST.get('at_rev', None)
121 post_revision = request.POST.get('at_rev', None)
122 if post_revision:
122 if post_revision:
123 cs = self.__get_cs_or_redirect(post_revision, repo_name)
123 cs = self.__get_cs_or_redirect(post_revision, repo_name)
124 redirect(url('files_home', repo_name=c.repo_name,
124 redirect(url('files_home', repo_name=c.repo_name,
125 revision=cs.raw_id, f_path=f_path))
125 revision=cs.raw_id, f_path=f_path))
126
126
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
128 c.branch = request.GET.get('branch', None)
128 c.branch = request.GET.get('branch', None)
129 c.f_path = f_path
129 c.f_path = f_path
130 c.annotate = annotate
130 c.annotate = annotate
131 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
131 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
132 cur_rev = c.changeset.revision
132 cur_rev = c.changeset.revision
133
133
134 # prev link
134 # prev link
135 try:
135 try:
136 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
137 c.url_prev = url('files_home', repo_name=c.repo_name,
137 c.url_prev = url('files_home', repo_name=c.repo_name,
138 revision=prev_rev.raw_id, f_path=f_path)
138 revision=prev_rev.raw_id, f_path=f_path)
139 if c.branch:
139 if c.branch:
140 c.url_prev += '?branch=%s' % c.branch
140 c.url_prev += '?branch=%s' % c.branch
141 except (ChangesetDoesNotExistError, VCSError):
141 except (ChangesetDoesNotExistError, VCSError):
142 c.url_prev = '#'
142 c.url_prev = '#'
143
143
144 # next link
144 # next link
145 try:
145 try:
146 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
147 c.url_next = url('files_home', repo_name=c.repo_name,
147 c.url_next = url('files_home', repo_name=c.repo_name,
148 revision=next_rev.raw_id, f_path=f_path)
148 revision=next_rev.raw_id, f_path=f_path)
149 if c.branch:
149 if c.branch:
150 c.url_next += '?branch=%s' % c.branch
150 c.url_next += '?branch=%s' % c.branch
151 except (ChangesetDoesNotExistError, VCSError):
151 except (ChangesetDoesNotExistError, VCSError):
152 c.url_next = '#'
152 c.url_next = '#'
153
153
154 # files or dirs
154 # files or dirs
155 try:
155 try:
156 c.file = c.changeset.get_node(f_path)
156 c.file = c.changeset.get_node(f_path)
157
157
158 if c.file.is_file():
158 if c.file.is_file():
159 c.load_full_history = False
159 c.load_full_history = False
160 file_last_cs = c.file.last_changeset
160 file_last_cs = c.file.last_changeset
161 c.file_changeset = (c.changeset
161 c.file_changeset = (c.changeset
162 if c.changeset.revision < file_last_cs.revision
162 if c.changeset.revision < file_last_cs.revision
163 else file_last_cs)
163 else file_last_cs)
164 #determine if we're on branch head
164 #determine if we're on branch head
165 _branches = c.rhodecode_repo.branches
165 _branches = c.rhodecode_repo.branches
166 c.on_branch_head = revision in _branches.keys() + _branches.values()
166 c.on_branch_head = revision in _branches.keys() + _branches.values()
167 _hist = []
167 _hist = []
168 c.file_history = []
168 c.file_history = []
169 if c.load_full_history:
169 if c.load_full_history:
170 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
170 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
171
171
172 c.authors = []
172 c.authors = []
173 for a in set([x.author for x in _hist]):
173 for a in set([x.author for x in _hist]):
174 c.authors.append((h.email(a), h.person(a)))
174 c.authors.append((h.email(a), h.person(a)))
175 else:
175 else:
176 c.authors = c.file_history = []
176 c.authors = c.file_history = []
177 except RepositoryError, e:
177 except RepositoryError, e:
178 h.flash(str(e), category='warning')
178 h.flash(str(e), category='warning')
179 redirect(h.url('files_home', repo_name=repo_name,
179 redirect(h.url('files_home', repo_name=repo_name,
180 revision='tip'))
180 revision='tip'))
181
181
182 if request.environ.get('HTTP_X_PARTIAL_XHR'):
182 if request.environ.get('HTTP_X_PARTIAL_XHR'):
183 return render('files/files_ypjax.html')
183 return render('files/files_ypjax.html')
184
184
185 return render('files/files.html')
185 return render('files/files.html')
186
186
187 def history(self, repo_name, revision, f_path, annotate=False):
187 def history(self, repo_name, revision, f_path, annotate=False):
188 if request.environ.get('HTTP_X_PARTIAL_XHR'):
188 if request.environ.get('HTTP_X_PARTIAL_XHR'):
189 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
189 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
190 c.f_path = f_path
190 c.f_path = f_path
191 c.annotate = annotate
191 c.annotate = annotate
192 c.file = c.changeset.get_node(f_path)
192 c.file = c.changeset.get_node(f_path)
193 if c.file.is_file():
193 if c.file.is_file():
194 file_last_cs = c.file.last_changeset
194 file_last_cs = c.file.last_changeset
195 c.file_changeset = (c.changeset
195 c.file_changeset = (c.changeset
196 if c.changeset.revision < file_last_cs.revision
196 if c.changeset.revision < file_last_cs.revision
197 else file_last_cs)
197 else file_last_cs)
198 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
198 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
199 c.authors = []
199 c.authors = []
200 for a in set([x.author for x in _hist]):
200 for a in set([x.author for x in _hist]):
201 c.authors.append((h.email(a), h.person(a)))
201 c.authors.append((h.email(a), h.person(a)))
202 return render('files/files_history_box.html')
202 return render('files/files_history_box.html')
203
203
204 @LoginRequired()
204 @LoginRequired()
205 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
205 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
206 'repository.admin')
206 'repository.admin')
207 def rawfile(self, repo_name, revision, f_path):
207 def rawfile(self, repo_name, revision, f_path):
208 cs = self.__get_cs_or_redirect(revision, repo_name)
208 cs = self.__get_cs_or_redirect(revision, repo_name)
209 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
209 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
210
210
211 response.content_disposition = 'attachment; filename=%s' % \
211 response.content_disposition = 'attachment; filename=%s' % \
212 safe_str(f_path.split(Repository.url_sep())[-1])
212 safe_str(f_path.split(Repository.url_sep())[-1])
213
213
214 response.content_type = file_node.mimetype
214 response.content_type = file_node.mimetype
215 return file_node.content
215 return file_node.content
216
216
217 @LoginRequired()
217 @LoginRequired()
218 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
218 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
219 'repository.admin')
219 'repository.admin')
220 def raw(self, repo_name, revision, f_path):
220 def raw(self, repo_name, revision, f_path):
221 cs = self.__get_cs_or_redirect(revision, repo_name)
221 cs = self.__get_cs_or_redirect(revision, repo_name)
222 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
222 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
223
223
224 raw_mimetype_mapping = {
224 raw_mimetype_mapping = {
225 # map original mimetype to a mimetype used for "show as raw"
225 # map original mimetype to a mimetype used for "show as raw"
226 # you can also provide a content-disposition to override the
226 # you can also provide a content-disposition to override the
227 # default "attachment" disposition.
227 # default "attachment" disposition.
228 # orig_type: (new_type, new_dispo)
228 # orig_type: (new_type, new_dispo)
229
229
230 # show images inline:
230 # show images inline:
231 'image/x-icon': ('image/x-icon', 'inline'),
231 'image/x-icon': ('image/x-icon', 'inline'),
232 'image/png': ('image/png', 'inline'),
232 'image/png': ('image/png', 'inline'),
233 'image/gif': ('image/gif', 'inline'),
233 'image/gif': ('image/gif', 'inline'),
234 'image/jpeg': ('image/jpeg', 'inline'),
234 'image/jpeg': ('image/jpeg', 'inline'),
235 'image/svg+xml': ('image/svg+xml', 'inline'),
235 'image/svg+xml': ('image/svg+xml', 'inline'),
236 }
236 }
237
237
238 mimetype = file_node.mimetype
238 mimetype = file_node.mimetype
239 try:
239 try:
240 mimetype, dispo = raw_mimetype_mapping[mimetype]
240 mimetype, dispo = raw_mimetype_mapping[mimetype]
241 except KeyError:
241 except KeyError:
242 # we don't know anything special about this, handle it safely
242 # we don't know anything special about this, handle it safely
243 if file_node.is_binary:
243 if file_node.is_binary:
244 # do same as download raw for binary files
244 # do same as download raw for binary files
245 mimetype, dispo = 'application/octet-stream', 'attachment'
245 mimetype, dispo = 'application/octet-stream', 'attachment'
246 else:
246 else:
247 # do not just use the original mimetype, but force text/plain,
247 # do not just use the original mimetype, but force text/plain,
248 # otherwise it would serve text/html and that might be unsafe.
248 # otherwise it would serve text/html and that might be unsafe.
249 # Note: underlying vcs library fakes text/plain mimetype if the
249 # Note: underlying vcs library fakes text/plain mimetype if the
250 # mimetype can not be determined and it thinks it is not
250 # mimetype can not be determined and it thinks it is not
251 # binary.This might lead to erroneous text display in some
251 # binary.This might lead to erroneous text display in some
252 # cases, but helps in other cases, like with text files
252 # cases, but helps in other cases, like with text files
253 # without extension.
253 # without extension.
254 mimetype, dispo = 'text/plain', 'inline'
254 mimetype, dispo = 'text/plain', 'inline'
255
255
256 if dispo == 'attachment':
256 if dispo == 'attachment':
257 dispo = 'attachment; filename=%s' % \
257 dispo = 'attachment; filename=%s' % \
258 safe_str(f_path.split(os.sep)[-1])
258 safe_str(f_path.split(os.sep)[-1])
259
259
260 response.content_disposition = dispo
260 response.content_disposition = dispo
261 response.content_type = mimetype
261 response.content_type = mimetype
262 return file_node.content
262 return file_node.content
263
263
264 @LoginRequired()
264 @LoginRequired()
265 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
265 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
266 def edit(self, repo_name, revision, f_path):
266 def edit(self, repo_name, revision, f_path):
267 repo = c.rhodecode_db_repo
267 repo = c.rhodecode_db_repo
268 if repo.enable_locking and repo.locked[0]:
268 if repo.enable_locking and repo.locked[0]:
269 h.flash(_('This repository is has been locked by %s on %s')
269 h.flash(_('This repository is has been locked by %s on %s')
270 % (h.person_by_id(repo.locked[0]),
270 % (h.person_by_id(repo.locked[0]),
271 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
271 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
272 'warning')
272 'warning')
273 return redirect(h.url('files_home',
273 return redirect(h.url('files_home',
274 repo_name=repo_name, revision='tip'))
274 repo_name=repo_name, revision='tip'))
275
275
276 # check if revision is a branch identifier- basically we cannot
276 # check if revision is a branch identifier- basically we cannot
277 # create multiple heads via file editing
277 # create multiple heads via file editing
278 _branches = repo.scm_instance.branches
278 _branches = repo.scm_instance.branches
279 # check if revision is a branch name or branch hash
279 # check if revision is a branch name or branch hash
280 if revision not in _branches.keys() + _branches.values():
280 if revision not in _branches.keys() + _branches.values():
281 h.flash(_('You can only edit files with revision '
281 h.flash(_('You can only edit files with revision '
282 'being a valid branch '), category='warning')
282 'being a valid branch '), category='warning')
283 return redirect(h.url('files_home',
283 return redirect(h.url('files_home',
284 repo_name=repo_name, revision='tip',
284 repo_name=repo_name, revision='tip',
285 f_path=f_path))
285 f_path=f_path))
286
286
287 r_post = request.POST
287 r_post = request.POST
288
288
289 c.cs = self.__get_cs_or_redirect(revision, repo_name)
289 c.cs = self.__get_cs_or_redirect(revision, repo_name)
290 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
290 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
291
291
292 if c.file.is_binary:
292 if c.file.is_binary:
293 return redirect(url('files_home', repo_name=c.repo_name,
293 return redirect(url('files_home', repo_name=c.repo_name,
294 revision=c.cs.raw_id, f_path=f_path))
294 revision=c.cs.raw_id, f_path=f_path))
295 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
295 c.default_message = _('Edited file %s via RhodeCode') % (f_path)
296 c.f_path = f_path
296 c.f_path = f_path
297
297
298 if r_post:
298 if r_post:
299
299
300 old_content = c.file.content
300 old_content = c.file.content
301 sl = old_content.splitlines(1)
301 sl = old_content.splitlines(1)
302 first_line = sl[0] if sl else ''
302 first_line = sl[0] if sl else ''
303 # modes: 0 - Unix, 1 - Mac, 2 - DOS
303 # modes: 0 - Unix, 1 - Mac, 2 - DOS
304 mode = detect_mode(first_line, 0)
304 mode = detect_mode(first_line, 0)
305 content = convert_line_endings(r_post.get('content'), mode)
305 content = convert_line_endings(r_post.get('content'), mode)
306
306
307 message = r_post.get('message') or c.default_message
307 message = r_post.get('message') or c.default_message
308 author = self.rhodecode_user.full_contact
308 author = self.rhodecode_user.full_contact
309
309
310 if content == old_content:
310 if content == old_content:
311 h.flash(_('No changes'),
311 h.flash(_('No changes'),
312 category='warning')
312 category='warning')
313 return redirect(url('changeset_home', repo_name=c.repo_name,
313 return redirect(url('changeset_home', repo_name=c.repo_name,
314 revision='tip'))
314 revision='tip'))
315 try:
315 try:
316 self.scm_model.commit_change(repo=c.rhodecode_repo,
316 self.scm_model.commit_change(repo=c.rhodecode_repo,
317 repo_name=repo_name, cs=c.cs,
317 repo_name=repo_name, cs=c.cs,
318 user=self.rhodecode_user.user_id,
318 user=self.rhodecode_user.user_id,
319 author=author, message=message,
319 author=author, message=message,
320 content=content, f_path=f_path)
320 content=content, f_path=f_path)
321 h.flash(_('Successfully committed to %s') % f_path,
321 h.flash(_('Successfully committed to %s') % f_path,
322 category='success')
322 category='success')
323
323
324 except Exception:
324 except Exception:
325 log.error(traceback.format_exc())
325 log.error(traceback.format_exc())
326 h.flash(_('Error occurred during commit'), category='error')
326 h.flash(_('Error occurred during commit'), category='error')
327 return redirect(url('changeset_home',
327 return redirect(url('changeset_home',
328 repo_name=c.repo_name, revision='tip'))
328 repo_name=c.repo_name, revision='tip'))
329
329
330 return render('files/files_edit.html')
330 return render('files/files_edit.html')
331
331
332 @LoginRequired()
332 @LoginRequired()
333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
333 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
334 def add(self, repo_name, revision, f_path):
334 def add(self, repo_name, revision, f_path):
335
335
336 repo = Repository.get_by_repo_name(repo_name)
336 repo = Repository.get_by_repo_name(repo_name)
337 if repo.enable_locking and repo.locked[0]:
337 if repo.enable_locking and repo.locked[0]:
338 h.flash(_('This repository is has been locked by %s on %s')
338 h.flash(_('This repository is has been locked by %s on %s')
339 % (h.person_by_id(repo.locked[0]),
339 % (h.person_by_id(repo.locked[0]),
340 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
340 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
341 'warning')
341 'warning')
342 return redirect(h.url('files_home',
342 return redirect(h.url('files_home',
343 repo_name=repo_name, revision='tip'))
343 repo_name=repo_name, revision='tip'))
344
344
345 r_post = request.POST
345 r_post = request.POST
346 c.cs = self.__get_cs_or_redirect(revision, repo_name,
346 c.cs = self.__get_cs_or_redirect(revision, repo_name,
347 redirect_after=False)
347 redirect_after=False)
348 if c.cs is None:
348 if c.cs is None:
349 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
349 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
350 c.default_message = (_('Added file via RhodeCode'))
350 c.default_message = (_('Added file via RhodeCode'))
351 c.f_path = f_path
351 c.f_path = f_path
352
352
353 if r_post:
353 if r_post:
354 unix_mode = 0
354 unix_mode = 0
355 content = convert_line_endings(r_post.get('content'), unix_mode)
355 content = convert_line_endings(r_post.get('content'), unix_mode)
356
356
357 message = r_post.get('message') or c.default_message
357 message = r_post.get('message') or c.default_message
358 location = r_post.get('location')
358 location = r_post.get('location')
359 filename = r_post.get('filename')
359 filename = r_post.get('filename')
360 file_obj = r_post.get('upload_file', None)
360 file_obj = r_post.get('upload_file', None)
361
361
362 if file_obj is not None and hasattr(file_obj, 'filename'):
362 if file_obj is not None and hasattr(file_obj, 'filename'):
363 filename = file_obj.filename
363 filename = file_obj.filename
364 content = file_obj.file
364 content = file_obj.file
365
365
366 node_path = os.path.join(location, filename)
366 node_path = os.path.join(location, filename)
367 author = self.rhodecode_user.full_contact
367 author = self.rhodecode_user.full_contact
368
368
369 if not content:
369 if not content:
370 h.flash(_('No content'), category='warning')
370 h.flash(_('No content'), category='warning')
371 return redirect(url('changeset_home', repo_name=c.repo_name,
371 return redirect(url('changeset_home', repo_name=c.repo_name,
372 revision='tip'))
372 revision='tip'))
373 if not filename:
373 if not filename:
374 h.flash(_('No filename'), category='warning')
374 h.flash(_('No filename'), category='warning')
375 return redirect(url('changeset_home', repo_name=c.repo_name,
375 return redirect(url('changeset_home', repo_name=c.repo_name,
376 revision='tip'))
376 revision='tip'))
377
377
378 try:
378 try:
379 self.scm_model.create_node(repo=c.rhodecode_repo,
379 self.scm_model.create_node(repo=c.rhodecode_repo,
380 repo_name=repo_name, cs=c.cs,
380 repo_name=repo_name, cs=c.cs,
381 user=self.rhodecode_user.user_id,
381 user=self.rhodecode_user.user_id,
382 author=author, message=message,
382 author=author, message=message,
383 content=content, f_path=node_path)
383 content=content, f_path=node_path)
384 h.flash(_('Successfully committed to %s') % node_path,
384 h.flash(_('Successfully committed to %s') % node_path,
385 category='success')
385 category='success')
386 except NodeAlreadyExistsError, e:
386 except NodeAlreadyExistsError, e:
387 h.flash(_(e), category='error')
387 h.flash(_(e), category='error')
388 except Exception:
388 except Exception:
389 log.error(traceback.format_exc())
389 log.error(traceback.format_exc())
390 h.flash(_('Error occurred during commit'), category='error')
390 h.flash(_('Error occurred during commit'), category='error')
391 return redirect(url('changeset_home',
391 return redirect(url('changeset_home',
392 repo_name=c.repo_name, revision='tip'))
392 repo_name=c.repo_name, revision='tip'))
393
393
394 return render('files/files_add.html')
394 return render('files/files_add.html')
395
395
396 @LoginRequired()
396 @LoginRequired()
397 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
397 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
398 'repository.admin')
398 'repository.admin')
399 def archivefile(self, repo_name, fname):
399 def archivefile(self, repo_name, fname):
400
400
401 fileformat = None
401 fileformat = None
402 revision = None
402 revision = None
403 ext = None
403 ext = None
404 subrepos = request.GET.get('subrepos') == 'true'
404 subrepos = request.GET.get('subrepos') == 'true'
405
405
406 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
406 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
407 archive_spec = fname.split(ext_data[1])
407 archive_spec = fname.split(ext_data[1])
408 if len(archive_spec) == 2 and archive_spec[1] == '':
408 if len(archive_spec) == 2 and archive_spec[1] == '':
409 fileformat = a_type or ext_data[1]
409 fileformat = a_type or ext_data[1]
410 revision = archive_spec[0]
410 revision = archive_spec[0]
411 ext = ext_data[1]
411 ext = ext_data[1]
412
412
413 try:
413 try:
414 dbrepo = RepoModel().get_by_repo_name(repo_name)
414 dbrepo = RepoModel().get_by_repo_name(repo_name)
415 if dbrepo.enable_downloads is False:
415 if dbrepo.enable_downloads is False:
416 return _('downloads disabled')
416 return _('downloads disabled')
417
417
418 if c.rhodecode_repo.alias == 'hg':
418 if c.rhodecode_repo.alias == 'hg':
419 # patch and reset hooks section of UI config to not run any
419 # patch and reset hooks section of UI config to not run any
420 # hooks on fetching archives with subrepos
420 # hooks on fetching archives with subrepos
421 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
421 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
422 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
422 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
423
423
424 cs = c.rhodecode_repo.get_changeset(revision)
424 cs = c.rhodecode_repo.get_changeset(revision)
425 content_type = settings.ARCHIVE_SPECS[fileformat][0]
425 content_type = settings.ARCHIVE_SPECS[fileformat][0]
426 except ChangesetDoesNotExistError:
426 except ChangesetDoesNotExistError:
427 return _('Unknown revision %s') % revision
427 return _('Unknown revision %s') % revision
428 except EmptyRepositoryError:
428 except EmptyRepositoryError:
429 return _('Empty repository')
429 return _('Empty repository')
430 except (ImproperArchiveTypeError, KeyError):
430 except (ImproperArchiveTypeError, KeyError):
431 return _('Unknown archive type')
431 return _('Unknown archive type')
432
432
433 fd, archive = tempfile.mkstemp()
433 fd, archive = tempfile.mkstemp()
434 t = open(archive, 'wb')
434 t = open(archive, 'wb')
435 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
435 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
436 t.close()
436 t.close()
437
437
438 def get_chunked_archive(archive):
438 def get_chunked_archive(archive):
439 stream = open(archive, 'rb')
439 stream = open(archive, 'rb')
440 while True:
440 while True:
441 data = stream.read(16 * 1024)
441 data = stream.read(16 * 1024)
442 if not data:
442 if not data:
443 stream.close()
443 stream.close()
444 os.close(fd)
444 os.close(fd)
445 os.remove(archive)
445 os.remove(archive)
446 break
446 break
447 yield data
447 yield data
448
448
449 response.content_disposition = str('attachment; filename=%s-%s%s' \
449 response.content_disposition = str('attachment; filename=%s-%s%s' \
450 % (repo_name, revision[:12], ext))
450 % (safe_str(repo_name),
451 safe_str(revision), ext))
451 response.content_type = str(content_type)
452 response.content_type = str(content_type)
452 return get_chunked_archive(archive)
453 return get_chunked_archive(archive)
453
454
454 @LoginRequired()
455 @LoginRequired()
455 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
456 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
456 'repository.admin')
457 'repository.admin')
457 def diff(self, repo_name, f_path):
458 def diff(self, repo_name, f_path):
458 ignore_whitespace = request.GET.get('ignorews') == '1'
459 ignore_whitespace = request.GET.get('ignorews') == '1'
459 line_context = request.GET.get('context', 3)
460 line_context = request.GET.get('context', 3)
460 diff1 = request.GET.get('diff1', '')
461 diff1 = request.GET.get('diff1', '')
461 diff2 = request.GET.get('diff2', '')
462 diff2 = request.GET.get('diff2', '')
462 c.action = request.GET.get('diff')
463 c.action = request.GET.get('diff')
463 c.no_changes = diff1 == diff2
464 c.no_changes = diff1 == diff2
464 c.f_path = f_path
465 c.f_path = f_path
465 c.big_diff = False
466 c.big_diff = False
466 c.anchor_url = anchor_url
467 c.anchor_url = anchor_url
467 c.ignorews_url = _ignorews_url
468 c.ignorews_url = _ignorews_url
468 c.context_url = _context_url
469 c.context_url = _context_url
469 c.changes = OrderedDict()
470 c.changes = OrderedDict()
470 c.changes[diff2] = []
471 c.changes[diff2] = []
471
472
472 #special case if we want a show rev only, it's impl here
473 #special case if we want a show rev only, it's impl here
473 #to reduce JS and callbacks
474 #to reduce JS and callbacks
474
475
475 if request.GET.get('show_rev'):
476 if request.GET.get('show_rev'):
476 if str2bool(request.GET.get('annotate', 'False')):
477 if str2bool(request.GET.get('annotate', 'False')):
477 _url = url('files_annotate_home', repo_name=c.repo_name,
478 _url = url('files_annotate_home', repo_name=c.repo_name,
478 revision=diff1, f_path=c.f_path)
479 revision=diff1, f_path=c.f_path)
479 else:
480 else:
480 _url = url('files_home', repo_name=c.repo_name,
481 _url = url('files_home', repo_name=c.repo_name,
481 revision=diff1, f_path=c.f_path)
482 revision=diff1, f_path=c.f_path)
482
483
483 return redirect(_url)
484 return redirect(_url)
484 try:
485 try:
485 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
486 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
486 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
487 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
487 try:
488 try:
488 node1 = c.changeset_1.get_node(f_path)
489 node1 = c.changeset_1.get_node(f_path)
489 if node1.is_dir():
490 if node1.is_dir():
490 raise NodeError('%s path is a %s not a file' % (node1, type(node1)))
491 raise NodeError('%s path is a %s not a file' % (node1, type(node1)))
491 except NodeDoesNotExistError:
492 except NodeDoesNotExistError:
492 c.changeset_1 = EmptyChangeset(cs=diff1,
493 c.changeset_1 = EmptyChangeset(cs=diff1,
493 revision=c.changeset_1.revision,
494 revision=c.changeset_1.revision,
494 repo=c.rhodecode_repo)
495 repo=c.rhodecode_repo)
495 node1 = FileNode(f_path, '', changeset=c.changeset_1)
496 node1 = FileNode(f_path, '', changeset=c.changeset_1)
496 else:
497 else:
497 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
498 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
498 node1 = FileNode(f_path, '', changeset=c.changeset_1)
499 node1 = FileNode(f_path, '', changeset=c.changeset_1)
499
500
500 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
501 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
501 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
502 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
502 try:
503 try:
503 node2 = c.changeset_2.get_node(f_path)
504 node2 = c.changeset_2.get_node(f_path)
504 raise NodeError('%s path is a %s not a file' % (node2, type(node2)))
505 raise NodeError('%s path is a %s not a file' % (node2, type(node2)))
505 except NodeDoesNotExistError:
506 except NodeDoesNotExistError:
506 c.changeset_2 = EmptyChangeset(cs=diff2,
507 c.changeset_2 = EmptyChangeset(cs=diff2,
507 revision=c.changeset_2.revision,
508 revision=c.changeset_2.revision,
508 repo=c.rhodecode_repo)
509 repo=c.rhodecode_repo)
509 node2 = FileNode(f_path, '', changeset=c.changeset_2)
510 node2 = FileNode(f_path, '', changeset=c.changeset_2)
510 else:
511 else:
511 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
512 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
512 node2 = FileNode(f_path, '', changeset=c.changeset_2)
513 node2 = FileNode(f_path, '', changeset=c.changeset_2)
513 except (RepositoryError, NodeError):
514 except (RepositoryError, NodeError):
514 log.error(traceback.format_exc())
515 log.error(traceback.format_exc())
515 return redirect(url('files_home', repo_name=c.repo_name,
516 return redirect(url('files_home', repo_name=c.repo_name,
516 f_path=f_path))
517 f_path=f_path))
517
518
518 if c.action == 'download':
519 if c.action == 'download':
519 _diff = diffs.get_gitdiff(node1, node2,
520 _diff = diffs.get_gitdiff(node1, node2,
520 ignore_whitespace=ignore_whitespace,
521 ignore_whitespace=ignore_whitespace,
521 context=line_context)
522 context=line_context)
522 diff = diffs.DiffProcessor(_diff, format='gitdiff')
523 diff = diffs.DiffProcessor(_diff, format='gitdiff')
523
524
524 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
525 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
525 response.content_type = 'text/plain'
526 response.content_type = 'text/plain'
526 response.content_disposition = (
527 response.content_disposition = (
527 'attachment; filename=%s' % diff_name
528 'attachment; filename=%s' % diff_name
528 )
529 )
529 return diff.as_raw()
530 return diff.as_raw()
530
531
531 elif c.action == 'raw':
532 elif c.action == 'raw':
532 _diff = diffs.get_gitdiff(node1, node2,
533 _diff = diffs.get_gitdiff(node1, node2,
533 ignore_whitespace=ignore_whitespace,
534 ignore_whitespace=ignore_whitespace,
534 context=line_context)
535 context=line_context)
535 diff = diffs.DiffProcessor(_diff, format='gitdiff')
536 diff = diffs.DiffProcessor(_diff, format='gitdiff')
536 response.content_type = 'text/plain'
537 response.content_type = 'text/plain'
537 return diff.as_raw()
538 return diff.as_raw()
538
539
539 else:
540 else:
540 fid = h.FID(diff2, node2.path)
541 fid = h.FID(diff2, node2.path)
541 line_context_lcl = get_line_ctx(fid, request.GET)
542 line_context_lcl = get_line_ctx(fid, request.GET)
542 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
543 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
543
544
544 lim = request.GET.get('fulldiff') or self.cut_off_limit
545 lim = request.GET.get('fulldiff') or self.cut_off_limit
545 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
546 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
546 filenode_new=node2,
547 filenode_new=node2,
547 cut_off_limit=lim,
548 cut_off_limit=lim,
548 ignore_whitespace=ign_whitespace_lcl,
549 ignore_whitespace=ign_whitespace_lcl,
549 line_context=line_context_lcl,
550 line_context=line_context_lcl,
550 enable_comments=False)
551 enable_comments=False)
551 op = ''
552 op = ''
552 filename = node1.path
553 filename = node1.path
553 cs_changes = {
554 cs_changes = {
554 'fid': [cs1, cs2, op, filename, diff, st]
555 'fid': [cs1, cs2, op, filename, diff, st]
555 }
556 }
556 c.changes = cs_changes
557 c.changes = cs_changes
557
558
558 return render('files/file_diff.html')
559 return render('files/file_diff.html')
559
560
560 def _get_node_history(self, cs, f_path, changesets=None):
561 def _get_node_history(self, cs, f_path, changesets=None):
561 """
562 """
562 get changesets history for given node
563 get changesets history for given node
563
564
564 :param cs: changeset to calculate history
565 :param cs: changeset to calculate history
565 :param f_path: path for node to calculate history for
566 :param f_path: path for node to calculate history for
566 :param changesets: if passed don't calculate history and take
567 :param changesets: if passed don't calculate history and take
567 changesets defined in this list
568 changesets defined in this list
568 """
569 """
569 # calculate history based on tip
570 # calculate history based on tip
570 tip_cs = c.rhodecode_repo.get_changeset()
571 tip_cs = c.rhodecode_repo.get_changeset()
571 if changesets is None:
572 if changesets is None:
572 try:
573 try:
573 changesets = tip_cs.get_file_history(f_path)
574 changesets = tip_cs.get_file_history(f_path)
574 except (NodeDoesNotExistError, ChangesetError):
575 except (NodeDoesNotExistError, ChangesetError):
575 #this node is not present at tip !
576 #this node is not present at tip !
576 changesets = cs.get_file_history(f_path)
577 changesets = cs.get_file_history(f_path)
577 hist_l = []
578 hist_l = []
578
579
579 changesets_group = ([], _("Changesets"))
580 changesets_group = ([], _("Changesets"))
580 branches_group = ([], _("Branches"))
581 branches_group = ([], _("Branches"))
581 tags_group = ([], _("Tags"))
582 tags_group = ([], _("Tags"))
582 _hg = cs.repository.alias == 'hg'
583 _hg = cs.repository.alias == 'hg'
583 for chs in changesets:
584 for chs in changesets:
584 #_branch = '(%s)' % chs.branch if _hg else ''
585 #_branch = '(%s)' % chs.branch if _hg else ''
585 _branch = chs.branch
586 _branch = chs.branch
586 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
587 n_desc = 'r%s:%s (%s)' % (chs.revision, chs.short_id, _branch)
587 changesets_group[0].append((chs.raw_id, n_desc,))
588 changesets_group[0].append((chs.raw_id, n_desc,))
588 hist_l.append(changesets_group)
589 hist_l.append(changesets_group)
589
590
590 for name, chs in c.rhodecode_repo.branches.items():
591 for name, chs in c.rhodecode_repo.branches.items():
591 branches_group[0].append((chs, name),)
592 branches_group[0].append((chs, name),)
592 hist_l.append(branches_group)
593 hist_l.append(branches_group)
593
594
594 for name, chs in c.rhodecode_repo.tags.items():
595 for name, chs in c.rhodecode_repo.tags.items():
595 tags_group[0].append((chs, name),)
596 tags_group[0].append((chs, name),)
596 hist_l.append(tags_group)
597 hist_l.append(tags_group)
597
598
598 return hist_l, changesets
599 return hist_l, changesets
599
600
600 @LoginRequired()
601 @LoginRequired()
601 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
602 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
602 'repository.admin')
603 'repository.admin')
603 @jsonify
604 @jsonify
604 def nodelist(self, repo_name, revision, f_path):
605 def nodelist(self, repo_name, revision, f_path):
605 if request.environ.get('HTTP_X_PARTIAL_XHR'):
606 if request.environ.get('HTTP_X_PARTIAL_XHR'):
606 cs = self.__get_cs_or_redirect(revision, repo_name)
607 cs = self.__get_cs_or_redirect(revision, repo_name)
607 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
608 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
608 flat=False)
609 flat=False)
609 return {'nodes': _d + _f}
610 return {'nodes': _d + _f}
@@ -1,2053 +1,2053
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 from rhodecode.lib.vcs.backends.base import EmptyChangeset
47 from rhodecode.lib.vcs.backends.base import EmptyChangeset
48
48
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
49 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
50 safe_unicode, remove_suffix, remove_prefix
50 safe_unicode, remove_suffix, remove_prefix
51 from rhodecode.lib.compat import json
51 from rhodecode.lib.compat import json
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model.meta import Base, Session
54 from rhodecode.model.meta import Base, Session
55
55
56 URL_SEP = '/'
56 URL_SEP = '/'
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59 #==============================================================================
59 #==============================================================================
60 # BASE CLASSES
60 # BASE CLASSES
61 #==============================================================================
61 #==============================================================================
62
62
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
63 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
64
64
65
65
66 class BaseModel(object):
66 class BaseModel(object):
67 """
67 """
68 Base Model for all classess
68 Base Model for all classess
69 """
69 """
70
70
71 @classmethod
71 @classmethod
72 def _get_keys(cls):
72 def _get_keys(cls):
73 """return column names for this model """
73 """return column names for this model """
74 return class_mapper(cls).c.keys()
74 return class_mapper(cls).c.keys()
75
75
76 def get_dict(self):
76 def get_dict(self):
77 """
77 """
78 return dict with keys and values corresponding
78 return dict with keys and values corresponding
79 to this model data """
79 to this model data """
80
80
81 d = {}
81 d = {}
82 for k in self._get_keys():
82 for k in self._get_keys():
83 d[k] = getattr(self, k)
83 d[k] = getattr(self, k)
84
84
85 # also use __json__() if present to get additional fields
85 # also use __json__() if present to get additional fields
86 _json_attr = getattr(self, '__json__', None)
86 _json_attr = getattr(self, '__json__', None)
87 if _json_attr:
87 if _json_attr:
88 # update with attributes from __json__
88 # update with attributes from __json__
89 if callable(_json_attr):
89 if callable(_json_attr):
90 _json_attr = _json_attr()
90 _json_attr = _json_attr()
91 for k, val in _json_attr.iteritems():
91 for k, val in _json_attr.iteritems():
92 d[k] = val
92 d[k] = val
93 return d
93 return d
94
94
95 def get_appstruct(self):
95 def get_appstruct(self):
96 """return list with keys and values tupples corresponding
96 """return list with keys and values tupples corresponding
97 to this model data """
97 to this model data """
98
98
99 l = []
99 l = []
100 for k in self._get_keys():
100 for k in self._get_keys():
101 l.append((k, getattr(self, k),))
101 l.append((k, getattr(self, k),))
102 return l
102 return l
103
103
104 def populate_obj(self, populate_dict):
104 def populate_obj(self, populate_dict):
105 """populate model with data from given populate_dict"""
105 """populate model with data from given populate_dict"""
106
106
107 for k in self._get_keys():
107 for k in self._get_keys():
108 if k in populate_dict:
108 if k in populate_dict:
109 setattr(self, k, populate_dict[k])
109 setattr(self, k, populate_dict[k])
110
110
111 @classmethod
111 @classmethod
112 def query(cls):
112 def query(cls):
113 return Session().query(cls)
113 return Session().query(cls)
114
114
115 @classmethod
115 @classmethod
116 def get(cls, id_):
116 def get(cls, id_):
117 if id_:
117 if id_:
118 return cls.query().get(id_)
118 return cls.query().get(id_)
119
119
120 @classmethod
120 @classmethod
121 def get_or_404(cls, id_):
121 def get_or_404(cls, id_):
122 try:
122 try:
123 id_ = int(id_)
123 id_ = int(id_)
124 except (TypeError, ValueError):
124 except (TypeError, ValueError):
125 raise HTTPNotFound
125 raise HTTPNotFound
126
126
127 res = cls.query().get(id_)
127 res = cls.query().get(id_)
128 if not res:
128 if not res:
129 raise HTTPNotFound
129 raise HTTPNotFound
130 return res
130 return res
131
131
132 @classmethod
132 @classmethod
133 def getAll(cls):
133 def getAll(cls):
134 return cls.query().all()
134 return cls.query().all()
135
135
136 @classmethod
136 @classmethod
137 def delete(cls, id_):
137 def delete(cls, id_):
138 obj = cls.query().get(id_)
138 obj = cls.query().get(id_)
139 Session().delete(obj)
139 Session().delete(obj)
140
140
141 def __repr__(self):
141 def __repr__(self):
142 if hasattr(self, '__unicode__'):
142 if hasattr(self, '__unicode__'):
143 # python repr needs to return str
143 # python repr needs to return str
144 return safe_str(self.__unicode__())
144 return safe_str(self.__unicode__())
145 return '<DB:%s>' % (self.__class__.__name__)
145 return '<DB:%s>' % (self.__class__.__name__)
146
146
147
147
148 class RhodeCodeSetting(Base, BaseModel):
148 class RhodeCodeSetting(Base, BaseModel):
149 __tablename__ = 'rhodecode_settings'
149 __tablename__ = 'rhodecode_settings'
150 __table_args__ = (
150 __table_args__ = (
151 UniqueConstraint('app_settings_name'),
151 UniqueConstraint('app_settings_name'),
152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
152 {'extend_existing': True, 'mysql_engine': 'InnoDB',
153 'mysql_charset': 'utf8'}
153 'mysql_charset': 'utf8'}
154 )
154 )
155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
155 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
156 app_settings_name = Column("app_settings_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
157 _app_settings_value = Column("app_settings_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
158
158
159 def __init__(self, k='', v=''):
159 def __init__(self, k='', v=''):
160 self.app_settings_name = k
160 self.app_settings_name = k
161 self.app_settings_value = v
161 self.app_settings_value = v
162
162
163 @validates('_app_settings_value')
163 @validates('_app_settings_value')
164 def validate_settings_value(self, key, val):
164 def validate_settings_value(self, key, val):
165 assert type(val) == unicode
165 assert type(val) == unicode
166 return val
166 return val
167
167
168 @hybrid_property
168 @hybrid_property
169 def app_settings_value(self):
169 def app_settings_value(self):
170 v = self._app_settings_value
170 v = self._app_settings_value
171 if self.app_settings_name in ["ldap_active",
171 if self.app_settings_name in ["ldap_active",
172 "default_repo_enable_statistics",
172 "default_repo_enable_statistics",
173 "default_repo_enable_locking",
173 "default_repo_enable_locking",
174 "default_repo_private",
174 "default_repo_private",
175 "default_repo_enable_downloads"]:
175 "default_repo_enable_downloads"]:
176 v = str2bool(v)
176 v = str2bool(v)
177 return v
177 return v
178
178
179 @app_settings_value.setter
179 @app_settings_value.setter
180 def app_settings_value(self, val):
180 def app_settings_value(self, val):
181 """
181 """
182 Setter that will always make sure we use unicode in app_settings_value
182 Setter that will always make sure we use unicode in app_settings_value
183
183
184 :param val:
184 :param val:
185 """
185 """
186 self._app_settings_value = safe_unicode(val)
186 self._app_settings_value = safe_unicode(val)
187
187
188 def __unicode__(self):
188 def __unicode__(self):
189 return u"<%s('%s:%s')>" % (
189 return u"<%s('%s:%s')>" % (
190 self.__class__.__name__,
190 self.__class__.__name__,
191 self.app_settings_name, self.app_settings_value
191 self.app_settings_name, self.app_settings_value
192 )
192 )
193
193
194 @classmethod
194 @classmethod
195 def get_by_name(cls, key):
195 def get_by_name(cls, key):
196 return cls.query()\
196 return cls.query()\
197 .filter(cls.app_settings_name == key).scalar()
197 .filter(cls.app_settings_name == key).scalar()
198
198
199 @classmethod
199 @classmethod
200 def get_by_name_or_create(cls, key):
200 def get_by_name_or_create(cls, key):
201 res = cls.get_by_name(key)
201 res = cls.get_by_name(key)
202 if not res:
202 if not res:
203 res = cls(key)
203 res = cls(key)
204 return res
204 return res
205
205
206 @classmethod
206 @classmethod
207 def get_app_settings(cls, cache=False):
207 def get_app_settings(cls, cache=False):
208
208
209 ret = cls.query()
209 ret = cls.query()
210
210
211 if cache:
211 if cache:
212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
212 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
213
213
214 if not ret:
214 if not ret:
215 raise Exception('Could not get application settings !')
215 raise Exception('Could not get application settings !')
216 settings = {}
216 settings = {}
217 for each in ret:
217 for each in ret:
218 settings['rhodecode_' + each.app_settings_name] = \
218 settings['rhodecode_' + each.app_settings_name] = \
219 each.app_settings_value
219 each.app_settings_value
220
220
221 return settings
221 return settings
222
222
223 @classmethod
223 @classmethod
224 def get_ldap_settings(cls, cache=False):
224 def get_ldap_settings(cls, cache=False):
225 ret = cls.query()\
225 ret = cls.query()\
226 .filter(cls.app_settings_name.startswith('ldap_')).all()
226 .filter(cls.app_settings_name.startswith('ldap_')).all()
227 fd = {}
227 fd = {}
228 for row in ret:
228 for row in ret:
229 fd.update({row.app_settings_name: row.app_settings_value})
229 fd.update({row.app_settings_name: row.app_settings_value})
230
230
231 return fd
231 return fd
232
232
233 @classmethod
233 @classmethod
234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
234 def get_default_repo_settings(cls, cache=False, strip_prefix=False):
235 ret = cls.query()\
235 ret = cls.query()\
236 .filter(cls.app_settings_name.startswith('default_')).all()
236 .filter(cls.app_settings_name.startswith('default_')).all()
237 fd = {}
237 fd = {}
238 for row in ret:
238 for row in ret:
239 key = row.app_settings_name
239 key = row.app_settings_name
240 if strip_prefix:
240 if strip_prefix:
241 key = remove_prefix(key, prefix='default_')
241 key = remove_prefix(key, prefix='default_')
242 fd.update({key: row.app_settings_value})
242 fd.update({key: row.app_settings_value})
243
243
244 return fd
244 return fd
245
245
246
246
247 class RhodeCodeUi(Base, BaseModel):
247 class RhodeCodeUi(Base, BaseModel):
248 __tablename__ = 'rhodecode_ui'
248 __tablename__ = 'rhodecode_ui'
249 __table_args__ = (
249 __table_args__ = (
250 UniqueConstraint('ui_key'),
250 UniqueConstraint('ui_key'),
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
251 {'extend_existing': True, 'mysql_engine': 'InnoDB',
252 'mysql_charset': 'utf8'}
252 'mysql_charset': 'utf8'}
253 )
253 )
254
254
255 HOOK_UPDATE = 'changegroup.update'
255 HOOK_UPDATE = 'changegroup.update'
256 HOOK_REPO_SIZE = 'changegroup.repo_size'
256 HOOK_REPO_SIZE = 'changegroup.repo_size'
257 HOOK_PUSH = 'changegroup.push_logger'
257 HOOK_PUSH = 'changegroup.push_logger'
258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
258 HOOK_PRE_PUSH = 'prechangegroup.pre_push'
259 HOOK_PULL = 'outgoing.pull_logger'
259 HOOK_PULL = 'outgoing.pull_logger'
260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
260 HOOK_PRE_PULL = 'preoutgoing.pre_pull'
261
261
262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
262 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
263 ui_section = Column("ui_section", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
264 ui_key = Column("ui_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
265 ui_value = Column("ui_value", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
266 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
267
267
268 @classmethod
268 @classmethod
269 def get_by_key(cls, key):
269 def get_by_key(cls, key):
270 return cls.query().filter(cls.ui_key == key).scalar()
270 return cls.query().filter(cls.ui_key == key).scalar()
271
271
272 @classmethod
272 @classmethod
273 def get_builtin_hooks(cls):
273 def get_builtin_hooks(cls):
274 q = cls.query()
274 q = cls.query()
275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
275 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
276 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
277 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
278 return q.all()
278 return q.all()
279
279
280 @classmethod
280 @classmethod
281 def get_custom_hooks(cls):
281 def get_custom_hooks(cls):
282 q = cls.query()
282 q = cls.query()
283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
283 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE, cls.HOOK_REPO_SIZE,
284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
284 cls.HOOK_PUSH, cls.HOOK_PRE_PUSH,
285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
285 cls.HOOK_PULL, cls.HOOK_PRE_PULL]))
286 q = q.filter(cls.ui_section == 'hooks')
286 q = q.filter(cls.ui_section == 'hooks')
287 return q.all()
287 return q.all()
288
288
289 @classmethod
289 @classmethod
290 def get_repos_location(cls):
290 def get_repos_location(cls):
291 return cls.get_by_key('/').ui_value
291 return cls.get_by_key('/').ui_value
292
292
293 @classmethod
293 @classmethod
294 def create_or_update_hook(cls, key, val):
294 def create_or_update_hook(cls, key, val):
295 new_ui = cls.get_by_key(key) or cls()
295 new_ui = cls.get_by_key(key) or cls()
296 new_ui.ui_section = 'hooks'
296 new_ui.ui_section = 'hooks'
297 new_ui.ui_active = True
297 new_ui.ui_active = True
298 new_ui.ui_key = key
298 new_ui.ui_key = key
299 new_ui.ui_value = val
299 new_ui.ui_value = val
300
300
301 Session().add(new_ui)
301 Session().add(new_ui)
302
302
303 def __repr__(self):
303 def __repr__(self):
304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
304 return '<DB:%s[%s:%s]>' % (self.__class__.__name__, self.ui_key,
305 self.ui_value)
305 self.ui_value)
306
306
307
307
308 class User(Base, BaseModel):
308 class User(Base, BaseModel):
309 __tablename__ = 'users'
309 __tablename__ = 'users'
310 __table_args__ = (
310 __table_args__ = (
311 UniqueConstraint('username'), UniqueConstraint('email'),
311 UniqueConstraint('username'), UniqueConstraint('email'),
312 Index('u_username_idx', 'username'),
312 Index('u_username_idx', 'username'),
313 Index('u_email_idx', 'email'),
313 Index('u_email_idx', 'email'),
314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
314 {'extend_existing': True, 'mysql_engine': 'InnoDB',
315 'mysql_charset': 'utf8'}
315 'mysql_charset': 'utf8'}
316 )
316 )
317 DEFAULT_USER = 'default'
317 DEFAULT_USER = 'default'
318 DEFAULT_PERMISSIONS = [
318 DEFAULT_PERMISSIONS = [
319 'hg.register.manual_activate', 'hg.create.repository',
319 'hg.register.manual_activate', 'hg.create.repository',
320 'hg.fork.repository', 'repository.read', 'group.read'
320 'hg.fork.repository', 'repository.read', 'group.read'
321 ]
321 ]
322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
322 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
323 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
324 password = Column("password", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
325 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
326 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
327 name = Column("firstname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
328 lastname = Column("lastname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
329 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
330 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 ldap_dn = Column("ldap_dn", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 api_key = Column("api_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
333 inherit_default_permissions = Column("inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
334
334
335 user_log = relationship('UserLog')
335 user_log = relationship('UserLog')
336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
336 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
337
337
338 repositories = relationship('Repository')
338 repositories = relationship('Repository')
339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
339 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
340 followings = relationship('UserFollowing', primaryjoin='UserFollowing.user_id==User.user_id', cascade='all')
341
341
342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
342 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
343 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
344
344
345 group_member = relationship('UserGroupMember', cascade='all')
345 group_member = relationship('UserGroupMember', cascade='all')
346
346
347 notifications = relationship('UserNotification', cascade='all')
347 notifications = relationship('UserNotification', cascade='all')
348 # notifications assigned to this user
348 # notifications assigned to this user
349 user_created_notifications = relationship('Notification', cascade='all')
349 user_created_notifications = relationship('Notification', cascade='all')
350 # comments created by this user
350 # comments created by this user
351 user_comments = relationship('ChangesetComment', cascade='all')
351 user_comments = relationship('ChangesetComment', cascade='all')
352 #extra emails for this user
352 #extra emails for this user
353 user_emails = relationship('UserEmailMap', cascade='all')
353 user_emails = relationship('UserEmailMap', cascade='all')
354
354
355 @hybrid_property
355 @hybrid_property
356 def email(self):
356 def email(self):
357 return self._email
357 return self._email
358
358
359 @email.setter
359 @email.setter
360 def email(self, val):
360 def email(self, val):
361 self._email = val.lower() if val else None
361 self._email = val.lower() if val else None
362
362
363 @property
363 @property
364 def firstname(self):
364 def firstname(self):
365 # alias for future
365 # alias for future
366 return self.name
366 return self.name
367
367
368 @property
368 @property
369 def emails(self):
369 def emails(self):
370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
370 other = UserEmailMap.query().filter(UserEmailMap.user==self).all()
371 return [self.email] + [x.email for x in other]
371 return [self.email] + [x.email for x in other]
372
372
373 @property
373 @property
374 def ip_addresses(self):
374 def ip_addresses(self):
375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
375 ret = UserIpMap.query().filter(UserIpMap.user == self).all()
376 return [x.ip_addr for x in ret]
376 return [x.ip_addr for x in ret]
377
377
378 @property
378 @property
379 def username_and_name(self):
379 def username_and_name(self):
380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
380 return '%s (%s %s)' % (self.username, self.firstname, self.lastname)
381
381
382 @property
382 @property
383 def full_name(self):
383 def full_name(self):
384 return '%s %s' % (self.firstname, self.lastname)
384 return '%s %s' % (self.firstname, self.lastname)
385
385
386 @property
386 @property
387 def full_name_or_username(self):
387 def full_name_or_username(self):
388 return ('%s %s' % (self.firstname, self.lastname)
388 return ('%s %s' % (self.firstname, self.lastname)
389 if (self.firstname and self.lastname) else self.username)
389 if (self.firstname and self.lastname) else self.username)
390
390
391 @property
391 @property
392 def full_contact(self):
392 def full_contact(self):
393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
393 return '%s %s <%s>' % (self.firstname, self.lastname, self.email)
394
394
395 @property
395 @property
396 def short_contact(self):
396 def short_contact(self):
397 return '%s %s' % (self.firstname, self.lastname)
397 return '%s %s' % (self.firstname, self.lastname)
398
398
399 @property
399 @property
400 def is_admin(self):
400 def is_admin(self):
401 return self.admin
401 return self.admin
402
402
403 @property
403 @property
404 def AuthUser(self):
404 def AuthUser(self):
405 """
405 """
406 Returns instance of AuthUser for this user
406 Returns instance of AuthUser for this user
407 """
407 """
408 from rhodecode.lib.auth import AuthUser
408 from rhodecode.lib.auth import AuthUser
409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
409 return AuthUser(user_id=self.user_id, api_key=self.api_key,
410 username=self.username)
410 username=self.username)
411
411
412 def __unicode__(self):
412 def __unicode__(self):
413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
413 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
414 self.user_id, self.username)
414 self.user_id, self.username)
415
415
416 @classmethod
416 @classmethod
417 def get_by_username(cls, username, case_insensitive=False, cache=False):
417 def get_by_username(cls, username, case_insensitive=False, cache=False):
418 if case_insensitive:
418 if case_insensitive:
419 q = cls.query().filter(cls.username.ilike(username))
419 q = cls.query().filter(cls.username.ilike(username))
420 else:
420 else:
421 q = cls.query().filter(cls.username == username)
421 q = cls.query().filter(cls.username == username)
422
422
423 if cache:
423 if cache:
424 q = q.options(FromCache(
424 q = q.options(FromCache(
425 "sql_cache_short",
425 "sql_cache_short",
426 "get_user_%s" % _hash_key(username)
426 "get_user_%s" % _hash_key(username)
427 )
427 )
428 )
428 )
429 return q.scalar()
429 return q.scalar()
430
430
431 @classmethod
431 @classmethod
432 def get_by_api_key(cls, api_key, cache=False):
432 def get_by_api_key(cls, api_key, cache=False):
433 q = cls.query().filter(cls.api_key == api_key)
433 q = cls.query().filter(cls.api_key == api_key)
434
434
435 if cache:
435 if cache:
436 q = q.options(FromCache("sql_cache_short",
436 q = q.options(FromCache("sql_cache_short",
437 "get_api_key_%s" % api_key))
437 "get_api_key_%s" % api_key))
438 return q.scalar()
438 return q.scalar()
439
439
440 @classmethod
440 @classmethod
441 def get_by_email(cls, email, case_insensitive=False, cache=False):
441 def get_by_email(cls, email, case_insensitive=False, cache=False):
442 if case_insensitive:
442 if case_insensitive:
443 q = cls.query().filter(cls.email.ilike(email))
443 q = cls.query().filter(cls.email.ilike(email))
444 else:
444 else:
445 q = cls.query().filter(cls.email == email)
445 q = cls.query().filter(cls.email == email)
446
446
447 if cache:
447 if cache:
448 q = q.options(FromCache("sql_cache_short",
448 q = q.options(FromCache("sql_cache_short",
449 "get_email_key_%s" % email))
449 "get_email_key_%s" % email))
450
450
451 ret = q.scalar()
451 ret = q.scalar()
452 if ret is None:
452 if ret is None:
453 q = UserEmailMap.query()
453 q = UserEmailMap.query()
454 # try fetching in alternate email map
454 # try fetching in alternate email map
455 if case_insensitive:
455 if case_insensitive:
456 q = q.filter(UserEmailMap.email.ilike(email))
456 q = q.filter(UserEmailMap.email.ilike(email))
457 else:
457 else:
458 q = q.filter(UserEmailMap.email == email)
458 q = q.filter(UserEmailMap.email == email)
459 q = q.options(joinedload(UserEmailMap.user))
459 q = q.options(joinedload(UserEmailMap.user))
460 if cache:
460 if cache:
461 q = q.options(FromCache("sql_cache_short",
461 q = q.options(FromCache("sql_cache_short",
462 "get_email_map_key_%s" % email))
462 "get_email_map_key_%s" % email))
463 ret = getattr(q.scalar(), 'user', None)
463 ret = getattr(q.scalar(), 'user', None)
464
464
465 return ret
465 return ret
466
466
467 @classmethod
467 @classmethod
468 def get_from_cs_author(cls, author):
468 def get_from_cs_author(cls, author):
469 """
469 """
470 Tries to get User objects out of commit author string
470 Tries to get User objects out of commit author string
471
471
472 :param author:
472 :param author:
473 """
473 """
474 from rhodecode.lib.helpers import email, author_name
474 from rhodecode.lib.helpers import email, author_name
475 # Valid email in the attribute passed, see if they're in the system
475 # Valid email in the attribute passed, see if they're in the system
476 _email = email(author)
476 _email = email(author)
477 if _email:
477 if _email:
478 user = cls.get_by_email(_email, case_insensitive=True)
478 user = cls.get_by_email(_email, case_insensitive=True)
479 if user:
479 if user:
480 return user
480 return user
481 # Maybe we can match by username?
481 # Maybe we can match by username?
482 _author = author_name(author)
482 _author = author_name(author)
483 user = cls.get_by_username(_author, case_insensitive=True)
483 user = cls.get_by_username(_author, case_insensitive=True)
484 if user:
484 if user:
485 return user
485 return user
486
486
487 def update_lastlogin(self):
487 def update_lastlogin(self):
488 """Update user lastlogin"""
488 """Update user lastlogin"""
489 self.last_login = datetime.datetime.now()
489 self.last_login = datetime.datetime.now()
490 Session().add(self)
490 Session().add(self)
491 log.debug('updated user %s lastlogin' % self.username)
491 log.debug('updated user %s lastlogin' % self.username)
492
492
493 def get_api_data(self):
493 def get_api_data(self):
494 """
494 """
495 Common function for generating user related data for API
495 Common function for generating user related data for API
496 """
496 """
497 user = self
497 user = self
498 data = dict(
498 data = dict(
499 user_id=user.user_id,
499 user_id=user.user_id,
500 username=user.username,
500 username=user.username,
501 firstname=user.name,
501 firstname=user.name,
502 lastname=user.lastname,
502 lastname=user.lastname,
503 email=user.email,
503 email=user.email,
504 emails=user.emails,
504 emails=user.emails,
505 api_key=user.api_key,
505 api_key=user.api_key,
506 active=user.active,
506 active=user.active,
507 admin=user.admin,
507 admin=user.admin,
508 ldap_dn=user.ldap_dn,
508 ldap_dn=user.ldap_dn,
509 last_login=user.last_login,
509 last_login=user.last_login,
510 ip_addresses=user.ip_addresses
510 ip_addresses=user.ip_addresses
511 )
511 )
512 return data
512 return data
513
513
514 def __json__(self):
514 def __json__(self):
515 data = dict(
515 data = dict(
516 full_name=self.full_name,
516 full_name=self.full_name,
517 full_name_or_username=self.full_name_or_username,
517 full_name_or_username=self.full_name_or_username,
518 short_contact=self.short_contact,
518 short_contact=self.short_contact,
519 full_contact=self.full_contact
519 full_contact=self.full_contact
520 )
520 )
521 data.update(self.get_api_data())
521 data.update(self.get_api_data())
522 return data
522 return data
523
523
524
524
525 class UserEmailMap(Base, BaseModel):
525 class UserEmailMap(Base, BaseModel):
526 __tablename__ = 'user_email_map'
526 __tablename__ = 'user_email_map'
527 __table_args__ = (
527 __table_args__ = (
528 Index('uem_email_idx', 'email'),
528 Index('uem_email_idx', 'email'),
529 UniqueConstraint('email'),
529 UniqueConstraint('email'),
530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
530 {'extend_existing': True, 'mysql_engine': 'InnoDB',
531 'mysql_charset': 'utf8'}
531 'mysql_charset': 'utf8'}
532 )
532 )
533 __mapper_args__ = {}
533 __mapper_args__ = {}
534
534
535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
535 email_id = Column("email_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
536 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
537 _email = Column("email", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
538 user = relationship('User', lazy='joined')
538 user = relationship('User', lazy='joined')
539
539
540 @validates('_email')
540 @validates('_email')
541 def validate_email(self, key, email):
541 def validate_email(self, key, email):
542 # check if this email is not main one
542 # check if this email is not main one
543 main_email = Session().query(User).filter(User.email == email).scalar()
543 main_email = Session().query(User).filter(User.email == email).scalar()
544 if main_email is not None:
544 if main_email is not None:
545 raise AttributeError('email %s is present is user table' % email)
545 raise AttributeError('email %s is present is user table' % email)
546 return email
546 return email
547
547
548 @hybrid_property
548 @hybrid_property
549 def email(self):
549 def email(self):
550 return self._email
550 return self._email
551
551
552 @email.setter
552 @email.setter
553 def email(self, val):
553 def email(self, val):
554 self._email = val.lower() if val else None
554 self._email = val.lower() if val else None
555
555
556
556
557 class UserIpMap(Base, BaseModel):
557 class UserIpMap(Base, BaseModel):
558 __tablename__ = 'user_ip_map'
558 __tablename__ = 'user_ip_map'
559 __table_args__ = (
559 __table_args__ = (
560 UniqueConstraint('user_id', 'ip_addr'),
560 UniqueConstraint('user_id', 'ip_addr'),
561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
561 {'extend_existing': True, 'mysql_engine': 'InnoDB',
562 'mysql_charset': 'utf8'}
562 'mysql_charset': 'utf8'}
563 )
563 )
564 __mapper_args__ = {}
564 __mapper_args__ = {}
565
565
566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
566 ip_id = Column("ip_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
567 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
568 ip_addr = Column("ip_addr", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
569 active = Column("active", Boolean(), nullable=True, unique=None, default=True)
570 user = relationship('User', lazy='joined')
570 user = relationship('User', lazy='joined')
571
571
572 @classmethod
572 @classmethod
573 def _get_ip_range(cls, ip_addr):
573 def _get_ip_range(cls, ip_addr):
574 from rhodecode.lib import ipaddr
574 from rhodecode.lib import ipaddr
575 net = ipaddr.IPNetwork(address=ip_addr)
575 net = ipaddr.IPNetwork(address=ip_addr)
576 return [str(net.network), str(net.broadcast)]
576 return [str(net.network), str(net.broadcast)]
577
577
578 def __json__(self):
578 def __json__(self):
579 return dict(
579 return dict(
580 ip_addr=self.ip_addr,
580 ip_addr=self.ip_addr,
581 ip_range=self._get_ip_range(self.ip_addr)
581 ip_range=self._get_ip_range(self.ip_addr)
582 )
582 )
583
583
584
584
585 class UserLog(Base, BaseModel):
585 class UserLog(Base, BaseModel):
586 __tablename__ = 'user_logs'
586 __tablename__ = 'user_logs'
587 __table_args__ = (
587 __table_args__ = (
588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
589 'mysql_charset': 'utf8'},
589 'mysql_charset': 'utf8'},
590 )
590 )
591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
591 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
592 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
593 username = Column("username", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
594 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
595 repository_name = Column("repository_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
596 user_ip = Column("user_ip", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
597 action = Column("action", UnicodeText(1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
598 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
599
599
600 @property
600 @property
601 def action_as_day(self):
601 def action_as_day(self):
602 return datetime.date(*self.action_date.timetuple()[:3])
602 return datetime.date(*self.action_date.timetuple()[:3])
603
603
604 user = relationship('User')
604 user = relationship('User')
605 repository = relationship('Repository', cascade='')
605 repository = relationship('Repository', cascade='')
606
606
607
607
608 class UserGroup(Base, BaseModel):
608 class UserGroup(Base, BaseModel):
609 __tablename__ = 'users_groups'
609 __tablename__ = 'users_groups'
610 __table_args__ = (
610 __table_args__ = (
611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
611 {'extend_existing': True, 'mysql_engine': 'InnoDB',
612 'mysql_charset': 'utf8'},
612 'mysql_charset': 'utf8'},
613 )
613 )
614
614
615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
615 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
616 users_group_name = Column("users_group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
617 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
618 inherit_default_permissions = Column("users_group_inherit_default_permissions", Boolean(), nullable=False, unique=None, default=True)
619
619
620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
620 members = relationship('UserGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
621 users_group_to_perm = relationship('UserGroupToPerm', cascade='all')
622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
622 users_group_repo_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
623
623
624 def __unicode__(self):
624 def __unicode__(self):
625 return u'<userGroup(%s)>' % (self.users_group_name)
625 return u'<userGroup(%s)>' % (self.users_group_name)
626
626
627 @classmethod
627 @classmethod
628 def get_by_group_name(cls, group_name, cache=False,
628 def get_by_group_name(cls, group_name, cache=False,
629 case_insensitive=False):
629 case_insensitive=False):
630 if case_insensitive:
630 if case_insensitive:
631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
631 q = cls.query().filter(cls.users_group_name.ilike(group_name))
632 else:
632 else:
633 q = cls.query().filter(cls.users_group_name == group_name)
633 q = cls.query().filter(cls.users_group_name == group_name)
634 if cache:
634 if cache:
635 q = q.options(FromCache(
635 q = q.options(FromCache(
636 "sql_cache_short",
636 "sql_cache_short",
637 "get_user_%s" % _hash_key(group_name)
637 "get_user_%s" % _hash_key(group_name)
638 )
638 )
639 )
639 )
640 return q.scalar()
640 return q.scalar()
641
641
642 @classmethod
642 @classmethod
643 def get(cls, users_group_id, cache=False):
643 def get(cls, users_group_id, cache=False):
644 users_group = cls.query()
644 users_group = cls.query()
645 if cache:
645 if cache:
646 users_group = users_group.options(FromCache("sql_cache_short",
646 users_group = users_group.options(FromCache("sql_cache_short",
647 "get_users_group_%s" % users_group_id))
647 "get_users_group_%s" % users_group_id))
648 return users_group.get(users_group_id)
648 return users_group.get(users_group_id)
649
649
650 def get_api_data(self):
650 def get_api_data(self):
651 users_group = self
651 users_group = self
652
652
653 data = dict(
653 data = dict(
654 users_group_id=users_group.users_group_id,
654 users_group_id=users_group.users_group_id,
655 group_name=users_group.users_group_name,
655 group_name=users_group.users_group_name,
656 active=users_group.users_group_active,
656 active=users_group.users_group_active,
657 )
657 )
658
658
659 return data
659 return data
660
660
661
661
662 class UserGroupMember(Base, BaseModel):
662 class UserGroupMember(Base, BaseModel):
663 __tablename__ = 'users_groups_members'
663 __tablename__ = 'users_groups_members'
664 __table_args__ = (
664 __table_args__ = (
665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
665 {'extend_existing': True, 'mysql_engine': 'InnoDB',
666 'mysql_charset': 'utf8'},
666 'mysql_charset': 'utf8'},
667 )
667 )
668
668
669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
669 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
670 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
671 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
672
672
673 user = relationship('User', lazy='joined')
673 user = relationship('User', lazy='joined')
674 users_group = relationship('UserGroup')
674 users_group = relationship('UserGroup')
675
675
676 def __init__(self, gr_id='', u_id=''):
676 def __init__(self, gr_id='', u_id=''):
677 self.users_group_id = gr_id
677 self.users_group_id = gr_id
678 self.user_id = u_id
678 self.user_id = u_id
679
679
680
680
681 class RepositoryField(Base, BaseModel):
681 class RepositoryField(Base, BaseModel):
682 __tablename__ = 'repositories_fields'
682 __tablename__ = 'repositories_fields'
683 __table_args__ = (
683 __table_args__ = (
684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
684 UniqueConstraint('repository_id', 'field_key'), # no-multi field
685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
685 {'extend_existing': True, 'mysql_engine': 'InnoDB',
686 'mysql_charset': 'utf8'},
686 'mysql_charset': 'utf8'},
687 )
687 )
688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
688 PREFIX = 'ex_' # prefix used in form to not conflict with already existing fields
689
689
690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
690 repo_field_id = Column("repo_field_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
691 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
692 field_key = Column("field_key", String(250, convert_unicode=False, assert_unicode=None))
693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
693 field_label = Column("field_label", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
694 field_value = Column("field_value", String(10000, convert_unicode=False, assert_unicode=None), nullable=False)
695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
695 field_desc = Column("field_desc", String(1024, convert_unicode=False, assert_unicode=None), nullable=False)
696 field_type = Column("field_type", String(256), nullable=False, unique=None)
696 field_type = Column("field_type", String(256), nullable=False, unique=None)
697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
697 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
698
698
699 repository = relationship('Repository')
699 repository = relationship('Repository')
700
700
701 @property
701 @property
702 def field_key_prefixed(self):
702 def field_key_prefixed(self):
703 return 'ex_%s' % self.field_key
703 return 'ex_%s' % self.field_key
704
704
705 @classmethod
705 @classmethod
706 def un_prefix_key(cls, key):
706 def un_prefix_key(cls, key):
707 if key.startswith(cls.PREFIX):
707 if key.startswith(cls.PREFIX):
708 return key[len(cls.PREFIX):]
708 return key[len(cls.PREFIX):]
709 return key
709 return key
710
710
711 @classmethod
711 @classmethod
712 def get_by_key_name(cls, key, repo):
712 def get_by_key_name(cls, key, repo):
713 row = cls.query()\
713 row = cls.query()\
714 .filter(cls.repository == repo)\
714 .filter(cls.repository == repo)\
715 .filter(cls.field_key == key).scalar()
715 .filter(cls.field_key == key).scalar()
716 return row
716 return row
717
717
718
718
719 class Repository(Base, BaseModel):
719 class Repository(Base, BaseModel):
720 __tablename__ = 'repositories'
720 __tablename__ = 'repositories'
721 __table_args__ = (
721 __table_args__ = (
722 UniqueConstraint('repo_name'),
722 UniqueConstraint('repo_name'),
723 Index('r_repo_name_idx', 'repo_name'),
723 Index('r_repo_name_idx', 'repo_name'),
724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
724 {'extend_existing': True, 'mysql_engine': 'InnoDB',
725 'mysql_charset': 'utf8'},
725 'mysql_charset': 'utf8'},
726 )
726 )
727
727
728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
728 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
729 repo_name = Column("repo_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
730 clone_uri = Column("clone_uri", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
731 repo_type = Column("repo_type", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
732 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
733 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
734 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
735 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
736 description = Column("description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
737 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
738 updated_on = Column('updated_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
739 landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
740 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
741 _locked = Column("locked", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
742 _changeset_cache = Column("changeset_cache", LargeBinary(), nullable=True) #JSON data
743
743
744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
744 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
745 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
746
746
747 user = relationship('User')
747 user = relationship('User')
748 fork = relationship('Repository', remote_side=repo_id)
748 fork = relationship('Repository', remote_side=repo_id)
749 group = relationship('RepoGroup')
749 group = relationship('RepoGroup')
750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
750 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
751 users_group_to_perm = relationship('UserGroupRepoToPerm', cascade='all')
752 stats = relationship('Statistics', cascade='all', uselist=False)
752 stats = relationship('Statistics', cascade='all', uselist=False)
753
753
754 followers = relationship('UserFollowing',
754 followers = relationship('UserFollowing',
755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
755 primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
756 cascade='all')
756 cascade='all')
757 extra_fields = relationship('RepositoryField',
757 extra_fields = relationship('RepositoryField',
758 cascade="all, delete, delete-orphan")
758 cascade="all, delete, delete-orphan")
759
759
760 logs = relationship('UserLog')
760 logs = relationship('UserLog')
761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
761 comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
762
762
763 pull_requests_org = relationship('PullRequest',
763 pull_requests_org = relationship('PullRequest',
764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
764 primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
765 cascade="all, delete, delete-orphan")
765 cascade="all, delete, delete-orphan")
766
766
767 pull_requests_other = relationship('PullRequest',
767 pull_requests_other = relationship('PullRequest',
768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
768 primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
769 cascade="all, delete, delete-orphan")
769 cascade="all, delete, delete-orphan")
770
770
771 def __unicode__(self):
771 def __unicode__(self):
772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
772 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.repo_id,
773 self.repo_name)
773 self.repo_name)
774
774
775 @hybrid_property
775 @hybrid_property
776 def locked(self):
776 def locked(self):
777 # always should return [user_id, timelocked]
777 # always should return [user_id, timelocked]
778 if self._locked:
778 if self._locked:
779 _lock_info = self._locked.split(':')
779 _lock_info = self._locked.split(':')
780 return int(_lock_info[0]), _lock_info[1]
780 return int(_lock_info[0]), _lock_info[1]
781 return [None, None]
781 return [None, None]
782
782
783 @locked.setter
783 @locked.setter
784 def locked(self, val):
784 def locked(self, val):
785 if val and isinstance(val, (list, tuple)):
785 if val and isinstance(val, (list, tuple)):
786 self._locked = ':'.join(map(str, val))
786 self._locked = ':'.join(map(str, val))
787 else:
787 else:
788 self._locked = None
788 self._locked = None
789
789
790 @hybrid_property
790 @hybrid_property
791 def changeset_cache(self):
791 def changeset_cache(self):
792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
792 from rhodecode.lib.vcs.backends.base import EmptyChangeset
793 dummy = EmptyChangeset().__json__()
793 dummy = EmptyChangeset().__json__()
794 if not self._changeset_cache:
794 if not self._changeset_cache:
795 return dummy
795 return dummy
796 try:
796 try:
797 return json.loads(self._changeset_cache)
797 return json.loads(self._changeset_cache)
798 except TypeError:
798 except TypeError:
799 return dummy
799 return dummy
800
800
801 @changeset_cache.setter
801 @changeset_cache.setter
802 def changeset_cache(self, val):
802 def changeset_cache(self, val):
803 try:
803 try:
804 self._changeset_cache = json.dumps(val)
804 self._changeset_cache = json.dumps(val)
805 except:
805 except:
806 log.error(traceback.format_exc())
806 log.error(traceback.format_exc())
807
807
808 @classmethod
808 @classmethod
809 def url_sep(cls):
809 def url_sep(cls):
810 return URL_SEP
810 return URL_SEP
811
811
812 @classmethod
812 @classmethod
813 def normalize_repo_name(cls, repo_name):
813 def normalize_repo_name(cls, repo_name):
814 """
814 """
815 Normalizes os specific repo_name to the format internally stored inside
815 Normalizes os specific repo_name to the format internally stored inside
816 dabatabase using URL_SEP
816 dabatabase using URL_SEP
817
817
818 :param cls:
818 :param cls:
819 :param repo_name:
819 :param repo_name:
820 """
820 """
821 return cls.url_sep().join(repo_name.split(os.sep))
821 return cls.url_sep().join(repo_name.split(os.sep))
822
822
823 @classmethod
823 @classmethod
824 def get_by_repo_name(cls, repo_name):
824 def get_by_repo_name(cls, repo_name):
825 q = Session().query(cls).filter(cls.repo_name == repo_name)
825 q = Session().query(cls).filter(cls.repo_name == repo_name)
826 q = q.options(joinedload(Repository.fork))\
826 q = q.options(joinedload(Repository.fork))\
827 .options(joinedload(Repository.user))\
827 .options(joinedload(Repository.user))\
828 .options(joinedload(Repository.group))
828 .options(joinedload(Repository.group))
829 return q.scalar()
829 return q.scalar()
830
830
831 @classmethod
831 @classmethod
832 def get_by_full_path(cls, repo_full_path):
832 def get_by_full_path(cls, repo_full_path):
833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
833 repo_name = repo_full_path.split(cls.base_path(), 1)[-1]
834 repo_name = cls.normalize_repo_name(repo_name)
834 repo_name = cls.normalize_repo_name(repo_name)
835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
835 return cls.get_by_repo_name(repo_name.strip(URL_SEP))
836
836
837 @classmethod
837 @classmethod
838 def get_repo_forks(cls, repo_id):
838 def get_repo_forks(cls, repo_id):
839 return cls.query().filter(Repository.fork_id == repo_id)
839 return cls.query().filter(Repository.fork_id == repo_id)
840
840
841 @classmethod
841 @classmethod
842 def base_path(cls):
842 def base_path(cls):
843 """
843 """
844 Returns base path when all repos are stored
844 Returns base path when all repos are stored
845
845
846 :param cls:
846 :param cls:
847 """
847 """
848 q = Session().query(RhodeCodeUi)\
848 q = Session().query(RhodeCodeUi)\
849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
849 .filter(RhodeCodeUi.ui_key == cls.url_sep())
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
850 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
851 return q.one().ui_value
851 return q.one().ui_value
852
852
853 @property
853 @property
854 def forks(self):
854 def forks(self):
855 """
855 """
856 Return forks of this repo
856 Return forks of this repo
857 """
857 """
858 return Repository.get_repo_forks(self.repo_id)
858 return Repository.get_repo_forks(self.repo_id)
859
859
860 @property
860 @property
861 def parent(self):
861 def parent(self):
862 """
862 """
863 Returns fork parent
863 Returns fork parent
864 """
864 """
865 return self.fork
865 return self.fork
866
866
867 @property
867 @property
868 def just_name(self):
868 def just_name(self):
869 return self.repo_name.split(Repository.url_sep())[-1]
869 return self.repo_name.split(Repository.url_sep())[-1]
870
870
871 @property
871 @property
872 def groups_with_parents(self):
872 def groups_with_parents(self):
873 groups = []
873 groups = []
874 if self.group is None:
874 if self.group is None:
875 return groups
875 return groups
876
876
877 cur_gr = self.group
877 cur_gr = self.group
878 groups.insert(0, cur_gr)
878 groups.insert(0, cur_gr)
879 while 1:
879 while 1:
880 gr = getattr(cur_gr, 'parent_group', None)
880 gr = getattr(cur_gr, 'parent_group', None)
881 cur_gr = cur_gr.parent_group
881 cur_gr = cur_gr.parent_group
882 if gr is None:
882 if gr is None:
883 break
883 break
884 groups.insert(0, gr)
884 groups.insert(0, gr)
885
885
886 return groups
886 return groups
887
887
888 @property
888 @property
889 def groups_and_repo(self):
889 def groups_and_repo(self):
890 return self.groups_with_parents, self.just_name
890 return self.groups_with_parents, self.just_name
891
891
892 @LazyProperty
892 @LazyProperty
893 def repo_path(self):
893 def repo_path(self):
894 """
894 """
895 Returns base full path for that repository means where it actually
895 Returns base full path for that repository means where it actually
896 exists on a filesystem
896 exists on a filesystem
897 """
897 """
898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
898 q = Session().query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
899 Repository.url_sep())
899 Repository.url_sep())
900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
900 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
901 return q.one().ui_value
901 return q.one().ui_value
902
902
903 @property
903 @property
904 def repo_full_path(self):
904 def repo_full_path(self):
905 p = [self.repo_path]
905 p = [self.repo_path]
906 # we need to split the name by / since this is how we store the
906 # we need to split the name by / since this is how we store the
907 # names in the database, but that eventually needs to be converted
907 # names in the database, but that eventually needs to be converted
908 # into a valid system path
908 # into a valid system path
909 p += self.repo_name.split(Repository.url_sep())
909 p += self.repo_name.split(Repository.url_sep())
910 return os.path.join(*p)
910 return os.path.join(*p)
911
911
912 @property
912 @property
913 def cache_keys(self):
913 def cache_keys(self):
914 """
914 """
915 Returns associated cache keys for that repo
915 Returns associated cache keys for that repo
916 """
916 """
917 return CacheInvalidation.query()\
917 return CacheInvalidation.query()\
918 .filter(CacheInvalidation.cache_args == self.repo_name)\
918 .filter(CacheInvalidation.cache_args == self.repo_name)\
919 .order_by(CacheInvalidation.cache_key)\
919 .order_by(CacheInvalidation.cache_key)\
920 .all()
920 .all()
921
921
922 def get_new_name(self, repo_name):
922 def get_new_name(self, repo_name):
923 """
923 """
924 returns new full repository name based on assigned group and new new
924 returns new full repository name based on assigned group and new new
925
925
926 :param group_name:
926 :param group_name:
927 """
927 """
928 path_prefix = self.group.full_path_splitted if self.group else []
928 path_prefix = self.group.full_path_splitted if self.group else []
929 return Repository.url_sep().join(path_prefix + [repo_name])
929 return Repository.url_sep().join(path_prefix + [repo_name])
930
930
931 @property
931 @property
932 def _ui(self):
932 def _ui(self):
933 """
933 """
934 Creates an db based ui object for this repository
934 Creates an db based ui object for this repository
935 """
935 """
936 from rhodecode.lib.utils import make_ui
936 from rhodecode.lib.utils import make_ui
937 return make_ui('db', clear_session=False)
937 return make_ui('db', clear_session=False)
938
938
939 @classmethod
939 @classmethod
940 def inject_ui(cls, repo, extras={}):
940 def inject_ui(cls, repo, extras={}):
941 repo.inject_ui(extras)
941 repo.inject_ui(extras)
942
942
943 @classmethod
943 @classmethod
944 def is_valid(cls, repo_name):
944 def is_valid(cls, repo_name):
945 """
945 """
946 returns True if given repo name is a valid filesystem repository
946 returns True if given repo name is a valid filesystem repository
947
947
948 :param cls:
948 :param cls:
949 :param repo_name:
949 :param repo_name:
950 """
950 """
951 from rhodecode.lib.utils import is_valid_repo
951 from rhodecode.lib.utils import is_valid_repo
952
952
953 return is_valid_repo(repo_name, cls.base_path())
953 return is_valid_repo(repo_name, cls.base_path())
954
954
955 def get_api_data(self):
955 def get_api_data(self):
956 """
956 """
957 Common function for generating repo api data
957 Common function for generating repo api data
958
958
959 """
959 """
960 repo = self
960 repo = self
961 data = dict(
961 data = dict(
962 repo_id=repo.repo_id,
962 repo_id=repo.repo_id,
963 repo_name=repo.repo_name,
963 repo_name=repo.repo_name,
964 repo_type=repo.repo_type,
964 repo_type=repo.repo_type,
965 clone_uri=repo.clone_uri,
965 clone_uri=repo.clone_uri,
966 private=repo.private,
966 private=repo.private,
967 created_on=repo.created_on,
967 created_on=repo.created_on,
968 description=repo.description,
968 description=repo.description,
969 landing_rev=repo.landing_rev,
969 landing_rev=repo.landing_rev,
970 owner=repo.user.username,
970 owner=repo.user.username,
971 fork_of=repo.fork.repo_name if repo.fork else None,
971 fork_of=repo.fork.repo_name if repo.fork else None,
972 enable_statistics=repo.enable_statistics,
972 enable_statistics=repo.enable_statistics,
973 enable_locking=repo.enable_locking,
973 enable_locking=repo.enable_locking,
974 enable_downloads=repo.enable_downloads,
974 enable_downloads=repo.enable_downloads,
975 last_changeset=repo.changeset_cache
975 last_changeset=repo.changeset_cache
976 )
976 )
977 rc_config = RhodeCodeSetting.get_app_settings()
977 rc_config = RhodeCodeSetting.get_app_settings()
978 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
978 repository_fields = str2bool(rc_config.get('rhodecode_repository_fields'))
979 if repository_fields:
979 if repository_fields:
980 for f in self.extra_fields:
980 for f in self.extra_fields:
981 data[f.field_key_prefixed] = f.field_value
981 data[f.field_key_prefixed] = f.field_value
982
982
983 return data
983 return data
984
984
985 @classmethod
985 @classmethod
986 def lock(cls, repo, user_id):
986 def lock(cls, repo, user_id):
987 repo.locked = [user_id, time.time()]
987 repo.locked = [user_id, time.time()]
988 Session().add(repo)
988 Session().add(repo)
989 Session().commit()
989 Session().commit()
990
990
991 @classmethod
991 @classmethod
992 def unlock(cls, repo):
992 def unlock(cls, repo):
993 repo.locked = None
993 repo.locked = None
994 Session().add(repo)
994 Session().add(repo)
995 Session().commit()
995 Session().commit()
996
996
997 @classmethod
997 @classmethod
998 def getlock(cls, repo):
998 def getlock(cls, repo):
999 return repo.locked
999 return repo.locked
1000
1000
1001 @property
1001 @property
1002 def last_db_change(self):
1002 def last_db_change(self):
1003 return self.updated_on
1003 return self.updated_on
1004
1004
1005 def clone_url(self, **override):
1005 def clone_url(self, **override):
1006 from pylons import url
1006 from pylons import url
1007 from urlparse import urlparse
1007 from urlparse import urlparse
1008 import urllib
1008 import urllib
1009 parsed_url = urlparse(url('home', qualified=True))
1009 parsed_url = urlparse(url('home', qualified=True))
1010 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1010 default_clone_uri = '%(scheme)s://%(user)s%(pass)s%(netloc)s%(prefix)s%(path)s'
1011 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1011 decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
1012 args = {
1012 args = {
1013 'user': '',
1013 'user': '',
1014 'pass': '',
1014 'pass': '',
1015 'scheme': parsed_url.scheme,
1015 'scheme': parsed_url.scheme,
1016 'netloc': parsed_url.netloc,
1016 'netloc': parsed_url.netloc,
1017 'prefix': decoded_path,
1017 'prefix': decoded_path,
1018 'path': self.repo_name
1018 'path': self.repo_name
1019 }
1019 }
1020
1020
1021 args.update(override)
1021 args.update(override)
1022 return default_clone_uri % args
1022 return default_clone_uri % args
1023
1023
1024 #==========================================================================
1024 #==========================================================================
1025 # SCM PROPERTIES
1025 # SCM PROPERTIES
1026 #==========================================================================
1026 #==========================================================================
1027
1027
1028 def get_changeset(self, rev=None):
1028 def get_changeset(self, rev=None):
1029 return get_changeset_safe(self.scm_instance, rev)
1029 return get_changeset_safe(self.scm_instance, rev)
1030
1030
1031 def get_landing_changeset(self):
1031 def get_landing_changeset(self):
1032 """
1032 """
1033 Returns landing changeset, or if that doesn't exist returns the tip
1033 Returns landing changeset, or if that doesn't exist returns the tip
1034 """
1034 """
1035 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1035 cs = self.get_changeset(self.landing_rev) or self.get_changeset()
1036 return cs
1036 return cs
1037
1037
1038 def update_changeset_cache(self, cs_cache=None):
1038 def update_changeset_cache(self, cs_cache=None):
1039 """
1039 """
1040 Update cache of last changeset for repository, keys should be::
1040 Update cache of last changeset for repository, keys should be::
1041
1041
1042 short_id
1042 short_id
1043 raw_id
1043 raw_id
1044 revision
1044 revision
1045 message
1045 message
1046 date
1046 date
1047 author
1047 author
1048
1048
1049 :param cs_cache:
1049 :param cs_cache:
1050 """
1050 """
1051 from rhodecode.lib.vcs.backends.base import BaseChangeset
1051 from rhodecode.lib.vcs.backends.base import BaseChangeset
1052 if cs_cache is None:
1052 if cs_cache is None:
1053 cs_cache = EmptyChangeset()
1053 cs_cache = EmptyChangeset()
1054 # use no-cache version here
1054 # use no-cache version here
1055 scm_repo = self.scm_instance_no_cache
1055 scm_repo = self.scm_instance_no_cache
1056 if scm_repo:
1056 if scm_repo:
1057 cs_cache = scm_repo.get_changeset()
1057 cs_cache = scm_repo.get_changeset()
1058
1058
1059 if isinstance(cs_cache, BaseChangeset):
1059 if isinstance(cs_cache, BaseChangeset):
1060 cs_cache = cs_cache.__json__()
1060 cs_cache = cs_cache.__json__()
1061
1061
1062 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1062 if (cs_cache != self.changeset_cache or not self.changeset_cache):
1063 _default = datetime.datetime.fromtimestamp(0)
1063 _default = datetime.datetime.fromtimestamp(0)
1064 last_change = cs_cache.get('date') or _default
1064 last_change = cs_cache.get('date') or _default
1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1065 log.debug('updated repo %s with new cs cache %s' % (self, cs_cache))
1066 self.updated_on = last_change
1066 self.updated_on = last_change
1067 self.changeset_cache = cs_cache
1067 self.changeset_cache = cs_cache
1068 Session().add(self)
1068 Session().add(self)
1069 Session().commit()
1069 Session().commit()
1070 else:
1070 else:
1071 log.debug('Skipping repo:%s already with latest changes' % self)
1071 log.debug('Skipping repo:%s already with latest changes' % self)
1072
1072
1073 @property
1073 @property
1074 def tip(self):
1074 def tip(self):
1075 return self.get_changeset('tip')
1075 return self.get_changeset('tip')
1076
1076
1077 @property
1077 @property
1078 def author(self):
1078 def author(self):
1079 return self.tip.author
1079 return self.tip.author
1080
1080
1081 @property
1081 @property
1082 def last_change(self):
1082 def last_change(self):
1083 return self.scm_instance.last_change
1083 return self.scm_instance.last_change
1084
1084
1085 def get_comments(self, revisions=None):
1085 def get_comments(self, revisions=None):
1086 """
1086 """
1087 Returns comments for this repository grouped by revisions
1087 Returns comments for this repository grouped by revisions
1088
1088
1089 :param revisions: filter query by revisions only
1089 :param revisions: filter query by revisions only
1090 """
1090 """
1091 cmts = ChangesetComment.query()\
1091 cmts = ChangesetComment.query()\
1092 .filter(ChangesetComment.repo == self)
1092 .filter(ChangesetComment.repo == self)
1093 if revisions:
1093 if revisions:
1094 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1094 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
1095 grouped = defaultdict(list)
1095 grouped = defaultdict(list)
1096 for cmt in cmts.all():
1096 for cmt in cmts.all():
1097 grouped[cmt.revision].append(cmt)
1097 grouped[cmt.revision].append(cmt)
1098 return grouped
1098 return grouped
1099
1099
1100 def statuses(self, revisions=None):
1100 def statuses(self, revisions=None):
1101 """
1101 """
1102 Returns statuses for this repository
1102 Returns statuses for this repository
1103
1103
1104 :param revisions: list of revisions to get statuses for
1104 :param revisions: list of revisions to get statuses for
1105 :type revisions: list
1105 :type revisions: list
1106 """
1106 """
1107
1107
1108 statuses = ChangesetStatus.query()\
1108 statuses = ChangesetStatus.query()\
1109 .filter(ChangesetStatus.repo == self)\
1109 .filter(ChangesetStatus.repo == self)\
1110 .filter(ChangesetStatus.version == 0)
1110 .filter(ChangesetStatus.version == 0)
1111 if revisions:
1111 if revisions:
1112 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1112 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
1113 grouped = {}
1113 grouped = {}
1114
1114
1115 #maybe we have open new pullrequest without a status ?
1115 #maybe we have open new pullrequest without a status ?
1116 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1116 stat = ChangesetStatus.STATUS_UNDER_REVIEW
1117 status_lbl = ChangesetStatus.get_status_lbl(stat)
1117 status_lbl = ChangesetStatus.get_status_lbl(stat)
1118 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1118 for pr in PullRequest.query().filter(PullRequest.org_repo == self).all():
1119 for rev in pr.revisions:
1119 for rev in pr.revisions:
1120 pr_id = pr.pull_request_id
1120 pr_id = pr.pull_request_id
1121 pr_repo = pr.other_repo.repo_name
1121 pr_repo = pr.other_repo.repo_name
1122 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1122 grouped[rev] = [stat, status_lbl, pr_id, pr_repo]
1123
1123
1124 for stat in statuses.all():
1124 for stat in statuses.all():
1125 pr_id = pr_repo = None
1125 pr_id = pr_repo = None
1126 if stat.pull_request:
1126 if stat.pull_request:
1127 pr_id = stat.pull_request.pull_request_id
1127 pr_id = stat.pull_request.pull_request_id
1128 pr_repo = stat.pull_request.other_repo.repo_name
1128 pr_repo = stat.pull_request.other_repo.repo_name
1129 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1129 grouped[stat.revision] = [str(stat.status), stat.status_lbl,
1130 pr_id, pr_repo]
1130 pr_id, pr_repo]
1131 return grouped
1131 return grouped
1132
1132
1133 def _repo_size(self):
1133 def _repo_size(self):
1134 from rhodecode.lib import helpers as h
1134 from rhodecode.lib import helpers as h
1135 log.debug('calculating repository size...')
1135 log.debug('calculating repository size...')
1136 return h.format_byte_size(self.scm_instance.size)
1136 return h.format_byte_size(self.scm_instance.size)
1137
1137
1138 #==========================================================================
1138 #==========================================================================
1139 # SCM CACHE INSTANCE
1139 # SCM CACHE INSTANCE
1140 #==========================================================================
1140 #==========================================================================
1141
1141
1142 @property
1142 @property
1143 def invalidate(self):
1143 def invalidate(self):
1144 return CacheInvalidation.invalidate(self.repo_name)
1144 return CacheInvalidation.invalidate(self.repo_name)
1145
1145
1146 def set_invalidate(self):
1146 def set_invalidate(self):
1147 """
1147 """
1148 set a cache for invalidation for this instance
1148 set a cache for invalidation for this instance
1149 """
1149 """
1150 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1150 CacheInvalidation.set_invalidate(repo_name=self.repo_name)
1151
1151
1152 @LazyProperty
1152 @LazyProperty
1153 def scm_instance_no_cache(self):
1153 def scm_instance_no_cache(self):
1154 return self.__get_instance()
1154 return self.__get_instance()
1155
1155
1156 @LazyProperty
1156 @LazyProperty
1157 def scm_instance(self):
1157 def scm_instance(self):
1158 import rhodecode
1158 import rhodecode
1159 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1159 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1160 if full_cache:
1160 if full_cache:
1161 return self.scm_instance_cached()
1161 return self.scm_instance_cached()
1162 return self.__get_instance()
1162 return self.__get_instance()
1163
1163
1164 def scm_instance_cached(self, cache_map=None):
1164 def scm_instance_cached(self, cache_map=None):
1165 @cache_region('long_term')
1165 @cache_region('long_term')
1166 def _c(repo_name):
1166 def _c(repo_name):
1167 return self.__get_instance()
1167 return self.__get_instance()
1168 rn = self.repo_name
1168 rn = self.repo_name
1169 log.debug('Getting cached instance of repo')
1169 log.debug('Getting cached instance of repo')
1170
1170
1171 if cache_map:
1171 if cache_map:
1172 # get using prefilled cache_map
1172 # get using prefilled cache_map
1173 invalidate_repo = cache_map[self.repo_name]
1173 invalidate_repo = cache_map[self.repo_name]
1174 if invalidate_repo:
1174 if invalidate_repo:
1175 invalidate_repo = (None if invalidate_repo.cache_active
1175 invalidate_repo = (None if invalidate_repo.cache_active
1176 else invalidate_repo)
1176 else invalidate_repo)
1177 else:
1177 else:
1178 # get from invalidate
1178 # get from invalidate
1179 invalidate_repo = self.invalidate
1179 invalidate_repo = self.invalidate
1180
1180
1181 if invalidate_repo is not None:
1181 if invalidate_repo is not None:
1182 region_invalidate(_c, None, rn)
1182 region_invalidate(_c, None, rn)
1183 # update our cache
1183 # update our cache
1184 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1184 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1185 return _c(rn)
1185 return _c(rn)
1186
1186
1187 def __get_instance(self):
1187 def __get_instance(self):
1188 repo_full_path = self.repo_full_path
1188 repo_full_path = self.repo_full_path
1189 try:
1189 try:
1190 alias = get_scm(repo_full_path)[0]
1190 alias = get_scm(repo_full_path)[0]
1191 log.debug('Creating instance of %s repository from %s'
1191 log.debug('Creating instance of %s repository from %s'
1192 % (alias, repo_full_path))
1192 % (alias, repo_full_path))
1193 backend = get_backend(alias)
1193 backend = get_backend(alias)
1194 except VCSError:
1194 except VCSError:
1195 log.error(traceback.format_exc())
1195 log.error(traceback.format_exc())
1196 log.error('Perhaps this repository is in db and not in '
1196 log.error('Perhaps this repository is in db and not in '
1197 'filesystem run rescan repositories with '
1197 'filesystem run rescan repositories with '
1198 '"destroy old data " option from admin panel')
1198 '"destroy old data " option from admin panel')
1199 return
1199 return
1200
1200
1201 if alias == 'hg':
1201 if alias == 'hg':
1202
1202
1203 repo = backend(safe_str(repo_full_path), create=False,
1203 repo = backend(safe_str(repo_full_path), create=False,
1204 baseui=self._ui)
1204 baseui=self._ui)
1205 # skip hidden web repository
1205 # skip hidden web repository
1206 if repo._get_hidden():
1206 if repo._get_hidden():
1207 return
1207 return
1208 else:
1208 else:
1209 repo = backend(repo_full_path, create=False)
1209 repo = backend(repo_full_path, create=False)
1210
1210
1211 return repo
1211 return repo
1212
1212
1213
1213
1214 class RepoGroup(Base, BaseModel):
1214 class RepoGroup(Base, BaseModel):
1215 __tablename__ = 'groups'
1215 __tablename__ = 'groups'
1216 __table_args__ = (
1216 __table_args__ = (
1217 UniqueConstraint('group_name', 'group_parent_id'),
1217 UniqueConstraint('group_name', 'group_parent_id'),
1218 CheckConstraint('group_id != group_parent_id'),
1218 CheckConstraint('group_id != group_parent_id'),
1219 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1220 'mysql_charset': 'utf8'},
1220 'mysql_charset': 'utf8'},
1221 )
1221 )
1222 __mapper_args__ = {'order_by': 'group_name'}
1222 __mapper_args__ = {'order_by': 'group_name'}
1223
1223
1224 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1225 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1225 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1226 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1226 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1227 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1227 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1228 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1228 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1229
1229
1230 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1230 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1231 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1231 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1232
1232
1233 parent_group = relationship('RepoGroup', remote_side=group_id)
1233 parent_group = relationship('RepoGroup', remote_side=group_id)
1234
1234
1235 def __init__(self, group_name='', parent_group=None):
1235 def __init__(self, group_name='', parent_group=None):
1236 self.group_name = group_name
1236 self.group_name = group_name
1237 self.parent_group = parent_group
1237 self.parent_group = parent_group
1238
1238
1239 def __unicode__(self):
1239 def __unicode__(self):
1240 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1240 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1241 self.group_name)
1241 self.group_name)
1242
1242
1243 @classmethod
1243 @classmethod
1244 def groups_choices(cls, groups=None, show_empty_group=True):
1244 def groups_choices(cls, groups=None, show_empty_group=True):
1245 from webhelpers.html import literal as _literal
1245 from webhelpers.html import literal as _literal
1246 if not groups:
1246 if not groups:
1247 groups = cls.query().all()
1247 groups = cls.query().all()
1248
1248
1249 repo_groups = []
1249 repo_groups = []
1250 if show_empty_group:
1250 if show_empty_group:
1251 repo_groups = [('-1', '-- no parent --')]
1251 repo_groups = [('-1', '-- no parent --')]
1252 sep = ' &raquo; '
1252 sep = ' &raquo; '
1253 _name = lambda k: _literal(sep.join(k))
1253 _name = lambda k: _literal(sep.join(k))
1254
1254
1255 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1255 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1256 for x in groups])
1256 for x in groups])
1257
1257
1258 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1258 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1259 return repo_groups
1259 return repo_groups
1260
1260
1261 @classmethod
1261 @classmethod
1262 def url_sep(cls):
1262 def url_sep(cls):
1263 return URL_SEP
1263 return URL_SEP
1264
1264
1265 @classmethod
1265 @classmethod
1266 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1266 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1267 if case_insensitive:
1267 if case_insensitive:
1268 gr = cls.query()\
1268 gr = cls.query()\
1269 .filter(cls.group_name.ilike(group_name))
1269 .filter(cls.group_name.ilike(group_name))
1270 else:
1270 else:
1271 gr = cls.query()\
1271 gr = cls.query()\
1272 .filter(cls.group_name == group_name)
1272 .filter(cls.group_name == group_name)
1273 if cache:
1273 if cache:
1274 gr = gr.options(FromCache(
1274 gr = gr.options(FromCache(
1275 "sql_cache_short",
1275 "sql_cache_short",
1276 "get_group_%s" % _hash_key(group_name)
1276 "get_group_%s" % _hash_key(group_name)
1277 )
1277 )
1278 )
1278 )
1279 return gr.scalar()
1279 return gr.scalar()
1280
1280
1281 @property
1281 @property
1282 def parents(self):
1282 def parents(self):
1283 parents_recursion_limit = 5
1283 parents_recursion_limit = 5
1284 groups = []
1284 groups = []
1285 if self.parent_group is None:
1285 if self.parent_group is None:
1286 return groups
1286 return groups
1287 cur_gr = self.parent_group
1287 cur_gr = self.parent_group
1288 groups.insert(0, cur_gr)
1288 groups.insert(0, cur_gr)
1289 cnt = 0
1289 cnt = 0
1290 while 1:
1290 while 1:
1291 cnt += 1
1291 cnt += 1
1292 gr = getattr(cur_gr, 'parent_group', None)
1292 gr = getattr(cur_gr, 'parent_group', None)
1293 cur_gr = cur_gr.parent_group
1293 cur_gr = cur_gr.parent_group
1294 if gr is None:
1294 if gr is None:
1295 break
1295 break
1296 if cnt == parents_recursion_limit:
1296 if cnt == parents_recursion_limit:
1297 # this will prevent accidental infinit loops
1297 # this will prevent accidental infinit loops
1298 log.error('group nested more than %s' %
1298 log.error('group nested more than %s' %
1299 parents_recursion_limit)
1299 parents_recursion_limit)
1300 break
1300 break
1301
1301
1302 groups.insert(0, gr)
1302 groups.insert(0, gr)
1303 return groups
1303 return groups
1304
1304
1305 @property
1305 @property
1306 def children(self):
1306 def children(self):
1307 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1307 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1308
1308
1309 @property
1309 @property
1310 def name(self):
1310 def name(self):
1311 return self.group_name.split(RepoGroup.url_sep())[-1]
1311 return self.group_name.split(RepoGroup.url_sep())[-1]
1312
1312
1313 @property
1313 @property
1314 def full_path(self):
1314 def full_path(self):
1315 return self.group_name
1315 return self.group_name
1316
1316
1317 @property
1317 @property
1318 def full_path_splitted(self):
1318 def full_path_splitted(self):
1319 return self.group_name.split(RepoGroup.url_sep())
1319 return self.group_name.split(RepoGroup.url_sep())
1320
1320
1321 @property
1321 @property
1322 def repositories(self):
1322 def repositories(self):
1323 return Repository.query()\
1323 return Repository.query()\
1324 .filter(Repository.group == self)\
1324 .filter(Repository.group == self)\
1325 .order_by(Repository.repo_name)
1325 .order_by(Repository.repo_name)
1326
1326
1327 @property
1327 @property
1328 def repositories_recursive_count(self):
1328 def repositories_recursive_count(self):
1329 cnt = self.repositories.count()
1329 cnt = self.repositories.count()
1330
1330
1331 def children_count(group):
1331 def children_count(group):
1332 cnt = 0
1332 cnt = 0
1333 for child in group.children:
1333 for child in group.children:
1334 cnt += child.repositories.count()
1334 cnt += child.repositories.count()
1335 cnt += children_count(child)
1335 cnt += children_count(child)
1336 return cnt
1336 return cnt
1337
1337
1338 return cnt + children_count(self)
1338 return cnt + children_count(self)
1339
1339
1340 def _recursive_objects(self, include_repos=True):
1340 def _recursive_objects(self, include_repos=True):
1341 all_ = []
1341 all_ = []
1342
1342
1343 def _get_members(root_gr):
1343 def _get_members(root_gr):
1344 if include_repos:
1344 if include_repos:
1345 for r in root_gr.repositories:
1345 for r in root_gr.repositories:
1346 all_.append(r)
1346 all_.append(r)
1347 childs = root_gr.children.all()
1347 childs = root_gr.children.all()
1348 if childs:
1348 if childs:
1349 for gr in childs:
1349 for gr in childs:
1350 all_.append(gr)
1350 all_.append(gr)
1351 _get_members(gr)
1351 _get_members(gr)
1352
1352
1353 _get_members(self)
1353 _get_members(self)
1354 return [self] + all_
1354 return [self] + all_
1355
1355
1356 def recursive_groups_and_repos(self):
1356 def recursive_groups_and_repos(self):
1357 """
1357 """
1358 Recursive return all groups, with repositories in those groups
1358 Recursive return all groups, with repositories in those groups
1359 """
1359 """
1360 return self._recursive_objects()
1360 return self._recursive_objects()
1361
1361
1362 def recursive_groups(self):
1362 def recursive_groups(self):
1363 """
1363 """
1364 Returns all children groups for this group including children of children
1364 Returns all children groups for this group including children of children
1365 """
1365 """
1366 return self._recursive_objects(include_repos=False)
1366 return self._recursive_objects(include_repos=False)
1367
1367
1368 def get_new_name(self, group_name):
1368 def get_new_name(self, group_name):
1369 """
1369 """
1370 returns new full group name based on parent and new name
1370 returns new full group name based on parent and new name
1371
1371
1372 :param group_name:
1372 :param group_name:
1373 """
1373 """
1374 path_prefix = (self.parent_group.full_path_splitted if
1374 path_prefix = (self.parent_group.full_path_splitted if
1375 self.parent_group else [])
1375 self.parent_group else [])
1376 return RepoGroup.url_sep().join(path_prefix + [group_name])
1376 return RepoGroup.url_sep().join(path_prefix + [group_name])
1377
1377
1378
1378
1379 class Permission(Base, BaseModel):
1379 class Permission(Base, BaseModel):
1380 __tablename__ = 'permissions'
1380 __tablename__ = 'permissions'
1381 __table_args__ = (
1381 __table_args__ = (
1382 Index('p_perm_name_idx', 'permission_name'),
1382 Index('p_perm_name_idx', 'permission_name'),
1383 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1383 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1384 'mysql_charset': 'utf8'},
1384 'mysql_charset': 'utf8'},
1385 )
1385 )
1386 PERMS = [
1386 PERMS = [
1387 ('repository.none', _('Repository no access')),
1387 ('repository.none', _('Repository no access')),
1388 ('repository.read', _('Repository read access')),
1388 ('repository.read', _('Repository read access')),
1389 ('repository.write', _('Repository write access')),
1389 ('repository.write', _('Repository write access')),
1390 ('repository.admin', _('Repository admin access')),
1390 ('repository.admin', _('Repository admin access')),
1391
1391
1392 ('group.none', _('Repository group no access')),
1392 ('group.none', _('Repository group no access')),
1393 ('group.read', _('Repository group read access')),
1393 ('group.read', _('Repository group read access')),
1394 ('group.write', _('Repository group write access')),
1394 ('group.write', _('Repository group write access')),
1395 ('group.admin', _('Repository group admin access')),
1395 ('group.admin', _('Repository group admin access')),
1396
1396
1397 ('hg.admin', _('RhodeCode Administrator')),
1397 ('hg.admin', _('RhodeCode Administrator')),
1398 ('hg.create.none', _('Repository creation disabled')),
1398 ('hg.create.none', _('Repository creation disabled')),
1399 ('hg.create.repository', _('Repository creation enabled')),
1399 ('hg.create.repository', _('Repository creation enabled')),
1400 ('hg.fork.none', _('Repository forking disabled')),
1400 ('hg.fork.none', _('Repository forking disabled')),
1401 ('hg.fork.repository', _('Repository forking enabled')),
1401 ('hg.fork.repository', _('Repository forking enabled')),
1402 ('hg.register.none', _('Register disabled')),
1402 ('hg.register.none', _('Register disabled')),
1403 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1403 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1404 'with manual activation')),
1404 'with manual activation')),
1405
1405
1406 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1406 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1407 'with auto activation')),
1407 'with auto activation')),
1408 ]
1408 ]
1409
1409
1410 # defines which permissions are more important higher the more important
1410 # defines which permissions are more important higher the more important
1411 PERM_WEIGHTS = {
1411 PERM_WEIGHTS = {
1412 'repository.none': 0,
1412 'repository.none': 0,
1413 'repository.read': 1,
1413 'repository.read': 1,
1414 'repository.write': 3,
1414 'repository.write': 3,
1415 'repository.admin': 4,
1415 'repository.admin': 4,
1416
1416
1417 'group.none': 0,
1417 'group.none': 0,
1418 'group.read': 1,
1418 'group.read': 1,
1419 'group.write': 3,
1419 'group.write': 3,
1420 'group.admin': 4,
1420 'group.admin': 4,
1421
1421
1422 'hg.fork.none': 0,
1422 'hg.fork.none': 0,
1423 'hg.fork.repository': 1,
1423 'hg.fork.repository': 1,
1424 'hg.create.none': 0,
1424 'hg.create.none': 0,
1425 'hg.create.repository':1
1425 'hg.create.repository':1
1426 }
1426 }
1427
1427
1428 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1428 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1429 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1429 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1430 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1430 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1431
1431
1432 def __unicode__(self):
1432 def __unicode__(self):
1433 return u"<%s('%s:%s')>" % (
1433 return u"<%s('%s:%s')>" % (
1434 self.__class__.__name__, self.permission_id, self.permission_name
1434 self.__class__.__name__, self.permission_id, self.permission_name
1435 )
1435 )
1436
1436
1437 @classmethod
1437 @classmethod
1438 def get_by_key(cls, key):
1438 def get_by_key(cls, key):
1439 return cls.query().filter(cls.permission_name == key).scalar()
1439 return cls.query().filter(cls.permission_name == key).scalar()
1440
1440
1441 @classmethod
1441 @classmethod
1442 def get_default_perms(cls, default_user_id):
1442 def get_default_perms(cls, default_user_id):
1443 q = Session().query(UserRepoToPerm, Repository, cls)\
1443 q = Session().query(UserRepoToPerm, Repository, cls)\
1444 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1444 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1445 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1445 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1446 .filter(UserRepoToPerm.user_id == default_user_id)
1446 .filter(UserRepoToPerm.user_id == default_user_id)
1447
1447
1448 return q.all()
1448 return q.all()
1449
1449
1450 @classmethod
1450 @classmethod
1451 def get_default_group_perms(cls, default_user_id):
1451 def get_default_group_perms(cls, default_user_id):
1452 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1452 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1453 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1453 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1454 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1454 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1455 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1455 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1456
1456
1457 return q.all()
1457 return q.all()
1458
1458
1459
1459
1460 class UserRepoToPerm(Base, BaseModel):
1460 class UserRepoToPerm(Base, BaseModel):
1461 __tablename__ = 'repo_to_perm'
1461 __tablename__ = 'repo_to_perm'
1462 __table_args__ = (
1462 __table_args__ = (
1463 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1463 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1465 'mysql_charset': 'utf8'}
1465 'mysql_charset': 'utf8'}
1466 )
1466 )
1467 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1467 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1468 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1469 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1469 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1470 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1470 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1471
1471
1472 user = relationship('User')
1472 user = relationship('User')
1473 repository = relationship('Repository')
1473 repository = relationship('Repository')
1474 permission = relationship('Permission')
1474 permission = relationship('Permission')
1475
1475
1476 @classmethod
1476 @classmethod
1477 def create(cls, user, repository, permission):
1477 def create(cls, user, repository, permission):
1478 n = cls()
1478 n = cls()
1479 n.user = user
1479 n.user = user
1480 n.repository = repository
1480 n.repository = repository
1481 n.permission = permission
1481 n.permission = permission
1482 Session().add(n)
1482 Session().add(n)
1483 return n
1483 return n
1484
1484
1485 def __unicode__(self):
1485 def __unicode__(self):
1486 return u'<user:%s => %s >' % (self.user, self.repository)
1486 return u'<user:%s => %s >' % (self.user, self.repository)
1487
1487
1488
1488
1489 class UserToPerm(Base, BaseModel):
1489 class UserToPerm(Base, BaseModel):
1490 __tablename__ = 'user_to_perm'
1490 __tablename__ = 'user_to_perm'
1491 __table_args__ = (
1491 __table_args__ = (
1492 UniqueConstraint('user_id', 'permission_id'),
1492 UniqueConstraint('user_id', 'permission_id'),
1493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1494 'mysql_charset': 'utf8'}
1494 'mysql_charset': 'utf8'}
1495 )
1495 )
1496 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1496 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1497 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1497 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1498 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1498 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1499
1499
1500 user = relationship('User')
1500 user = relationship('User')
1501 permission = relationship('Permission', lazy='joined')
1501 permission = relationship('Permission', lazy='joined')
1502
1502
1503
1503
1504 class UserGroupRepoToPerm(Base, BaseModel):
1504 class UserGroupRepoToPerm(Base, BaseModel):
1505 __tablename__ = 'users_group_repo_to_perm'
1505 __tablename__ = 'users_group_repo_to_perm'
1506 __table_args__ = (
1506 __table_args__ = (
1507 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1507 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1508 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1508 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1509 'mysql_charset': 'utf8'}
1509 'mysql_charset': 'utf8'}
1510 )
1510 )
1511 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1511 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1512 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1512 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1513 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1513 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1514 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1514 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1515
1515
1516 users_group = relationship('UserGroup')
1516 users_group = relationship('UserGroup')
1517 permission = relationship('Permission')
1517 permission = relationship('Permission')
1518 repository = relationship('Repository')
1518 repository = relationship('Repository')
1519
1519
1520 @classmethod
1520 @classmethod
1521 def create(cls, users_group, repository, permission):
1521 def create(cls, users_group, repository, permission):
1522 n = cls()
1522 n = cls()
1523 n.users_group = users_group
1523 n.users_group = users_group
1524 n.repository = repository
1524 n.repository = repository
1525 n.permission = permission
1525 n.permission = permission
1526 Session().add(n)
1526 Session().add(n)
1527 return n
1527 return n
1528
1528
1529 def __unicode__(self):
1529 def __unicode__(self):
1530 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1530 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1531
1531
1532
1532
1533 class UserGroupToPerm(Base, BaseModel):
1533 class UserGroupToPerm(Base, BaseModel):
1534 __tablename__ = 'users_group_to_perm'
1534 __tablename__ = 'users_group_to_perm'
1535 __table_args__ = (
1535 __table_args__ = (
1536 UniqueConstraint('users_group_id', 'permission_id',),
1536 UniqueConstraint('users_group_id', 'permission_id',),
1537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1537 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1538 'mysql_charset': 'utf8'}
1538 'mysql_charset': 'utf8'}
1539 )
1539 )
1540 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1540 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1541 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1541 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1542 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1542 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1543
1543
1544 users_group = relationship('UserGroup')
1544 users_group = relationship('UserGroup')
1545 permission = relationship('Permission')
1545 permission = relationship('Permission')
1546
1546
1547
1547
1548 class UserRepoGroupToPerm(Base, BaseModel):
1548 class UserRepoGroupToPerm(Base, BaseModel):
1549 __tablename__ = 'user_repo_group_to_perm'
1549 __tablename__ = 'user_repo_group_to_perm'
1550 __table_args__ = (
1550 __table_args__ = (
1551 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1551 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1552 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1553 'mysql_charset': 'utf8'}
1553 'mysql_charset': 'utf8'}
1554 )
1554 )
1555
1555
1556 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1556 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1557 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1558 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1558 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1559 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1559 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1560
1560
1561 user = relationship('User')
1561 user = relationship('User')
1562 group = relationship('RepoGroup')
1562 group = relationship('RepoGroup')
1563 permission = relationship('Permission')
1563 permission = relationship('Permission')
1564
1564
1565
1565
1566 class UserGroupRepoGroupToPerm(Base, BaseModel):
1566 class UserGroupRepoGroupToPerm(Base, BaseModel):
1567 __tablename__ = 'users_group_repo_group_to_perm'
1567 __tablename__ = 'users_group_repo_group_to_perm'
1568 __table_args__ = (
1568 __table_args__ = (
1569 UniqueConstraint('users_group_id', 'group_id'),
1569 UniqueConstraint('users_group_id', 'group_id'),
1570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1570 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1571 'mysql_charset': 'utf8'}
1571 'mysql_charset': 'utf8'}
1572 )
1572 )
1573
1573
1574 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)
1574 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)
1575 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1576 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1576 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1577 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1577 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1578
1578
1579 users_group = relationship('UserGroup')
1579 users_group = relationship('UserGroup')
1580 permission = relationship('Permission')
1580 permission = relationship('Permission')
1581 group = relationship('RepoGroup')
1581 group = relationship('RepoGroup')
1582
1582
1583
1583
1584 class Statistics(Base, BaseModel):
1584 class Statistics(Base, BaseModel):
1585 __tablename__ = 'statistics'
1585 __tablename__ = 'statistics'
1586 __table_args__ = (
1586 __table_args__ = (
1587 UniqueConstraint('repository_id'),
1587 UniqueConstraint('repository_id'),
1588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1588 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1589 'mysql_charset': 'utf8'}
1589 'mysql_charset': 'utf8'}
1590 )
1590 )
1591 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1592 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1592 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1593 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1593 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1594 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1594 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1595 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1595 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1596 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1596 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1597
1597
1598 repository = relationship('Repository', single_parent=True)
1598 repository = relationship('Repository', single_parent=True)
1599
1599
1600
1600
1601 class UserFollowing(Base, BaseModel):
1601 class UserFollowing(Base, BaseModel):
1602 __tablename__ = 'user_followings'
1602 __tablename__ = 'user_followings'
1603 __table_args__ = (
1603 __table_args__ = (
1604 UniqueConstraint('user_id', 'follows_repository_id'),
1604 UniqueConstraint('user_id', 'follows_repository_id'),
1605 UniqueConstraint('user_id', 'follows_user_id'),
1605 UniqueConstraint('user_id', 'follows_user_id'),
1606 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1606 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1607 'mysql_charset': 'utf8'}
1607 'mysql_charset': 'utf8'}
1608 )
1608 )
1609
1609
1610 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1610 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1611 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1611 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1612 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1612 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1613 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1613 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1614 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1614 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1615
1615
1616 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1616 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1617
1617
1618 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1618 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1619 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1619 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1620
1620
1621 @classmethod
1621 @classmethod
1622 def get_repo_followers(cls, repo_id):
1622 def get_repo_followers(cls, repo_id):
1623 return cls.query().filter(cls.follows_repo_id == repo_id)
1623 return cls.query().filter(cls.follows_repo_id == repo_id)
1624
1624
1625
1625
1626 class CacheInvalidation(Base, BaseModel):
1626 class CacheInvalidation(Base, BaseModel):
1627 __tablename__ = 'cache_invalidation'
1627 __tablename__ = 'cache_invalidation'
1628 __table_args__ = (
1628 __table_args__ = (
1629 UniqueConstraint('cache_key'),
1629 UniqueConstraint('cache_key'),
1630 Index('key_idx', 'cache_key'),
1630 Index('key_idx', 'cache_key'),
1631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1632 'mysql_charset': 'utf8'},
1632 'mysql_charset': 'utf8'},
1633 )
1633 )
1634 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1634 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1635 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1635 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1636 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1636 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1637 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1637 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1638
1638
1639 def __init__(self, cache_key, cache_args=''):
1639 def __init__(self, cache_key, cache_args=''):
1640 self.cache_key = cache_key
1640 self.cache_key = cache_key
1641 self.cache_args = cache_args
1641 self.cache_args = cache_args
1642 self.cache_active = False
1642 self.cache_active = False
1643
1643
1644 def __unicode__(self):
1644 def __unicode__(self):
1645 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1645 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1646 self.cache_id, self.cache_key)
1646 self.cache_id, self.cache_key)
1647
1647
1648 @property
1648 @property
1649 def prefix(self):
1649 def prefix(self):
1650 _split = self.cache_key.split(self.cache_args, 1)
1650 _split = self.cache_key.split(self.cache_args, 1)
1651 if _split and len(_split) == 2:
1651 if _split and len(_split) == 2:
1652 return _split[0]
1652 return _split[0]
1653 return ''
1653 return ''
1654
1654
1655 @classmethod
1655 @classmethod
1656 def clear_cache(cls):
1656 def clear_cache(cls):
1657 cls.query().delete()
1657 cls.query().delete()
1658
1658
1659 @classmethod
1659 @classmethod
1660 def _get_key(cls, key):
1660 def _get_key(cls, key):
1661 """
1661 """
1662 Wrapper for generating a key, together with a prefix
1662 Wrapper for generating a key, together with a prefix
1663
1663
1664 :param key:
1664 :param key:
1665 """
1665 """
1666 import rhodecode
1666 import rhodecode
1667 prefix = ''
1667 prefix = ''
1668 org_key = key
1668 org_key = key
1669 iid = rhodecode.CONFIG.get('instance_id')
1669 iid = rhodecode.CONFIG.get('instance_id')
1670 if iid:
1670 if iid:
1671 prefix = iid
1671 prefix = iid
1672
1672
1673 return "%s%s" % (prefix, key), prefix, org_key
1673 return "%s%s" % (prefix, key), prefix, org_key
1674
1674
1675 @classmethod
1675 @classmethod
1676 def get_by_key(cls, key):
1676 def get_by_key(cls, key):
1677 return cls.query().filter(cls.cache_key == key).scalar()
1677 return cls.query().filter(cls.cache_key == key).scalar()
1678
1678
1679 @classmethod
1679 @classmethod
1680 def get_by_repo_name(cls, repo_name):
1680 def get_by_repo_name(cls, repo_name):
1681 return cls.query().filter(cls.cache_args == repo_name).all()
1681 return cls.query().filter(cls.cache_args == repo_name).all()
1682
1682
1683 @classmethod
1683 @classmethod
1684 def _get_or_create_key(cls, key, repo_name, commit=True):
1684 def _get_or_create_key(cls, key, repo_name, commit=True):
1685 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1685 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1686 if not inv_obj:
1686 if not inv_obj:
1687 try:
1687 try:
1688 inv_obj = CacheInvalidation(key, repo_name)
1688 inv_obj = CacheInvalidation(key, repo_name)
1689 Session().add(inv_obj)
1689 Session().add(inv_obj)
1690 if commit:
1690 if commit:
1691 Session().commit()
1691 Session().commit()
1692 except Exception:
1692 except Exception:
1693 log.error(traceback.format_exc())
1693 log.error(traceback.format_exc())
1694 Session().rollback()
1694 Session().rollback()
1695 return inv_obj
1695 return inv_obj
1696
1696
1697 @classmethod
1697 @classmethod
1698 def invalidate(cls, key):
1698 def invalidate(cls, key):
1699 """
1699 """
1700 Returns Invalidation object if this given key should be invalidated
1700 Returns Invalidation object if this given key should be invalidated
1701 None otherwise. `cache_active = False` means that this cache
1701 None otherwise. `cache_active = False` means that this cache
1702 state is not valid and needs to be invalidated
1702 state is not valid and needs to be invalidated
1703
1703
1704 :param key:
1704 :param key:
1705 """
1705 """
1706 repo_name = key
1706 repo_name = key
1707 repo_name = remove_suffix(repo_name, '_README')
1707 repo_name = remove_suffix(repo_name, '_README')
1708 repo_name = remove_suffix(repo_name, '_RSS')
1708 repo_name = remove_suffix(repo_name, '_RSS')
1709 repo_name = remove_suffix(repo_name, '_ATOM')
1709 repo_name = remove_suffix(repo_name, '_ATOM')
1710
1710
1711 # adds instance prefix
1711 # adds instance prefix
1712 key, _prefix, _org_key = cls._get_key(key)
1712 key, _prefix, _org_key = cls._get_key(key)
1713 inv = cls._get_or_create_key(key, repo_name)
1713 inv = cls._get_or_create_key(key, repo_name)
1714
1714
1715 if inv and inv.cache_active is False:
1715 if inv and inv.cache_active is False:
1716 return inv
1716 return inv
1717
1717
1718 @classmethod
1718 @classmethod
1719 def set_invalidate(cls, key=None, repo_name=None):
1719 def set_invalidate(cls, key=None, repo_name=None):
1720 """
1720 """
1721 Mark this Cache key for invalidation, either by key or whole
1721 Mark this Cache key for invalidation, either by key or whole
1722 cache sets based on repo_name
1722 cache sets based on repo_name
1723
1723
1724 :param key:
1724 :param key:
1725 """
1725 """
1726 invalidated_keys = []
1726 invalidated_keys = []
1727 if key:
1727 if key:
1728 key, _prefix, _org_key = cls._get_key(key)
1728 key, _prefix, _org_key = cls._get_key(key)
1729 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1729 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1730 elif repo_name:
1730 elif repo_name:
1731 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1731 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1732
1732
1733 try:
1733 try:
1734 for inv_obj in inv_objs:
1734 for inv_obj in inv_objs:
1735 inv_obj.cache_active = False
1735 inv_obj.cache_active = False
1736 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1736 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1737 % (inv_obj, key, repo_name))
1737 % (inv_obj, key, safe_str(repo_name)))
1738 invalidated_keys.append(inv_obj.cache_key)
1738 invalidated_keys.append(inv_obj.cache_key)
1739 Session().add(inv_obj)
1739 Session().add(inv_obj)
1740 Session().commit()
1740 Session().commit()
1741 except Exception:
1741 except Exception:
1742 log.error(traceback.format_exc())
1742 log.error(traceback.format_exc())
1743 Session().rollback()
1743 Session().rollback()
1744 return invalidated_keys
1744 return invalidated_keys
1745
1745
1746 @classmethod
1746 @classmethod
1747 def set_valid(cls, key):
1747 def set_valid(cls, key):
1748 """
1748 """
1749 Mark this cache key as active and currently cached
1749 Mark this cache key as active and currently cached
1750
1750
1751 :param key:
1751 :param key:
1752 """
1752 """
1753 inv_obj = cls.get_by_key(key)
1753 inv_obj = cls.get_by_key(key)
1754 inv_obj.cache_active = True
1754 inv_obj.cache_active = True
1755 Session().add(inv_obj)
1755 Session().add(inv_obj)
1756 Session().commit()
1756 Session().commit()
1757
1757
1758 @classmethod
1758 @classmethod
1759 def get_cache_map(cls):
1759 def get_cache_map(cls):
1760
1760
1761 class cachemapdict(dict):
1761 class cachemapdict(dict):
1762
1762
1763 def __init__(self, *args, **kwargs):
1763 def __init__(self, *args, **kwargs):
1764 fixkey = kwargs.get('fixkey')
1764 fixkey = kwargs.get('fixkey')
1765 if fixkey:
1765 if fixkey:
1766 del kwargs['fixkey']
1766 del kwargs['fixkey']
1767 self.fixkey = fixkey
1767 self.fixkey = fixkey
1768 super(cachemapdict, self).__init__(*args, **kwargs)
1768 super(cachemapdict, self).__init__(*args, **kwargs)
1769
1769
1770 def __getattr__(self, name):
1770 def __getattr__(self, name):
1771 key = name
1771 key = name
1772 if self.fixkey:
1772 if self.fixkey:
1773 key, _prefix, _org_key = cls._get_key(key)
1773 key, _prefix, _org_key = cls._get_key(key)
1774 if key in self.__dict__:
1774 if key in self.__dict__:
1775 return self.__dict__[key]
1775 return self.__dict__[key]
1776 else:
1776 else:
1777 return self[key]
1777 return self[key]
1778
1778
1779 def __getitem__(self, key):
1779 def __getitem__(self, key):
1780 if self.fixkey:
1780 if self.fixkey:
1781 key, _prefix, _org_key = cls._get_key(key)
1781 key, _prefix, _org_key = cls._get_key(key)
1782 try:
1782 try:
1783 return super(cachemapdict, self).__getitem__(key)
1783 return super(cachemapdict, self).__getitem__(key)
1784 except KeyError:
1784 except KeyError:
1785 return
1785 return
1786
1786
1787 cache_map = cachemapdict(fixkey=True)
1787 cache_map = cachemapdict(fixkey=True)
1788 for obj in cls.query().all():
1788 for obj in cls.query().all():
1789 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1789 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1790 return cache_map
1790 return cache_map
1791
1791
1792
1792
1793 class ChangesetComment(Base, BaseModel):
1793 class ChangesetComment(Base, BaseModel):
1794 __tablename__ = 'changeset_comments'
1794 __tablename__ = 'changeset_comments'
1795 __table_args__ = (
1795 __table_args__ = (
1796 Index('cc_revision_idx', 'revision'),
1796 Index('cc_revision_idx', 'revision'),
1797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1797 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1798 'mysql_charset': 'utf8'},
1798 'mysql_charset': 'utf8'},
1799 )
1799 )
1800 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1800 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1801 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1801 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1802 revision = Column('revision', String(40), nullable=True)
1802 revision = Column('revision', String(40), nullable=True)
1803 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1803 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1804 line_no = Column('line_no', Unicode(10), nullable=True)
1804 line_no = Column('line_no', Unicode(10), nullable=True)
1805 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1805 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1806 f_path = Column('f_path', Unicode(1000), nullable=True)
1806 f_path = Column('f_path', Unicode(1000), nullable=True)
1807 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1807 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1808 text = Column('text', UnicodeText(25000), nullable=False)
1808 text = Column('text', UnicodeText(25000), nullable=False)
1809 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1809 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1810 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1810 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1811
1811
1812 author = relationship('User', lazy='joined')
1812 author = relationship('User', lazy='joined')
1813 repo = relationship('Repository')
1813 repo = relationship('Repository')
1814 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1814 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1815 pull_request = relationship('PullRequest', lazy='joined')
1815 pull_request = relationship('PullRequest', lazy='joined')
1816
1816
1817 @classmethod
1817 @classmethod
1818 def get_users(cls, revision=None, pull_request_id=None):
1818 def get_users(cls, revision=None, pull_request_id=None):
1819 """
1819 """
1820 Returns user associated with this ChangesetComment. ie those
1820 Returns user associated with this ChangesetComment. ie those
1821 who actually commented
1821 who actually commented
1822
1822
1823 :param cls:
1823 :param cls:
1824 :param revision:
1824 :param revision:
1825 """
1825 """
1826 q = Session().query(User)\
1826 q = Session().query(User)\
1827 .join(ChangesetComment.author)
1827 .join(ChangesetComment.author)
1828 if revision:
1828 if revision:
1829 q = q.filter(cls.revision == revision)
1829 q = q.filter(cls.revision == revision)
1830 elif pull_request_id:
1830 elif pull_request_id:
1831 q = q.filter(cls.pull_request_id == pull_request_id)
1831 q = q.filter(cls.pull_request_id == pull_request_id)
1832 return q.all()
1832 return q.all()
1833
1833
1834
1834
1835 class ChangesetStatus(Base, BaseModel):
1835 class ChangesetStatus(Base, BaseModel):
1836 __tablename__ = 'changeset_statuses'
1836 __tablename__ = 'changeset_statuses'
1837 __table_args__ = (
1837 __table_args__ = (
1838 Index('cs_revision_idx', 'revision'),
1838 Index('cs_revision_idx', 'revision'),
1839 Index('cs_version_idx', 'version'),
1839 Index('cs_version_idx', 'version'),
1840 UniqueConstraint('repo_id', 'revision', 'version'),
1840 UniqueConstraint('repo_id', 'revision', 'version'),
1841 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1841 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1842 'mysql_charset': 'utf8'}
1842 'mysql_charset': 'utf8'}
1843 )
1843 )
1844 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1844 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1845 STATUS_APPROVED = 'approved'
1845 STATUS_APPROVED = 'approved'
1846 STATUS_REJECTED = 'rejected'
1846 STATUS_REJECTED = 'rejected'
1847 STATUS_UNDER_REVIEW = 'under_review'
1847 STATUS_UNDER_REVIEW = 'under_review'
1848
1848
1849 STATUSES = [
1849 STATUSES = [
1850 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1850 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1851 (STATUS_APPROVED, _("Approved")),
1851 (STATUS_APPROVED, _("Approved")),
1852 (STATUS_REJECTED, _("Rejected")),
1852 (STATUS_REJECTED, _("Rejected")),
1853 (STATUS_UNDER_REVIEW, _("Under Review")),
1853 (STATUS_UNDER_REVIEW, _("Under Review")),
1854 ]
1854 ]
1855
1855
1856 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1856 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1857 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1857 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1858 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1858 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1859 revision = Column('revision', String(40), nullable=False)
1859 revision = Column('revision', String(40), nullable=False)
1860 status = Column('status', String(128), nullable=False, default=DEFAULT)
1860 status = Column('status', String(128), nullable=False, default=DEFAULT)
1861 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1861 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1862 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1862 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1863 version = Column('version', Integer(), nullable=False, default=0)
1863 version = Column('version', Integer(), nullable=False, default=0)
1864 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1864 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1865
1865
1866 author = relationship('User', lazy='joined')
1866 author = relationship('User', lazy='joined')
1867 repo = relationship('Repository')
1867 repo = relationship('Repository')
1868 comment = relationship('ChangesetComment', lazy='joined')
1868 comment = relationship('ChangesetComment', lazy='joined')
1869 pull_request = relationship('PullRequest', lazy='joined')
1869 pull_request = relationship('PullRequest', lazy='joined')
1870
1870
1871 def __unicode__(self):
1871 def __unicode__(self):
1872 return u"<%s('%s:%s')>" % (
1872 return u"<%s('%s:%s')>" % (
1873 self.__class__.__name__,
1873 self.__class__.__name__,
1874 self.status, self.author
1874 self.status, self.author
1875 )
1875 )
1876
1876
1877 @classmethod
1877 @classmethod
1878 def get_status_lbl(cls, value):
1878 def get_status_lbl(cls, value):
1879 return dict(cls.STATUSES).get(value)
1879 return dict(cls.STATUSES).get(value)
1880
1880
1881 @property
1881 @property
1882 def status_lbl(self):
1882 def status_lbl(self):
1883 return ChangesetStatus.get_status_lbl(self.status)
1883 return ChangesetStatus.get_status_lbl(self.status)
1884
1884
1885
1885
1886 class PullRequest(Base, BaseModel):
1886 class PullRequest(Base, BaseModel):
1887 __tablename__ = 'pull_requests'
1887 __tablename__ = 'pull_requests'
1888 __table_args__ = (
1888 __table_args__ = (
1889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1890 'mysql_charset': 'utf8'},
1890 'mysql_charset': 'utf8'},
1891 )
1891 )
1892
1892
1893 STATUS_NEW = u'new'
1893 STATUS_NEW = u'new'
1894 STATUS_OPEN = u'open'
1894 STATUS_OPEN = u'open'
1895 STATUS_CLOSED = u'closed'
1895 STATUS_CLOSED = u'closed'
1896
1896
1897 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1897 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1898 title = Column('title', Unicode(256), nullable=True)
1898 title = Column('title', Unicode(256), nullable=True)
1899 description = Column('description', UnicodeText(10240), nullable=True)
1899 description = Column('description', UnicodeText(10240), nullable=True)
1900 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1900 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1901 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1901 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1902 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1902 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1903 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1903 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1904 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1904 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1905 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1905 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1906 org_ref = Column('org_ref', Unicode(256), nullable=False)
1906 org_ref = Column('org_ref', Unicode(256), nullable=False)
1907 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1907 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1908 other_ref = Column('other_ref', Unicode(256), nullable=False)
1908 other_ref = Column('other_ref', Unicode(256), nullable=False)
1909
1909
1910 @hybrid_property
1910 @hybrid_property
1911 def revisions(self):
1911 def revisions(self):
1912 return self._revisions.split(':')
1912 return self._revisions.split(':')
1913
1913
1914 @revisions.setter
1914 @revisions.setter
1915 def revisions(self, val):
1915 def revisions(self, val):
1916 self._revisions = ':'.join(val)
1916 self._revisions = ':'.join(val)
1917
1917
1918 @property
1918 @property
1919 def org_ref_parts(self):
1919 def org_ref_parts(self):
1920 return self.org_ref.split(':')
1920 return self.org_ref.split(':')
1921
1921
1922 @property
1922 @property
1923 def other_ref_parts(self):
1923 def other_ref_parts(self):
1924 return self.other_ref.split(':')
1924 return self.other_ref.split(':')
1925
1925
1926 author = relationship('User', lazy='joined')
1926 author = relationship('User', lazy='joined')
1927 reviewers = relationship('PullRequestReviewers',
1927 reviewers = relationship('PullRequestReviewers',
1928 cascade="all, delete, delete-orphan")
1928 cascade="all, delete, delete-orphan")
1929 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1929 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1930 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1930 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1931 statuses = relationship('ChangesetStatus')
1931 statuses = relationship('ChangesetStatus')
1932 comments = relationship('ChangesetComment',
1932 comments = relationship('ChangesetComment',
1933 cascade="all, delete, delete-orphan")
1933 cascade="all, delete, delete-orphan")
1934
1934
1935 def is_closed(self):
1935 def is_closed(self):
1936 return self.status == self.STATUS_CLOSED
1936 return self.status == self.STATUS_CLOSED
1937
1937
1938 @property
1938 @property
1939 def last_review_status(self):
1939 def last_review_status(self):
1940 return self.statuses[-1].status if self.statuses else ''
1940 return self.statuses[-1].status if self.statuses else ''
1941
1941
1942 def __json__(self):
1942 def __json__(self):
1943 return dict(
1943 return dict(
1944 revisions=self.revisions
1944 revisions=self.revisions
1945 )
1945 )
1946
1946
1947
1947
1948 class PullRequestReviewers(Base, BaseModel):
1948 class PullRequestReviewers(Base, BaseModel):
1949 __tablename__ = 'pull_request_reviewers'
1949 __tablename__ = 'pull_request_reviewers'
1950 __table_args__ = (
1950 __table_args__ = (
1951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1951 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1952 'mysql_charset': 'utf8'},
1952 'mysql_charset': 'utf8'},
1953 )
1953 )
1954
1954
1955 def __init__(self, user=None, pull_request=None):
1955 def __init__(self, user=None, pull_request=None):
1956 self.user = user
1956 self.user = user
1957 self.pull_request = pull_request
1957 self.pull_request = pull_request
1958
1958
1959 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1959 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1960 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1960 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1961 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1961 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1962
1962
1963 user = relationship('User')
1963 user = relationship('User')
1964 pull_request = relationship('PullRequest')
1964 pull_request = relationship('PullRequest')
1965
1965
1966
1966
1967 class Notification(Base, BaseModel):
1967 class Notification(Base, BaseModel):
1968 __tablename__ = 'notifications'
1968 __tablename__ = 'notifications'
1969 __table_args__ = (
1969 __table_args__ = (
1970 Index('notification_type_idx', 'type'),
1970 Index('notification_type_idx', 'type'),
1971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1971 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1972 'mysql_charset': 'utf8'},
1972 'mysql_charset': 'utf8'},
1973 )
1973 )
1974
1974
1975 TYPE_CHANGESET_COMMENT = u'cs_comment'
1975 TYPE_CHANGESET_COMMENT = u'cs_comment'
1976 TYPE_MESSAGE = u'message'
1976 TYPE_MESSAGE = u'message'
1977 TYPE_MENTION = u'mention'
1977 TYPE_MENTION = u'mention'
1978 TYPE_REGISTRATION = u'registration'
1978 TYPE_REGISTRATION = u'registration'
1979 TYPE_PULL_REQUEST = u'pull_request'
1979 TYPE_PULL_REQUEST = u'pull_request'
1980 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1980 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1981
1981
1982 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1982 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1983 subject = Column('subject', Unicode(512), nullable=True)
1983 subject = Column('subject', Unicode(512), nullable=True)
1984 body = Column('body', UnicodeText(50000), nullable=True)
1984 body = Column('body', UnicodeText(50000), nullable=True)
1985 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1985 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1986 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1986 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1987 type_ = Column('type', Unicode(256))
1987 type_ = Column('type', Unicode(256))
1988
1988
1989 created_by_user = relationship('User')
1989 created_by_user = relationship('User')
1990 notifications_to_users = relationship('UserNotification', lazy='joined',
1990 notifications_to_users = relationship('UserNotification', lazy='joined',
1991 cascade="all, delete, delete-orphan")
1991 cascade="all, delete, delete-orphan")
1992
1992
1993 @property
1993 @property
1994 def recipients(self):
1994 def recipients(self):
1995 return [x.user for x in UserNotification.query()\
1995 return [x.user for x in UserNotification.query()\
1996 .filter(UserNotification.notification == self)\
1996 .filter(UserNotification.notification == self)\
1997 .order_by(UserNotification.user_id.asc()).all()]
1997 .order_by(UserNotification.user_id.asc()).all()]
1998
1998
1999 @classmethod
1999 @classmethod
2000 def create(cls, created_by, subject, body, recipients, type_=None):
2000 def create(cls, created_by, subject, body, recipients, type_=None):
2001 if type_ is None:
2001 if type_ is None:
2002 type_ = Notification.TYPE_MESSAGE
2002 type_ = Notification.TYPE_MESSAGE
2003
2003
2004 notification = cls()
2004 notification = cls()
2005 notification.created_by_user = created_by
2005 notification.created_by_user = created_by
2006 notification.subject = subject
2006 notification.subject = subject
2007 notification.body = body
2007 notification.body = body
2008 notification.type_ = type_
2008 notification.type_ = type_
2009 notification.created_on = datetime.datetime.now()
2009 notification.created_on = datetime.datetime.now()
2010
2010
2011 for u in recipients:
2011 for u in recipients:
2012 assoc = UserNotification()
2012 assoc = UserNotification()
2013 assoc.notification = notification
2013 assoc.notification = notification
2014 u.notifications.append(assoc)
2014 u.notifications.append(assoc)
2015 Session().add(notification)
2015 Session().add(notification)
2016 return notification
2016 return notification
2017
2017
2018 @property
2018 @property
2019 def description(self):
2019 def description(self):
2020 from rhodecode.model.notification import NotificationModel
2020 from rhodecode.model.notification import NotificationModel
2021 return NotificationModel().make_description(self)
2021 return NotificationModel().make_description(self)
2022
2022
2023
2023
2024 class UserNotification(Base, BaseModel):
2024 class UserNotification(Base, BaseModel):
2025 __tablename__ = 'user_to_notification'
2025 __tablename__ = 'user_to_notification'
2026 __table_args__ = (
2026 __table_args__ = (
2027 UniqueConstraint('user_id', 'notification_id'),
2027 UniqueConstraint('user_id', 'notification_id'),
2028 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2028 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2029 'mysql_charset': 'utf8'}
2029 'mysql_charset': 'utf8'}
2030 )
2030 )
2031 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2031 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2032 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2032 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2033 read = Column('read', Boolean, default=False)
2033 read = Column('read', Boolean, default=False)
2034 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2034 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2035
2035
2036 user = relationship('User', lazy="joined")
2036 user = relationship('User', lazy="joined")
2037 notification = relationship('Notification', lazy="joined",
2037 notification = relationship('Notification', lazy="joined",
2038 order_by=lambda: Notification.created_on.desc(),)
2038 order_by=lambda: Notification.created_on.desc(),)
2039
2039
2040 def mark_as_read(self):
2040 def mark_as_read(self):
2041 self.read = True
2041 self.read = True
2042 Session().add(self)
2042 Session().add(self)
2043
2043
2044
2044
2045 class DbMigrateVersion(Base, BaseModel):
2045 class DbMigrateVersion(Base, BaseModel):
2046 __tablename__ = 'db_migrate_version'
2046 __tablename__ = 'db_migrate_version'
2047 __table_args__ = (
2047 __table_args__ = (
2048 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2048 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2049 'mysql_charset': 'utf8'},
2049 'mysql_charset': 'utf8'},
2050 )
2050 )
2051 repository_id = Column('repository_id', String(250), primary_key=True)
2051 repository_id = Column('repository_id', String(250), primary_key=True)
2052 repository_path = Column('repository_path', Text)
2052 repository_path = Column('repository_path', Text)
2053 version = Column('version', Integer)
2053 version = Column('version', Integer)
General Comments 0
You need to be logged in to leave comments. Login now