##// END OF EJS Templates
breadcrumbs: make repo names link to summary pages
Mads Kiilerich -
r3599:80877319 beta
parent child Browse files
Show More
@@ -1,1214 +1,1210 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13 import urlparse
14 import textwrap
14 import textwrap
15
15
16 from datetime import datetime
16 from datetime import datetime
17 from pygments.formatters.html import HtmlFormatter
17 from pygments.formatters.html import HtmlFormatter
18 from pygments import highlight as code_highlight
18 from pygments import highlight as code_highlight
19 from pylons import url, request, config
19 from pylons import url, request, config
20 from pylons.i18n.translation import _, ungettext
20 from pylons.i18n.translation import _, ungettext
21 from hashlib import md5
21 from hashlib import md5
22
22
23 from webhelpers.html import literal, HTML, escape
23 from webhelpers.html import literal, HTML, escape
24 from webhelpers.html.tools import *
24 from webhelpers.html.tools import *
25 from webhelpers.html.builder import make_tag
25 from webhelpers.html.builder import make_tag
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
27 end_form, file, form, hidden, image, javascript_link, link_to, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
29 submit, text, password, textarea, title, ul, xml_declaration, radio
29 submit, text, password, textarea, title, ul, xml_declaration, radio
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 from webhelpers.html.tools import auto_link, button_to, highlight, \
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
32 from webhelpers.number import format_byte_size, format_bit_size
32 from webhelpers.number import format_byte_size, format_bit_size
33 from webhelpers.pylonslib import Flash as _Flash
33 from webhelpers.pylonslib import Flash as _Flash
34 from webhelpers.pylonslib.secure_form import secure_form
34 from webhelpers.pylonslib.secure_form import secure_form
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
37 replace_whitespace, urlify, truncate, wrap_paragraphs
37 replace_whitespace, urlify, truncate, wrap_paragraphs
38 from webhelpers.date import time_ago_in_words
38 from webhelpers.date import time_ago_in_words
39 from webhelpers.paginate import Page
39 from webhelpers.paginate import Page
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 convert_boolean_attrs, NotGiven, _make_safe_id_component
42
42
43 from rhodecode.lib.annotate import annotate_highlight
43 from rhodecode.lib.annotate import annotate_highlight
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
44 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
46 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict,\
47 safe_int
47 safe_int
48 from rhodecode.lib.markup_renderer import MarkupRenderer
48 from rhodecode.lib.markup_renderer import MarkupRenderer
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
49 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
50 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
51 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
52 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.changeset_status import ChangesetStatusModel
53 from rhodecode.model.db import URL_SEP, Permission
53 from rhodecode.model.db import URL_SEP, Permission
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 html_escape_table = {
58 html_escape_table = {
59 "&": "&",
59 "&": "&",
60 '"': """,
60 '"': """,
61 "'": "'",
61 "'": "'",
62 ">": ">",
62 ">": ">",
63 "<": "&lt;",
63 "<": "&lt;",
64 }
64 }
65
65
66
66
67 def html_escape(text):
67 def html_escape(text):
68 """Produce entities within text."""
68 """Produce entities within text."""
69 return "".join(html_escape_table.get(c, c) for c in text)
69 return "".join(html_escape_table.get(c, c) for c in text)
70
70
71
71
72 def shorter(text, size=20):
72 def shorter(text, size=20):
73 postfix = '...'
73 postfix = '...'
74 if len(text) > size:
74 if len(text) > size:
75 return text[:size - len(postfix)] + postfix
75 return text[:size - len(postfix)] + postfix
76 return text
76 return text
77
77
78
78
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
79 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
80 """
80 """
81 Reset button
81 Reset button
82 """
82 """
83 _set_input_attrs(attrs, type, name, value)
83 _set_input_attrs(attrs, type, name, value)
84 _set_id_attr(attrs, id, name)
84 _set_id_attr(attrs, id, name)
85 convert_boolean_attrs(attrs, ["disabled"])
85 convert_boolean_attrs(attrs, ["disabled"])
86 return HTML.input(**attrs)
86 return HTML.input(**attrs)
87
87
88 reset = _reset
88 reset = _reset
89 safeid = _make_safe_id_component
89 safeid = _make_safe_id_component
90
90
91
91
92 def FID(raw_id, path):
92 def FID(raw_id, path):
93 """
93 """
94 Creates a uniqe ID for filenode based on it's hash of path and revision
94 Creates a uniqe ID for filenode based on it's hash of path and revision
95 it's safe to use in urls
95 it's safe to use in urls
96
96
97 :param raw_id:
97 :param raw_id:
98 :param path:
98 :param path:
99 """
99 """
100
100
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
101 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
102
102
103
103
104 def get_token():
104 def get_token():
105 """Return the current authentication token, creating one if one doesn't
105 """Return the current authentication token, creating one if one doesn't
106 already exist.
106 already exist.
107 """
107 """
108 token_key = "_authentication_token"
108 token_key = "_authentication_token"
109 from pylons import session
109 from pylons import session
110 if not token_key in session:
110 if not token_key in session:
111 try:
111 try:
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
112 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
113 except AttributeError: # Python < 2.4
113 except AttributeError: # Python < 2.4
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
114 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
115 session[token_key] = token
115 session[token_key] = token
116 if hasattr(session, 'save'):
116 if hasattr(session, 'save'):
117 session.save()
117 session.save()
118 return session[token_key]
118 return session[token_key]
119
119
120
120
121 class _GetError(object):
121 class _GetError(object):
122 """Get error from form_errors, and represent it as span wrapped error
122 """Get error from form_errors, and represent it as span wrapped error
123 message
123 message
124
124
125 :param field_name: field to fetch errors for
125 :param field_name: field to fetch errors for
126 :param form_errors: form errors dict
126 :param form_errors: form errors dict
127 """
127 """
128
128
129 def __call__(self, field_name, form_errors):
129 def __call__(self, field_name, form_errors):
130 tmpl = """<span class="error_msg">%s</span>"""
130 tmpl = """<span class="error_msg">%s</span>"""
131 if form_errors and field_name in form_errors:
131 if form_errors and field_name in form_errors:
132 return literal(tmpl % form_errors.get(field_name))
132 return literal(tmpl % form_errors.get(field_name))
133
133
134 get_error = _GetError()
134 get_error = _GetError()
135
135
136
136
137 class _ToolTip(object):
137 class _ToolTip(object):
138
138
139 def __call__(self, tooltip_title, trim_at=50):
139 def __call__(self, tooltip_title, trim_at=50):
140 """
140 """
141 Special function just to wrap our text into nice formatted
141 Special function just to wrap our text into nice formatted
142 autowrapped text
142 autowrapped text
143
143
144 :param tooltip_title:
144 :param tooltip_title:
145 """
145 """
146 tooltip_title = escape(tooltip_title)
146 tooltip_title = escape(tooltip_title)
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
147 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
148 return tooltip_title
148 return tooltip_title
149 tooltip = _ToolTip()
149 tooltip = _ToolTip()
150
150
151
151
152 class _FilesBreadCrumbs(object):
152 class _FilesBreadCrumbs(object):
153
153
154 def __call__(self, repo_name, rev, paths):
154 def __call__(self, repo_name, rev, paths):
155 if isinstance(paths, str):
155 if isinstance(paths, str):
156 paths = safe_unicode(paths)
156 paths = safe_unicode(paths)
157 url_l = [link_to(repo_name, url('files_home',
157 url_l = [link_to(repo_name, url('files_home',
158 repo_name=repo_name,
158 repo_name=repo_name,
159 revision=rev, f_path=''),
159 revision=rev, f_path=''),
160 class_='ypjax-link')]
160 class_='ypjax-link')]
161 paths_l = paths.split('/')
161 paths_l = paths.split('/')
162 for cnt, p in enumerate(paths_l):
162 for cnt, p in enumerate(paths_l):
163 if p != '':
163 if p != '':
164 url_l.append(link_to(p,
164 url_l.append(link_to(p,
165 url('files_home',
165 url('files_home',
166 repo_name=repo_name,
166 repo_name=repo_name,
167 revision=rev,
167 revision=rev,
168 f_path='/'.join(paths_l[:cnt + 1])
168 f_path='/'.join(paths_l[:cnt + 1])
169 ),
169 ),
170 class_='ypjax-link'
170 class_='ypjax-link'
171 )
171 )
172 )
172 )
173
173
174 return literal('/'.join(url_l))
174 return literal('/'.join(url_l))
175
175
176 files_breadcrumbs = _FilesBreadCrumbs()
176 files_breadcrumbs = _FilesBreadCrumbs()
177
177
178
178
179 class CodeHtmlFormatter(HtmlFormatter):
179 class CodeHtmlFormatter(HtmlFormatter):
180 """
180 """
181 My code Html Formatter for source codes
181 My code Html Formatter for source codes
182 """
182 """
183
183
184 def wrap(self, source, outfile):
184 def wrap(self, source, outfile):
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
185 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
186
186
187 def _wrap_code(self, source):
187 def _wrap_code(self, source):
188 for cnt, it in enumerate(source):
188 for cnt, it in enumerate(source):
189 i, t = it
189 i, t = it
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
190 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
191 yield i, t
191 yield i, t
192
192
193 def _wrap_tablelinenos(self, inner):
193 def _wrap_tablelinenos(self, inner):
194 dummyoutfile = StringIO.StringIO()
194 dummyoutfile = StringIO.StringIO()
195 lncount = 0
195 lncount = 0
196 for t, line in inner:
196 for t, line in inner:
197 if t:
197 if t:
198 lncount += 1
198 lncount += 1
199 dummyoutfile.write(line)
199 dummyoutfile.write(line)
200
200
201 fl = self.linenostart
201 fl = self.linenostart
202 mw = len(str(lncount + fl - 1))
202 mw = len(str(lncount + fl - 1))
203 sp = self.linenospecial
203 sp = self.linenospecial
204 st = self.linenostep
204 st = self.linenostep
205 la = self.lineanchors
205 la = self.lineanchors
206 aln = self.anchorlinenos
206 aln = self.anchorlinenos
207 nocls = self.noclasses
207 nocls = self.noclasses
208 if sp:
208 if sp:
209 lines = []
209 lines = []
210
210
211 for i in range(fl, fl + lncount):
211 for i in range(fl, fl + lncount):
212 if i % st == 0:
212 if i % st == 0:
213 if i % sp == 0:
213 if i % sp == 0:
214 if aln:
214 if aln:
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
215 lines.append('<a href="#%s%d" class="special">%*d</a>' %
216 (la, i, mw, i))
216 (la, i, mw, i))
217 else:
217 else:
218 lines.append('<span class="special">%*d</span>' % (mw, i))
218 lines.append('<span class="special">%*d</span>' % (mw, i))
219 else:
219 else:
220 if aln:
220 if aln:
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
221 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
222 else:
222 else:
223 lines.append('%*d' % (mw, i))
223 lines.append('%*d' % (mw, i))
224 else:
224 else:
225 lines.append('')
225 lines.append('')
226 ls = '\n'.join(lines)
226 ls = '\n'.join(lines)
227 else:
227 else:
228 lines = []
228 lines = []
229 for i in range(fl, fl + lncount):
229 for i in range(fl, fl + lncount):
230 if i % st == 0:
230 if i % st == 0:
231 if aln:
231 if aln:
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
232 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
233 else:
233 else:
234 lines.append('%*d' % (mw, i))
234 lines.append('%*d' % (mw, i))
235 else:
235 else:
236 lines.append('')
236 lines.append('')
237 ls = '\n'.join(lines)
237 ls = '\n'.join(lines)
238
238
239 # in case you wonder about the seemingly redundant <div> here: since the
239 # in case you wonder about the seemingly redundant <div> here: since the
240 # content in the other cell also is wrapped in a div, some browsers in
240 # content in the other cell also is wrapped in a div, some browsers in
241 # some configurations seem to mess up the formatting...
241 # some configurations seem to mess up the formatting...
242 if nocls:
242 if nocls:
243 yield 0, ('<table class="%stable">' % self.cssclass +
243 yield 0, ('<table class="%stable">' % self.cssclass +
244 '<tr><td><div class="linenodiv" '
244 '<tr><td><div class="linenodiv" '
245 'style="background-color: #f0f0f0; padding-right: 10px">'
245 'style="background-color: #f0f0f0; padding-right: 10px">'
246 '<pre style="line-height: 125%">' +
246 '<pre style="line-height: 125%">' +
247 ls + '</pre></div></td><td id="hlcode" class="code">')
247 ls + '</pre></div></td><td id="hlcode" class="code">')
248 else:
248 else:
249 yield 0, ('<table class="%stable">' % self.cssclass +
249 yield 0, ('<table class="%stable">' % self.cssclass +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
250 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
251 ls + '</pre></div></td><td id="hlcode" class="code">')
251 ls + '</pre></div></td><td id="hlcode" class="code">')
252 yield 0, dummyoutfile.getvalue()
252 yield 0, dummyoutfile.getvalue()
253 yield 0, '</td></tr></table>'
253 yield 0, '</td></tr></table>'
254
254
255
255
256 def pygmentize(filenode, **kwargs):
256 def pygmentize(filenode, **kwargs):
257 """
257 """
258 pygmentize function using pygments
258 pygmentize function using pygments
259
259
260 :param filenode:
260 :param filenode:
261 """
261 """
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
262 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
263 return literal(code_highlight(filenode.content, lexer,
263 return literal(code_highlight(filenode.content, lexer,
264 CodeHtmlFormatter(**kwargs)))
264 CodeHtmlFormatter(**kwargs)))
265
265
266
266
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
267 def pygmentize_annotation(repo_name, filenode, **kwargs):
268 """
268 """
269 pygmentize function for annotation
269 pygmentize function for annotation
270
270
271 :param filenode:
271 :param filenode:
272 """
272 """
273
273
274 color_dict = {}
274 color_dict = {}
275
275
276 def gen_color(n=10000):
276 def gen_color(n=10000):
277 """generator for getting n of evenly distributed colors using
277 """generator for getting n of evenly distributed colors using
278 hsv color and golden ratio. It always return same order of colors
278 hsv color and golden ratio. It always return same order of colors
279
279
280 :returns: RGB tuple
280 :returns: RGB tuple
281 """
281 """
282
282
283 def hsv_to_rgb(h, s, v):
283 def hsv_to_rgb(h, s, v):
284 if s == 0.0:
284 if s == 0.0:
285 return v, v, v
285 return v, v, v
286 i = int(h * 6.0) # XXX assume int() truncates!
286 i = int(h * 6.0) # XXX assume int() truncates!
287 f = (h * 6.0) - i
287 f = (h * 6.0) - i
288 p = v * (1.0 - s)
288 p = v * (1.0 - s)
289 q = v * (1.0 - s * f)
289 q = v * (1.0 - s * f)
290 t = v * (1.0 - s * (1.0 - f))
290 t = v * (1.0 - s * (1.0 - f))
291 i = i % 6
291 i = i % 6
292 if i == 0:
292 if i == 0:
293 return v, t, p
293 return v, t, p
294 if i == 1:
294 if i == 1:
295 return q, v, p
295 return q, v, p
296 if i == 2:
296 if i == 2:
297 return p, v, t
297 return p, v, t
298 if i == 3:
298 if i == 3:
299 return p, q, v
299 return p, q, v
300 if i == 4:
300 if i == 4:
301 return t, p, v
301 return t, p, v
302 if i == 5:
302 if i == 5:
303 return v, p, q
303 return v, p, q
304
304
305 golden_ratio = 0.618033988749895
305 golden_ratio = 0.618033988749895
306 h = 0.22717784590367374
306 h = 0.22717784590367374
307
307
308 for _ in xrange(n):
308 for _ in xrange(n):
309 h += golden_ratio
309 h += golden_ratio
310 h %= 1
310 h %= 1
311 HSV_tuple = [h, 0.95, 0.95]
311 HSV_tuple = [h, 0.95, 0.95]
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
312 RGB_tuple = hsv_to_rgb(*HSV_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
313 yield map(lambda x: str(int(x * 256)), RGB_tuple)
314
314
315 cgenerator = gen_color()
315 cgenerator = gen_color()
316
316
317 def get_color_string(cs):
317 def get_color_string(cs):
318 if cs in color_dict:
318 if cs in color_dict:
319 col = color_dict[cs]
319 col = color_dict[cs]
320 else:
320 else:
321 col = color_dict[cs] = cgenerator.next()
321 col = color_dict[cs] = cgenerator.next()
322 return "color: rgb(%s)! important;" % (', '.join(col))
322 return "color: rgb(%s)! important;" % (', '.join(col))
323
323
324 def url_func(repo_name):
324 def url_func(repo_name):
325
325
326 def _url_func(changeset):
326 def _url_func(changeset):
327 author = changeset.author
327 author = changeset.author
328 date = changeset.date
328 date = changeset.date
329 message = tooltip(changeset.message)
329 message = tooltip(changeset.message)
330
330
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
331 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
333 "</b> %s<br/></div>")
333 "</b> %s<br/></div>")
334
334
335 tooltip_html = tooltip_html % (author, date, message)
335 tooltip_html = tooltip_html % (author, date, message)
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
337 short_id(changeset.raw_id))
337 short_id(changeset.raw_id))
338 uri = link_to(
338 uri = link_to(
339 lnk_format,
339 lnk_format,
340 url('changeset_home', repo_name=repo_name,
340 url('changeset_home', repo_name=repo_name,
341 revision=changeset.raw_id),
341 revision=changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
343 class_='tooltip',
343 class_='tooltip',
344 title=tooltip_html
344 title=tooltip_html
345 )
345 )
346
346
347 uri += '\n'
347 uri += '\n'
348 return uri
348 return uri
349 return _url_func
349 return _url_func
350
350
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
351 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
352
352
353
353
354 def is_following_repo(repo_name, user_id):
354 def is_following_repo(repo_name, user_id):
355 from rhodecode.model.scm import ScmModel
355 from rhodecode.model.scm import ScmModel
356 return ScmModel().is_following_repo(repo_name, user_id)
356 return ScmModel().is_following_repo(repo_name, user_id)
357
357
358 flash = _Flash()
358 flash = _Flash()
359
359
360 #==============================================================================
360 #==============================================================================
361 # SCM FILTERS available via h.
361 # SCM FILTERS available via h.
362 #==============================================================================
362 #==============================================================================
363 from rhodecode.lib.vcs.utils import author_name, author_email
363 from rhodecode.lib.vcs.utils import author_name, author_email
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
364 from rhodecode.lib.utils2 import credentials_filter, age as _age
365 from rhodecode.model.db import User, ChangesetStatus
365 from rhodecode.model.db import User, ChangesetStatus
366
366
367 age = lambda x, y=False: _age(x, y)
367 age = lambda x, y=False: _age(x, y)
368 capitalize = lambda x: x.capitalize()
368 capitalize = lambda x: x.capitalize()
369 email = author_email
369 email = author_email
370 short_id = lambda x: x[:12]
370 short_id = lambda x: x[:12]
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
371 hide_credentials = lambda x: ''.join(credentials_filter(x))
372
372
373
373
374 def show_id(cs):
374 def show_id(cs):
375 """
375 """
376 Configurable function that shows ID
376 Configurable function that shows ID
377 by default it's r123:fffeeefffeee
377 by default it's r123:fffeeefffeee
378
378
379 :param cs: changeset instance
379 :param cs: changeset instance
380 """
380 """
381 from rhodecode import CONFIG
381 from rhodecode import CONFIG
382 def_len = safe_int(CONFIG.get('sha_len', 12))
382 def_len = safe_int(CONFIG.get('sha_len', 12))
383 show_rev = str2bool(CONFIG.get('sha_show_numeric_rev', True))
383 show_rev = str2bool(CONFIG.get('sha_show_numeric_rev', True))
384
384
385 raw_id = cs.raw_id[:def_len]
385 raw_id = cs.raw_id[:def_len]
386 if show_rev:
386 if show_rev:
387 return 'r%s:%s' % (cs.revision, raw_id)
387 return 'r%s:%s' % (cs.revision, raw_id)
388 else:
388 else:
389 return '%s' % (raw_id)
389 return '%s' % (raw_id)
390
390
391
391
392 def fmt_date(date):
392 def fmt_date(date):
393 if date:
393 if date:
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
394 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
395 return date.strftime(_fmt).decode('utf8')
395 return date.strftime(_fmt).decode('utf8')
396
396
397 return ""
397 return ""
398
398
399
399
400 def is_git(repository):
400 def is_git(repository):
401 if hasattr(repository, 'alias'):
401 if hasattr(repository, 'alias'):
402 _type = repository.alias
402 _type = repository.alias
403 elif hasattr(repository, 'repo_type'):
403 elif hasattr(repository, 'repo_type'):
404 _type = repository.repo_type
404 _type = repository.repo_type
405 else:
405 else:
406 _type = repository
406 _type = repository
407 return _type == 'git'
407 return _type == 'git'
408
408
409
409
410 def is_hg(repository):
410 def is_hg(repository):
411 if hasattr(repository, 'alias'):
411 if hasattr(repository, 'alias'):
412 _type = repository.alias
412 _type = repository.alias
413 elif hasattr(repository, 'repo_type'):
413 elif hasattr(repository, 'repo_type'):
414 _type = repository.repo_type
414 _type = repository.repo_type
415 else:
415 else:
416 _type = repository
416 _type = repository
417 return _type == 'hg'
417 return _type == 'hg'
418
418
419
419
420 def email_or_none(author):
420 def email_or_none(author):
421 # extract email from the commit string
421 # extract email from the commit string
422 _email = email(author)
422 _email = email(author)
423 if _email != '':
423 if _email != '':
424 # check it against RhodeCode database, and use the MAIN email for this
424 # check it against RhodeCode database, and use the MAIN email for this
425 # user
425 # user
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 if user is not None:
427 if user is not None:
428 return user.email
428 return user.email
429 return _email
429 return _email
430
430
431 # See if it contains a username we can get an email from
431 # See if it contains a username we can get an email from
432 user = User.get_by_username(author_name(author), case_insensitive=True,
432 user = User.get_by_username(author_name(author), case_insensitive=True,
433 cache=True)
433 cache=True)
434 if user is not None:
434 if user is not None:
435 return user.email
435 return user.email
436
436
437 # No valid email, not a valid user in the system, none!
437 # No valid email, not a valid user in the system, none!
438 return None
438 return None
439
439
440
440
441 def person(author, show_attr="username_and_name"):
441 def person(author, show_attr="username_and_name"):
442 # attr to return from fetched user
442 # attr to return from fetched user
443 person_getter = lambda usr: getattr(usr, show_attr)
443 person_getter = lambda usr: getattr(usr, show_attr)
444
444
445 # Valid email in the attribute passed, see if they're in the system
445 # Valid email in the attribute passed, see if they're in the system
446 _email = email(author)
446 _email = email(author)
447 if _email != '':
447 if _email != '':
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
448 user = User.get_by_email(_email, case_insensitive=True, cache=True)
449 if user is not None:
449 if user is not None:
450 return person_getter(user)
450 return person_getter(user)
451 return _email
451 return _email
452
452
453 # Maybe it's a username?
453 # Maybe it's a username?
454 _author = author_name(author)
454 _author = author_name(author)
455 user = User.get_by_username(_author, case_insensitive=True,
455 user = User.get_by_username(_author, case_insensitive=True,
456 cache=True)
456 cache=True)
457 if user is not None:
457 if user is not None:
458 return person_getter(user)
458 return person_getter(user)
459
459
460 # Still nothing? Just pass back the author name then
460 # Still nothing? Just pass back the author name then
461 return _author
461 return _author
462
462
463
463
464 def person_by_id(id_, show_attr="username_and_name"):
464 def person_by_id(id_, show_attr="username_and_name"):
465 # attr to return from fetched user
465 # attr to return from fetched user
466 person_getter = lambda usr: getattr(usr, show_attr)
466 person_getter = lambda usr: getattr(usr, show_attr)
467
467
468 #maybe it's an ID ?
468 #maybe it's an ID ?
469 if str(id_).isdigit() or isinstance(id_, int):
469 if str(id_).isdigit() or isinstance(id_, int):
470 id_ = int(id_)
470 id_ = int(id_)
471 user = User.get(id_)
471 user = User.get(id_)
472 if user is not None:
472 if user is not None:
473 return person_getter(user)
473 return person_getter(user)
474 return id_
474 return id_
475
475
476
476
477 def desc_stylize(value):
477 def desc_stylize(value):
478 """
478 """
479 converts tags from value into html equivalent
479 converts tags from value into html equivalent
480
480
481 :param value:
481 :param value:
482 """
482 """
483 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
483 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
484 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
484 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
485 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
485 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
486 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
486 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
487 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
487 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
488 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
488 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
489 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
489 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
490 '<div class="metatag" tag="lang">\\2</div>', value)
490 '<div class="metatag" tag="lang">\\2</div>', value)
491 value = re.sub(r'\[([a-z]+)\]',
491 value = re.sub(r'\[([a-z]+)\]',
492 '<div class="metatag" tag="\\1">\\1</div>', value)
492 '<div class="metatag" tag="\\1">\\1</div>', value)
493
493
494 return value
494 return value
495
495
496
496
497 def bool2icon(value):
497 def bool2icon(value):
498 """Returns True/False values represented as small html image of true/false
498 """Returns True/False values represented as small html image of true/false
499 icons
499 icons
500
500
501 :param value: bool value
501 :param value: bool value
502 """
502 """
503
503
504 if value is True:
504 if value is True:
505 return HTML.tag('img', src=url("/images/icons/accept.png"),
505 return HTML.tag('img', src=url("/images/icons/accept.png"),
506 alt=_('True'))
506 alt=_('True'))
507
507
508 if value is False:
508 if value is False:
509 return HTML.tag('img', src=url("/images/icons/cancel.png"),
509 return HTML.tag('img', src=url("/images/icons/cancel.png"),
510 alt=_('False'))
510 alt=_('False'))
511
511
512 return value
512 return value
513
513
514
514
515 def action_parser(user_log, feed=False, parse_cs=False):
515 def action_parser(user_log, feed=False, parse_cs=False):
516 """
516 """
517 This helper will action_map the specified string action into translated
517 This helper will action_map the specified string action into translated
518 fancy names with icons and links
518 fancy names with icons and links
519
519
520 :param user_log: user log instance
520 :param user_log: user log instance
521 :param feed: use output for feeds (no html and fancy icons)
521 :param feed: use output for feeds (no html and fancy icons)
522 :param parse_cs: parse Changesets into VCS instances
522 :param parse_cs: parse Changesets into VCS instances
523 """
523 """
524
524
525 action = user_log.action
525 action = user_log.action
526 action_params = ' '
526 action_params = ' '
527
527
528 x = action.split(':')
528 x = action.split(':')
529
529
530 if len(x) > 1:
530 if len(x) > 1:
531 action, action_params = x
531 action, action_params = x
532
532
533 def get_cs_links():
533 def get_cs_links():
534 revs_limit = 3 # display this amount always
534 revs_limit = 3 # display this amount always
535 revs_top_limit = 50 # show upto this amount of changesets hidden
535 revs_top_limit = 50 # show upto this amount of changesets hidden
536 revs_ids = action_params.split(',')
536 revs_ids = action_params.split(',')
537 deleted = user_log.repository is None
537 deleted = user_log.repository is None
538 if deleted:
538 if deleted:
539 return ','.join(revs_ids)
539 return ','.join(revs_ids)
540
540
541 repo_name = user_log.repository.repo_name
541 repo_name = user_log.repository.repo_name
542
542
543 def lnk(rev, repo_name):
543 def lnk(rev, repo_name):
544 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
544 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
545 lazy_cs = True
545 lazy_cs = True
546 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
546 if getattr(rev, 'op', None) and getattr(rev, 'ref_name', None):
547 lazy_cs = False
547 lazy_cs = False
548 lbl = '?'
548 lbl = '?'
549 if rev.op == 'delete_branch':
549 if rev.op == 'delete_branch':
550 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
550 lbl = '%s' % _('Deleted branch: %s') % rev.ref_name
551 title = ''
551 title = ''
552 elif rev.op == 'tag':
552 elif rev.op == 'tag':
553 lbl = '%s' % _('Created tag: %s') % rev.ref_name
553 lbl = '%s' % _('Created tag: %s') % rev.ref_name
554 title = ''
554 title = ''
555 _url = '#'
555 _url = '#'
556
556
557 else:
557 else:
558 lbl = '%s' % (rev.short_id[:8])
558 lbl = '%s' % (rev.short_id[:8])
559 _url = url('changeset_home', repo_name=repo_name,
559 _url = url('changeset_home', repo_name=repo_name,
560 revision=rev.raw_id)
560 revision=rev.raw_id)
561 title = tooltip(rev.message)
561 title = tooltip(rev.message)
562 else:
562 else:
563 ## changeset cannot be found/striped/removed etc.
563 ## changeset cannot be found/striped/removed etc.
564 lbl = ('%s' % rev)[:12]
564 lbl = ('%s' % rev)[:12]
565 _url = '#'
565 _url = '#'
566 title = _('Changeset not found')
566 title = _('Changeset not found')
567 if parse_cs:
567 if parse_cs:
568 return link_to(lbl, _url, title=title, class_='tooltip')
568 return link_to(lbl, _url, title=title, class_='tooltip')
569 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
569 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
570 class_='lazy-cs' if lazy_cs else '')
570 class_='lazy-cs' if lazy_cs else '')
571
571
572 def _get_op(rev_txt):
572 def _get_op(rev_txt):
573 _op = None
573 _op = None
574 _name = rev_txt
574 _name = rev_txt
575 if len(rev_txt.split('=>')) == 2:
575 if len(rev_txt.split('=>')) == 2:
576 _op, _name = rev_txt.split('=>')
576 _op, _name = rev_txt.split('=>')
577 return _op, _name
577 return _op, _name
578
578
579 revs = []
579 revs = []
580 if len(filter(lambda v: v != '', revs_ids)) > 0:
580 if len(filter(lambda v: v != '', revs_ids)) > 0:
581 repo = None
581 repo = None
582 for rev in revs_ids[:revs_top_limit]:
582 for rev in revs_ids[:revs_top_limit]:
583 _op, _name = _get_op(rev)
583 _op, _name = _get_op(rev)
584
584
585 # we want parsed changesets, or new log store format is bad
585 # we want parsed changesets, or new log store format is bad
586 if parse_cs:
586 if parse_cs:
587 try:
587 try:
588 if repo is None:
588 if repo is None:
589 repo = user_log.repository.scm_instance
589 repo = user_log.repository.scm_instance
590 _rev = repo.get_changeset(rev)
590 _rev = repo.get_changeset(rev)
591 revs.append(_rev)
591 revs.append(_rev)
592 except ChangesetDoesNotExistError:
592 except ChangesetDoesNotExistError:
593 log.error('cannot find revision %s in this repo' % rev)
593 log.error('cannot find revision %s in this repo' % rev)
594 revs.append(rev)
594 revs.append(rev)
595 continue
595 continue
596 else:
596 else:
597 _rev = AttributeDict({
597 _rev = AttributeDict({
598 'short_id': rev[:12],
598 'short_id': rev[:12],
599 'raw_id': rev,
599 'raw_id': rev,
600 'message': '',
600 'message': '',
601 'op': _op,
601 'op': _op,
602 'ref_name': _name
602 'ref_name': _name
603 })
603 })
604 revs.append(_rev)
604 revs.append(_rev)
605 cs_links = []
605 cs_links = []
606 cs_links.append(" " + ', '.join(
606 cs_links.append(" " + ', '.join(
607 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
607 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
608 )
608 )
609 )
609 )
610 _op1, _name1 = _get_op(revs_ids[0])
610 _op1, _name1 = _get_op(revs_ids[0])
611 _op2, _name2 = _get_op(revs_ids[-1])
611 _op2, _name2 = _get_op(revs_ids[-1])
612
612
613 _rev = '%s...%s' % (_name1, _name2)
613 _rev = '%s...%s' % (_name1, _name2)
614
614
615 compare_view = (
615 compare_view = (
616 ' <div class="compare_view tooltip" title="%s">'
616 ' <div class="compare_view tooltip" title="%s">'
617 '<a href="%s">%s</a> </div>' % (
617 '<a href="%s">%s</a> </div>' % (
618 _('Show all combined changesets %s->%s') % (
618 _('Show all combined changesets %s->%s') % (
619 revs_ids[0][:12], revs_ids[-1][:12]
619 revs_ids[0][:12], revs_ids[-1][:12]
620 ),
620 ),
621 url('changeset_home', repo_name=repo_name,
621 url('changeset_home', repo_name=repo_name,
622 revision=_rev
622 revision=_rev
623 ),
623 ),
624 _('compare view')
624 _('compare view')
625 )
625 )
626 )
626 )
627
627
628 # if we have exactly one more than normally displayed
628 # if we have exactly one more than normally displayed
629 # just display it, takes less space than displaying
629 # just display it, takes less space than displaying
630 # "and 1 more revisions"
630 # "and 1 more revisions"
631 if len(revs_ids) == revs_limit + 1:
631 if len(revs_ids) == revs_limit + 1:
632 rev = revs[revs_limit]
632 rev = revs[revs_limit]
633 cs_links.append(", " + lnk(rev, repo_name))
633 cs_links.append(", " + lnk(rev, repo_name))
634
634
635 # hidden-by-default ones
635 # hidden-by-default ones
636 if len(revs_ids) > revs_limit + 1:
636 if len(revs_ids) > revs_limit + 1:
637 uniq_id = revs_ids[0]
637 uniq_id = revs_ids[0]
638 html_tmpl = (
638 html_tmpl = (
639 '<span> %s <a class="show_more" id="_%s" '
639 '<span> %s <a class="show_more" id="_%s" '
640 'href="#more">%s</a> %s</span>'
640 'href="#more">%s</a> %s</span>'
641 )
641 )
642 if not feed:
642 if not feed:
643 cs_links.append(html_tmpl % (
643 cs_links.append(html_tmpl % (
644 _('and'),
644 _('and'),
645 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
645 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
646 _('revisions')
646 _('revisions')
647 )
647 )
648 )
648 )
649
649
650 if not feed:
650 if not feed:
651 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
651 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
652 else:
652 else:
653 html_tmpl = '<span id="%s"> %s </span>'
653 html_tmpl = '<span id="%s"> %s </span>'
654
654
655 morelinks = ', '.join(
655 morelinks = ', '.join(
656 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
656 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
657 )
657 )
658
658
659 if len(revs_ids) > revs_top_limit:
659 if len(revs_ids) > revs_top_limit:
660 morelinks += ', ...'
660 morelinks += ', ...'
661
661
662 cs_links.append(html_tmpl % (uniq_id, morelinks))
662 cs_links.append(html_tmpl % (uniq_id, morelinks))
663 if len(revs) > 1:
663 if len(revs) > 1:
664 cs_links.append(compare_view)
664 cs_links.append(compare_view)
665 return ''.join(cs_links)
665 return ''.join(cs_links)
666
666
667 def get_fork_name():
667 def get_fork_name():
668 repo_name = action_params
668 repo_name = action_params
669 _url = url('summary_home', repo_name=repo_name)
669 _url = url('summary_home', repo_name=repo_name)
670 return _('fork name %s') % link_to(action_params, _url)
670 return _('fork name %s') % link_to(action_params, _url)
671
671
672 def get_user_name():
672 def get_user_name():
673 user_name = action_params
673 user_name = action_params
674 return user_name
674 return user_name
675
675
676 def get_users_group():
676 def get_users_group():
677 group_name = action_params
677 group_name = action_params
678 return group_name
678 return group_name
679
679
680 def get_pull_request():
680 def get_pull_request():
681 pull_request_id = action_params
681 pull_request_id = action_params
682 deleted = user_log.repository is None
682 deleted = user_log.repository is None
683 if deleted:
683 if deleted:
684 repo_name = user_log.repository_name
684 repo_name = user_log.repository_name
685 else:
685 else:
686 repo_name = user_log.repository.repo_name
686 repo_name = user_log.repository.repo_name
687 return link_to(_('Pull request #%s') % pull_request_id,
687 return link_to(_('Pull request #%s') % pull_request_id,
688 url('pullrequest_show', repo_name=repo_name,
688 url('pullrequest_show', repo_name=repo_name,
689 pull_request_id=pull_request_id))
689 pull_request_id=pull_request_id))
690
690
691 # action : translated str, callback(extractor), icon
691 # action : translated str, callback(extractor), icon
692 action_map = {
692 action_map = {
693 'user_deleted_repo': (_('[deleted] repository'),
693 'user_deleted_repo': (_('[deleted] repository'),
694 None, 'database_delete.png'),
694 None, 'database_delete.png'),
695 'user_created_repo': (_('[created] repository'),
695 'user_created_repo': (_('[created] repository'),
696 None, 'database_add.png'),
696 None, 'database_add.png'),
697 'user_created_fork': (_('[created] repository as fork'),
697 'user_created_fork': (_('[created] repository as fork'),
698 None, 'arrow_divide.png'),
698 None, 'arrow_divide.png'),
699 'user_forked_repo': (_('[forked] repository'),
699 'user_forked_repo': (_('[forked] repository'),
700 get_fork_name, 'arrow_divide.png'),
700 get_fork_name, 'arrow_divide.png'),
701 'user_updated_repo': (_('[updated] repository'),
701 'user_updated_repo': (_('[updated] repository'),
702 None, 'database_edit.png'),
702 None, 'database_edit.png'),
703 'admin_deleted_repo': (_('[delete] repository'),
703 'admin_deleted_repo': (_('[delete] repository'),
704 None, 'database_delete.png'),
704 None, 'database_delete.png'),
705 'admin_created_repo': (_('[created] repository'),
705 'admin_created_repo': (_('[created] repository'),
706 None, 'database_add.png'),
706 None, 'database_add.png'),
707 'admin_forked_repo': (_('[forked] repository'),
707 'admin_forked_repo': (_('[forked] repository'),
708 None, 'arrow_divide.png'),
708 None, 'arrow_divide.png'),
709 'admin_updated_repo': (_('[updated] repository'),
709 'admin_updated_repo': (_('[updated] repository'),
710 None, 'database_edit.png'),
710 None, 'database_edit.png'),
711 'admin_created_user': (_('[created] user'),
711 'admin_created_user': (_('[created] user'),
712 get_user_name, 'user_add.png'),
712 get_user_name, 'user_add.png'),
713 'admin_updated_user': (_('[updated] user'),
713 'admin_updated_user': (_('[updated] user'),
714 get_user_name, 'user_edit.png'),
714 get_user_name, 'user_edit.png'),
715 'admin_created_users_group': (_('[created] user group'),
715 'admin_created_users_group': (_('[created] user group'),
716 get_users_group, 'group_add.png'),
716 get_users_group, 'group_add.png'),
717 'admin_updated_users_group': (_('[updated] user group'),
717 'admin_updated_users_group': (_('[updated] user group'),
718 get_users_group, 'group_edit.png'),
718 get_users_group, 'group_edit.png'),
719 'user_commented_revision': (_('[commented] on revision in repository'),
719 'user_commented_revision': (_('[commented] on revision in repository'),
720 get_cs_links, 'comment_add.png'),
720 get_cs_links, 'comment_add.png'),
721 'user_commented_pull_request': (_('[commented] on pull request for'),
721 'user_commented_pull_request': (_('[commented] on pull request for'),
722 get_pull_request, 'comment_add.png'),
722 get_pull_request, 'comment_add.png'),
723 'user_closed_pull_request': (_('[closed] pull request for'),
723 'user_closed_pull_request': (_('[closed] pull request for'),
724 get_pull_request, 'tick.png'),
724 get_pull_request, 'tick.png'),
725 'push': (_('[pushed] into'),
725 'push': (_('[pushed] into'),
726 get_cs_links, 'script_add.png'),
726 get_cs_links, 'script_add.png'),
727 'push_local': (_('[committed via RhodeCode] into repository'),
727 'push_local': (_('[committed via RhodeCode] into repository'),
728 get_cs_links, 'script_edit.png'),
728 get_cs_links, 'script_edit.png'),
729 'push_remote': (_('[pulled from remote] into repository'),
729 'push_remote': (_('[pulled from remote] into repository'),
730 get_cs_links, 'connect.png'),
730 get_cs_links, 'connect.png'),
731 'pull': (_('[pulled] from'),
731 'pull': (_('[pulled] from'),
732 None, 'down_16.png'),
732 None, 'down_16.png'),
733 'started_following_repo': (_('[started following] repository'),
733 'started_following_repo': (_('[started following] repository'),
734 None, 'heart_add.png'),
734 None, 'heart_add.png'),
735 'stopped_following_repo': (_('[stopped following] repository'),
735 'stopped_following_repo': (_('[stopped following] repository'),
736 None, 'heart_delete.png'),
736 None, 'heart_delete.png'),
737 }
737 }
738
738
739 action_str = action_map.get(action, action)
739 action_str = action_map.get(action, action)
740 if feed:
740 if feed:
741 action = action_str[0].replace('[', '').replace(']', '')
741 action = action_str[0].replace('[', '').replace(']', '')
742 else:
742 else:
743 action = action_str[0]\
743 action = action_str[0]\
744 .replace('[', '<span class="journal_highlight">')\
744 .replace('[', '<span class="journal_highlight">')\
745 .replace(']', '</span>')
745 .replace(']', '</span>')
746
746
747 action_params_func = lambda: ""
747 action_params_func = lambda: ""
748
748
749 if callable(action_str[1]):
749 if callable(action_str[1]):
750 action_params_func = action_str[1]
750 action_params_func = action_str[1]
751
751
752 def action_parser_icon():
752 def action_parser_icon():
753 action = user_log.action
753 action = user_log.action
754 action_params = None
754 action_params = None
755 x = action.split(':')
755 x = action.split(':')
756
756
757 if len(x) > 1:
757 if len(x) > 1:
758 action, action_params = x
758 action, action_params = x
759
759
760 tmpl = """<img src="%s%s" alt="%s"/>"""
760 tmpl = """<img src="%s%s" alt="%s"/>"""
761 ico = action_map.get(action, ['', '', ''])[2]
761 ico = action_map.get(action, ['', '', ''])[2]
762 return literal(tmpl % ((url('/images/icons/')), ico, action))
762 return literal(tmpl % ((url('/images/icons/')), ico, action))
763
763
764 # returned callbacks we need to call to get
764 # returned callbacks we need to call to get
765 return [lambda: literal(action), action_params_func, action_parser_icon]
765 return [lambda: literal(action), action_params_func, action_parser_icon]
766
766
767
767
768
768
769 #==============================================================================
769 #==============================================================================
770 # PERMS
770 # PERMS
771 #==============================================================================
771 #==============================================================================
772 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
772 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
773 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
773 HasRepoPermissionAny, HasRepoPermissionAll, HasReposGroupPermissionAll, \
774 HasReposGroupPermissionAny
774 HasReposGroupPermissionAny
775
775
776
776
777 #==============================================================================
777 #==============================================================================
778 # GRAVATAR URL
778 # GRAVATAR URL
779 #==============================================================================
779 #==============================================================================
780
780
781 def gravatar_url(email_address, size=30):
781 def gravatar_url(email_address, size=30):
782 from pylons import url # doh, we need to re-import url to mock it later
782 from pylons import url # doh, we need to re-import url to mock it later
783 _def = 'anonymous@rhodecode.org'
783 _def = 'anonymous@rhodecode.org'
784 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
784 use_gravatar = str2bool(config['app_conf'].get('use_gravatar'))
785 email_address = email_address or _def
785 email_address = email_address or _def
786 if (not use_gravatar or not email_address or email_address == _def):
786 if (not use_gravatar or not email_address or email_address == _def):
787 f = lambda a, l: min(l, key=lambda x: abs(x - a))
787 f = lambda a, l: min(l, key=lambda x: abs(x - a))
788 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
788 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
789
789
790 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
790 if use_gravatar and config['app_conf'].get('alternative_gravatar_url'):
791 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
791 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
792 parsed_url = urlparse.urlparse(url.current(qualified=True))
792 parsed_url = urlparse.urlparse(url.current(qualified=True))
793 tmpl = tmpl.replace('{email}', email_address)\
793 tmpl = tmpl.replace('{email}', email_address)\
794 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
794 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
795 .replace('{netloc}', parsed_url.netloc)\
795 .replace('{netloc}', parsed_url.netloc)\
796 .replace('{scheme}', parsed_url.scheme)\
796 .replace('{scheme}', parsed_url.scheme)\
797 .replace('{size}', str(size))
797 .replace('{size}', str(size))
798 return tmpl
798 return tmpl
799
799
800 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
800 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
801 default = 'identicon'
801 default = 'identicon'
802 baseurl_nossl = "http://www.gravatar.com/avatar/"
802 baseurl_nossl = "http://www.gravatar.com/avatar/"
803 baseurl_ssl = "https://secure.gravatar.com/avatar/"
803 baseurl_ssl = "https://secure.gravatar.com/avatar/"
804 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
804 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
805
805
806 if isinstance(email_address, unicode):
806 if isinstance(email_address, unicode):
807 #hashlib crashes on unicode items
807 #hashlib crashes on unicode items
808 email_address = safe_str(email_address)
808 email_address = safe_str(email_address)
809 # construct the url
809 # construct the url
810 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
810 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
811 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
811 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
812
812
813 return gravatar_url
813 return gravatar_url
814
814
815
815
816 #==============================================================================
816 #==============================================================================
817 # REPO PAGER, PAGER FOR REPOSITORY
817 # REPO PAGER, PAGER FOR REPOSITORY
818 #==============================================================================
818 #==============================================================================
819 class RepoPage(Page):
819 class RepoPage(Page):
820
820
821 def __init__(self, collection, page=1, items_per_page=20,
821 def __init__(self, collection, page=1, items_per_page=20,
822 item_count=None, url=None, **kwargs):
822 item_count=None, url=None, **kwargs):
823
823
824 """Create a "RepoPage" instance. special pager for paging
824 """Create a "RepoPage" instance. special pager for paging
825 repository
825 repository
826 """
826 """
827 self._url_generator = url
827 self._url_generator = url
828
828
829 # Safe the kwargs class-wide so they can be used in the pager() method
829 # Safe the kwargs class-wide so they can be used in the pager() method
830 self.kwargs = kwargs
830 self.kwargs = kwargs
831
831
832 # Save a reference to the collection
832 # Save a reference to the collection
833 self.original_collection = collection
833 self.original_collection = collection
834
834
835 self.collection = collection
835 self.collection = collection
836
836
837 # The self.page is the number of the current page.
837 # The self.page is the number of the current page.
838 # The first page has the number 1!
838 # The first page has the number 1!
839 try:
839 try:
840 self.page = int(page) # make it int() if we get it as a string
840 self.page = int(page) # make it int() if we get it as a string
841 except (ValueError, TypeError):
841 except (ValueError, TypeError):
842 self.page = 1
842 self.page = 1
843
843
844 self.items_per_page = items_per_page
844 self.items_per_page = items_per_page
845
845
846 # Unless the user tells us how many items the collections has
846 # Unless the user tells us how many items the collections has
847 # we calculate that ourselves.
847 # we calculate that ourselves.
848 if item_count is not None:
848 if item_count is not None:
849 self.item_count = item_count
849 self.item_count = item_count
850 else:
850 else:
851 self.item_count = len(self.collection)
851 self.item_count = len(self.collection)
852
852
853 # Compute the number of the first and last available page
853 # Compute the number of the first and last available page
854 if self.item_count > 0:
854 if self.item_count > 0:
855 self.first_page = 1
855 self.first_page = 1
856 self.page_count = int(math.ceil(float(self.item_count) /
856 self.page_count = int(math.ceil(float(self.item_count) /
857 self.items_per_page))
857 self.items_per_page))
858 self.last_page = self.first_page + self.page_count - 1
858 self.last_page = self.first_page + self.page_count - 1
859
859
860 # Make sure that the requested page number is the range of
860 # Make sure that the requested page number is the range of
861 # valid pages
861 # valid pages
862 if self.page > self.last_page:
862 if self.page > self.last_page:
863 self.page = self.last_page
863 self.page = self.last_page
864 elif self.page < self.first_page:
864 elif self.page < self.first_page:
865 self.page = self.first_page
865 self.page = self.first_page
866
866
867 # Note: the number of items on this page can be less than
867 # Note: the number of items on this page can be less than
868 # items_per_page if the last page is not full
868 # items_per_page if the last page is not full
869 self.first_item = max(0, (self.item_count) - (self.page *
869 self.first_item = max(0, (self.item_count) - (self.page *
870 items_per_page))
870 items_per_page))
871 self.last_item = ((self.item_count - 1) - items_per_page *
871 self.last_item = ((self.item_count - 1) - items_per_page *
872 (self.page - 1))
872 (self.page - 1))
873
873
874 self.items = list(self.collection[self.first_item:self.last_item + 1])
874 self.items = list(self.collection[self.first_item:self.last_item + 1])
875
875
876 # Links to previous and next page
876 # Links to previous and next page
877 if self.page > self.first_page:
877 if self.page > self.first_page:
878 self.previous_page = self.page - 1
878 self.previous_page = self.page - 1
879 else:
879 else:
880 self.previous_page = None
880 self.previous_page = None
881
881
882 if self.page < self.last_page:
882 if self.page < self.last_page:
883 self.next_page = self.page + 1
883 self.next_page = self.page + 1
884 else:
884 else:
885 self.next_page = None
885 self.next_page = None
886
886
887 # No items available
887 # No items available
888 else:
888 else:
889 self.first_page = None
889 self.first_page = None
890 self.page_count = 0
890 self.page_count = 0
891 self.last_page = None
891 self.last_page = None
892 self.first_item = None
892 self.first_item = None
893 self.last_item = None
893 self.last_item = None
894 self.previous_page = None
894 self.previous_page = None
895 self.next_page = None
895 self.next_page = None
896 self.items = []
896 self.items = []
897
897
898 # This is a subclass of the 'list' type. Initialise the list now.
898 # This is a subclass of the 'list' type. Initialise the list now.
899 list.__init__(self, reversed(self.items))
899 list.__init__(self, reversed(self.items))
900
900
901
901
902 def changed_tooltip(nodes):
902 def changed_tooltip(nodes):
903 """
903 """
904 Generates a html string for changed nodes in changeset page.
904 Generates a html string for changed nodes in changeset page.
905 It limits the output to 30 entries
905 It limits the output to 30 entries
906
906
907 :param nodes: LazyNodesGenerator
907 :param nodes: LazyNodesGenerator
908 """
908 """
909 if nodes:
909 if nodes:
910 pref = ': <br/> '
910 pref = ': <br/> '
911 suf = ''
911 suf = ''
912 if len(nodes) > 30:
912 if len(nodes) > 30:
913 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
913 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
914 return literal(pref + '<br/> '.join([safe_unicode(x.path)
914 return literal(pref + '<br/> '.join([safe_unicode(x.path)
915 for x in nodes[:30]]) + suf)
915 for x in nodes[:30]]) + suf)
916 else:
916 else:
917 return ': ' + _('No Files')
917 return ': ' + _('No Files')
918
918
919
919
920 def repo_link(groups_and_repos, last_url=None):
920 def repo_link(groups_and_repos):
921 """
921 """
922 Makes a breadcrumbs link to repo within a group
922 Makes a breadcrumbs link to repo within a group
923 joins &raquo; on each group to create a fancy link
923 joins &raquo; on each group to create a fancy link
924
924
925 ex::
925 ex::
926 group >> subgroup >> repo
926 group >> subgroup >> repo
927
927
928 :param groups_and_repos:
928 :param groups_and_repos:
929 :param last_url:
929 :param last_url:
930 """
930 """
931 groups, repo_name = groups_and_repos
931 groups, just_name, repo_name = groups_and_repos
932 last_link = link_to(repo_name, last_url) if last_url else repo_name
932 last_url = url('summary_home', repo_name=repo_name)
933 last_link = link_to(just_name, last_url)
933
934
934 if not groups:
935 def make_link(group):
935 if last_url:
936 return link_to(group.name,
936 return literal('<span>%s</span>' % last_link)
937 url('repos_group_home', group_name=group.group_name))
937 return literal('<span>%s</span>' % repo_name)
938 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>%s</span>' % last_link]))
938 else:
939 def make_link(group):
940 return link_to(group.name,
941 url('repos_group_home', group_name=group.group_name))
942 return literal(' &raquo; '.join(map(make_link, groups) + ['<span>' + last_link + '</span>']))
943
939
944
940
945 def fancy_file_stats(stats):
941 def fancy_file_stats(stats):
946 """
942 """
947 Displays a fancy two colored bar for number of added/deleted
943 Displays a fancy two colored bar for number of added/deleted
948 lines of code on file
944 lines of code on file
949
945
950 :param stats: two element list of added/deleted lines of code
946 :param stats: two element list of added/deleted lines of code
951 """
947 """
952 def cgen(l_type, a_v, d_v):
948 def cgen(l_type, a_v, d_v):
953 mapping = {'tr': 'top-right-rounded-corner-mid',
949 mapping = {'tr': 'top-right-rounded-corner-mid',
954 'tl': 'top-left-rounded-corner-mid',
950 'tl': 'top-left-rounded-corner-mid',
955 'br': 'bottom-right-rounded-corner-mid',
951 'br': 'bottom-right-rounded-corner-mid',
956 'bl': 'bottom-left-rounded-corner-mid'}
952 'bl': 'bottom-left-rounded-corner-mid'}
957 map_getter = lambda x: mapping[x]
953 map_getter = lambda x: mapping[x]
958
954
959 if l_type == 'a' and d_v:
955 if l_type == 'a' and d_v:
960 #case when added and deleted are present
956 #case when added and deleted are present
961 return ' '.join(map(map_getter, ['tl', 'bl']))
957 return ' '.join(map(map_getter, ['tl', 'bl']))
962
958
963 if l_type == 'a' and not d_v:
959 if l_type == 'a' and not d_v:
964 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
960 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
965
961
966 if l_type == 'd' and a_v:
962 if l_type == 'd' and a_v:
967 return ' '.join(map(map_getter, ['tr', 'br']))
963 return ' '.join(map(map_getter, ['tr', 'br']))
968
964
969 if l_type == 'd' and not a_v:
965 if l_type == 'd' and not a_v:
970 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
966 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
971
967
972 a, d = stats[0], stats[1]
968 a, d = stats[0], stats[1]
973 width = 100
969 width = 100
974
970
975 if a == 'b':
971 if a == 'b':
976 #binary mode
972 #binary mode
977 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
973 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
978 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
974 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
979 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
975 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
980
976
981 t = stats[0] + stats[1]
977 t = stats[0] + stats[1]
982 unit = float(width) / (t or 1)
978 unit = float(width) / (t or 1)
983
979
984 # needs > 9% of width to be visible or 0 to be hidden
980 # needs > 9% of width to be visible or 0 to be hidden
985 a_p = max(9, unit * a) if a > 0 else 0
981 a_p = max(9, unit * a) if a > 0 else 0
986 d_p = max(9, unit * d) if d > 0 else 0
982 d_p = max(9, unit * d) if d > 0 else 0
987 p_sum = a_p + d_p
983 p_sum = a_p + d_p
988
984
989 if p_sum > width:
985 if p_sum > width:
990 #adjust the percentage to be == 100% since we adjusted to 9
986 #adjust the percentage to be == 100% since we adjusted to 9
991 if a_p > d_p:
987 if a_p > d_p:
992 a_p = a_p - (p_sum - width)
988 a_p = a_p - (p_sum - width)
993 else:
989 else:
994 d_p = d_p - (p_sum - width)
990 d_p = d_p - (p_sum - width)
995
991
996 a_v = a if a > 0 else ''
992 a_v = a if a > 0 else ''
997 d_v = d if d > 0 else ''
993 d_v = d if d > 0 else ''
998
994
999 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
995 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1000 cgen('a', a_v, d_v), a_p, a_v
996 cgen('a', a_v, d_v), a_p, a_v
1001 )
997 )
1002 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
998 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1003 cgen('d', a_v, d_v), d_p, d_v
999 cgen('d', a_v, d_v), d_p, d_v
1004 )
1000 )
1005 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1001 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1006
1002
1007
1003
1008 def urlify_text(text_, safe=True):
1004 def urlify_text(text_, safe=True):
1009 """
1005 """
1010 Extrac urls from text and make html links out of them
1006 Extrac urls from text and make html links out of them
1011
1007
1012 :param text_:
1008 :param text_:
1013 """
1009 """
1014
1010
1015 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1011 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
1016 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1012 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1017
1013
1018 def url_func(match_obj):
1014 def url_func(match_obj):
1019 url_full = match_obj.groups()[0]
1015 url_full = match_obj.groups()[0]
1020 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1016 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1021 _newtext = url_pat.sub(url_func, text_)
1017 _newtext = url_pat.sub(url_func, text_)
1022 if safe:
1018 if safe:
1023 return literal(_newtext)
1019 return literal(_newtext)
1024 return _newtext
1020 return _newtext
1025
1021
1026
1022
1027 def urlify_changesets(text_, repository):
1023 def urlify_changesets(text_, repository):
1028 """
1024 """
1029 Extract revision ids from changeset and make link from them
1025 Extract revision ids from changeset and make link from them
1030
1026
1031 :param text_:
1027 :param text_:
1032 :param repository: repo name to build the URL with
1028 :param repository: repo name to build the URL with
1033 """
1029 """
1034 from pylons import url # doh, we need to re-import url to mock it later
1030 from pylons import url # doh, we need to re-import url to mock it later
1035 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1031 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1036
1032
1037 def url_func(match_obj):
1033 def url_func(match_obj):
1038 rev = match_obj.groups()[1]
1034 rev = match_obj.groups()[1]
1039 pref = match_obj.groups()[0]
1035 pref = match_obj.groups()[0]
1040 suf = match_obj.groups()[2]
1036 suf = match_obj.groups()[2]
1041
1037
1042 tmpl = (
1038 tmpl = (
1043 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1039 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1044 '%(rev)s</a>%(suf)s'
1040 '%(rev)s</a>%(suf)s'
1045 )
1041 )
1046 return tmpl % {
1042 return tmpl % {
1047 'pref': pref,
1043 'pref': pref,
1048 'cls': 'revision-link',
1044 'cls': 'revision-link',
1049 'url': url('changeset_home', repo_name=repository, revision=rev),
1045 'url': url('changeset_home', repo_name=repository, revision=rev),
1050 'rev': rev,
1046 'rev': rev,
1051 'suf': suf
1047 'suf': suf
1052 }
1048 }
1053
1049
1054 newtext = URL_PAT.sub(url_func, text_)
1050 newtext = URL_PAT.sub(url_func, text_)
1055
1051
1056 return newtext
1052 return newtext
1057
1053
1058
1054
1059 def urlify_commit(text_, repository=None, link_=None):
1055 def urlify_commit(text_, repository=None, link_=None):
1060 """
1056 """
1061 Parses given text message and makes proper links.
1057 Parses given text message and makes proper links.
1062 issues are linked to given issue-server, and rest is a changeset link
1058 issues are linked to given issue-server, and rest is a changeset link
1063 if link_ is given, in other case it's a plain text
1059 if link_ is given, in other case it's a plain text
1064
1060
1065 :param text_:
1061 :param text_:
1066 :param repository:
1062 :param repository:
1067 :param link_: changeset link
1063 :param link_: changeset link
1068 """
1064 """
1069 import traceback
1065 import traceback
1070 from pylons import url # doh, we need to re-import url to mock it later
1066 from pylons import url # doh, we need to re-import url to mock it later
1071
1067
1072 def escaper(string):
1068 def escaper(string):
1073 return string.replace('<', '&lt;').replace('>', '&gt;')
1069 return string.replace('<', '&lt;').replace('>', '&gt;')
1074
1070
1075 def linkify_others(t, l):
1071 def linkify_others(t, l):
1076 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1072 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1077 links = []
1073 links = []
1078 for e in urls.split(t):
1074 for e in urls.split(t):
1079 if not urls.match(e):
1075 if not urls.match(e):
1080 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1076 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1081 else:
1077 else:
1082 links.append(e)
1078 links.append(e)
1083
1079
1084 return ''.join(links)
1080 return ''.join(links)
1085
1081
1086 # urlify changesets - extrac revisions and make link out of them
1082 # urlify changesets - extrac revisions and make link out of them
1087 newtext = urlify_changesets(escaper(text_), repository)
1083 newtext = urlify_changesets(escaper(text_), repository)
1088
1084
1089 # extract http/https links and make them real urls
1085 # extract http/https links and make them real urls
1090 newtext = urlify_text(newtext, safe=False)
1086 newtext = urlify_text(newtext, safe=False)
1091
1087
1092 try:
1088 try:
1093 from rhodecode import CONFIG
1089 from rhodecode import CONFIG
1094 conf = CONFIG
1090 conf = CONFIG
1095
1091
1096 # allow multiple issue servers to be used
1092 # allow multiple issue servers to be used
1097 valid_indices = [
1093 valid_indices = [
1098 x.group(1)
1094 x.group(1)
1099 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1095 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1100 if x and 'issue_server_link%s' % x.group(1) in conf
1096 if x and 'issue_server_link%s' % x.group(1) in conf
1101 and 'issue_prefix%s' % x.group(1) in conf
1097 and 'issue_prefix%s' % x.group(1) in conf
1102 ]
1098 ]
1103
1099
1104 log.debug('found issue server suffixes `%s` during valuation of: %s'
1100 log.debug('found issue server suffixes `%s` during valuation of: %s'
1105 % (','.join(valid_indices), newtext))
1101 % (','.join(valid_indices), newtext))
1106
1102
1107 for pattern_index in valid_indices:
1103 for pattern_index in valid_indices:
1108 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1104 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1109 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1105 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1110 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1106 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1111
1107
1112 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1108 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1113 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1109 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1114 ISSUE_PREFIX))
1110 ISSUE_PREFIX))
1115
1111
1116 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1112 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1117
1113
1118 def url_func(match_obj):
1114 def url_func(match_obj):
1119 pref = ''
1115 pref = ''
1120 if match_obj.group().startswith(' '):
1116 if match_obj.group().startswith(' '):
1121 pref = ' '
1117 pref = ' '
1122
1118
1123 issue_id = ''.join(match_obj.groups())
1119 issue_id = ''.join(match_obj.groups())
1124 tmpl = (
1120 tmpl = (
1125 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1121 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1126 '%(issue-prefix)s%(id-repr)s'
1122 '%(issue-prefix)s%(id-repr)s'
1127 '</a>'
1123 '</a>'
1128 )
1124 )
1129 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1125 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1130 if repository:
1126 if repository:
1131 url = url.replace('{repo}', repository)
1127 url = url.replace('{repo}', repository)
1132 repo_name = repository.split(URL_SEP)[-1]
1128 repo_name = repository.split(URL_SEP)[-1]
1133 url = url.replace('{repo_name}', repo_name)
1129 url = url.replace('{repo_name}', repo_name)
1134
1130
1135 return tmpl % {
1131 return tmpl % {
1136 'pref': pref,
1132 'pref': pref,
1137 'cls': 'issue-tracker-link',
1133 'cls': 'issue-tracker-link',
1138 'url': url,
1134 'url': url,
1139 'id-repr': issue_id,
1135 'id-repr': issue_id,
1140 'issue-prefix': ISSUE_PREFIX,
1136 'issue-prefix': ISSUE_PREFIX,
1141 'serv': ISSUE_SERVER_LNK,
1137 'serv': ISSUE_SERVER_LNK,
1142 }
1138 }
1143 newtext = URL_PAT.sub(url_func, newtext)
1139 newtext = URL_PAT.sub(url_func, newtext)
1144 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1140 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1145
1141
1146 # if we actually did something above
1142 # if we actually did something above
1147 if link_:
1143 if link_:
1148 # wrap not links into final link => link_
1144 # wrap not links into final link => link_
1149 newtext = linkify_others(newtext, link_)
1145 newtext = linkify_others(newtext, link_)
1150 except:
1146 except:
1151 log.error(traceback.format_exc())
1147 log.error(traceback.format_exc())
1152 pass
1148 pass
1153
1149
1154 return literal(newtext)
1150 return literal(newtext)
1155
1151
1156
1152
1157 def rst(source):
1153 def rst(source):
1158 return literal('<div class="rst-block">%s</div>' %
1154 return literal('<div class="rst-block">%s</div>' %
1159 MarkupRenderer.rst(source))
1155 MarkupRenderer.rst(source))
1160
1156
1161
1157
1162 def rst_w_mentions(source):
1158 def rst_w_mentions(source):
1163 """
1159 """
1164 Wrapped rst renderer with @mention highlighting
1160 Wrapped rst renderer with @mention highlighting
1165
1161
1166 :param source:
1162 :param source:
1167 """
1163 """
1168 return literal('<div class="rst-block">%s</div>' %
1164 return literal('<div class="rst-block">%s</div>' %
1169 MarkupRenderer.rst_with_mentions(source))
1165 MarkupRenderer.rst_with_mentions(source))
1170
1166
1171
1167
1172 def changeset_status(repo, revision):
1168 def changeset_status(repo, revision):
1173 return ChangesetStatusModel().get_status(repo, revision)
1169 return ChangesetStatusModel().get_status(repo, revision)
1174
1170
1175
1171
1176 def changeset_status_lbl(changeset_status):
1172 def changeset_status_lbl(changeset_status):
1177 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1173 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1178
1174
1179
1175
1180 def get_permission_name(key):
1176 def get_permission_name(key):
1181 return dict(Permission.PERMS).get(key)
1177 return dict(Permission.PERMS).get(key)
1182
1178
1183
1179
1184 def journal_filter_help():
1180 def journal_filter_help():
1185 return _(textwrap.dedent('''
1181 return _(textwrap.dedent('''
1186 Example filter terms:
1182 Example filter terms:
1187 repository:vcs
1183 repository:vcs
1188 username:marcin
1184 username:marcin
1189 action:*push*
1185 action:*push*
1190 ip:127.0.0.1
1186 ip:127.0.0.1
1191 date:20120101
1187 date:20120101
1192 date:[20120101100000 TO 20120102]
1188 date:[20120101100000 TO 20120102]
1193
1189
1194 Generate wildcards using '*' character:
1190 Generate wildcards using '*' character:
1195 "repositroy:vcs*" - search everything starting with 'vcs'
1191 "repositroy:vcs*" - search everything starting with 'vcs'
1196 "repository:*vcs*" - search for repository containing 'vcs'
1192 "repository:*vcs*" - search for repository containing 'vcs'
1197
1193
1198 Optional AND / OR operators in queries
1194 Optional AND / OR operators in queries
1199 "repository:vcs OR repository:test"
1195 "repository:vcs OR repository:test"
1200 "username:test AND repository:test*"
1196 "username:test AND repository:test*"
1201 '''))
1197 '''))
1202
1198
1203
1199
1204 def not_mapped_error(repo_name):
1200 def not_mapped_error(repo_name):
1205 flash(_('%s repository is not mapped to db perhaps'
1201 flash(_('%s repository is not mapped to db perhaps'
1206 ' it was created or renamed from the filesystem'
1202 ' it was created or renamed from the filesystem'
1207 ' please run the application again'
1203 ' please run the application again'
1208 ' in order to rescan repositories') % repo_name, category='error')
1204 ' in order to rescan repositories') % repo_name, category='error')
1209
1205
1210
1206
1211 def ip_range(ip_addr):
1207 def ip_range(ip_addr):
1212 from rhodecode.model.db import UserIpMap
1208 from rhodecode.model.db import UserIpMap
1213 s, e = UserIpMap._get_ip_range(ip_addr)
1209 s, e = UserIpMap._get_ip_range(ip_addr)
1214 return '%s - %s' % (s, e)
1210 return '%s - %s' % (s, e)
@@ -1,2052 +1,2052 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import hashlib
30 import hashlib
31 import time
31 import time
32 from collections import defaultdict
32 from collections import defaultdict
33
33
34 from sqlalchemy import *
34 from sqlalchemy import *
35 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.ext.hybrid import hybrid_property
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
36 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
37 from sqlalchemy.exc import DatabaseError
37 from sqlalchemy.exc import DatabaseError
38 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
39 from webob.exc import HTTPNotFound
39 from webob.exc import HTTPNotFound
40
40
41 from pylons.i18n.translation import lazy_ugettext as _
41 from pylons.i18n.translation import lazy_ugettext as _
42
42
43 from rhodecode.lib.vcs import get_backend
43 from rhodecode.lib.vcs import get_backend
44 from rhodecode.lib.vcs.utils.helpers import get_scm
44 from rhodecode.lib.vcs.utils.helpers import get_scm
45 from rhodecode.lib.vcs.exceptions import VCSError
45 from rhodecode.lib.vcs.exceptions import VCSError
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
46 from rhodecode.lib.vcs.utils.lazy import LazyProperty
47 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, time_to_datetime, _set_extras
50 safe_unicode, remove_suffix, remove_prefix, time_to_datetime, _set_extras
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, self.repo_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 is_valid(cls, repo_name):
940 def is_valid(cls, repo_name):
941 """
941 """
942 returns True if given repo name is a valid filesystem repository
942 returns True if given repo name is a valid filesystem repository
943
943
944 :param cls:
944 :param cls:
945 :param repo_name:
945 :param repo_name:
946 """
946 """
947 from rhodecode.lib.utils import is_valid_repo
947 from rhodecode.lib.utils import is_valid_repo
948
948
949 return is_valid_repo(repo_name, cls.base_path())
949 return is_valid_repo(repo_name, cls.base_path())
950
950
951 def get_api_data(self):
951 def get_api_data(self):
952 """
952 """
953 Common function for generating repo api data
953 Common function for generating repo api data
954
954
955 """
955 """
956 repo = self
956 repo = self
957 data = dict(
957 data = dict(
958 repo_id=repo.repo_id,
958 repo_id=repo.repo_id,
959 repo_name=repo.repo_name,
959 repo_name=repo.repo_name,
960 repo_type=repo.repo_type,
960 repo_type=repo.repo_type,
961 clone_uri=repo.clone_uri,
961 clone_uri=repo.clone_uri,
962 private=repo.private,
962 private=repo.private,
963 created_on=repo.created_on,
963 created_on=repo.created_on,
964 description=repo.description,
964 description=repo.description,
965 landing_rev=repo.landing_rev,
965 landing_rev=repo.landing_rev,
966 owner=repo.user.username,
966 owner=repo.user.username,
967 fork_of=repo.fork.repo_name if repo.fork else None,
967 fork_of=repo.fork.repo_name if repo.fork else None,
968 enable_statistics=repo.enable_statistics,
968 enable_statistics=repo.enable_statistics,
969 enable_locking=repo.enable_locking,
969 enable_locking=repo.enable_locking,
970 enable_downloads=repo.enable_downloads,
970 enable_downloads=repo.enable_downloads,
971 last_changeset=repo.changeset_cache,
971 last_changeset=repo.changeset_cache,
972 locked_by=User.get(self.locked[0]).get_api_data() \
972 locked_by=User.get(self.locked[0]).get_api_data() \
973 if self.locked[0] else None,
973 if self.locked[0] else None,
974 locked_date=time_to_datetime(self.locked[1]) \
974 locked_date=time_to_datetime(self.locked[1]) \
975 if self.locked[1] else None
975 if self.locked[1] else None
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 def scm_instance_no_cache(self):
1152 def scm_instance_no_cache(self):
1153 return self.__get_instance()
1153 return self.__get_instance()
1154
1154
1155 @LazyProperty
1155 @LazyProperty
1156 def scm_instance(self):
1156 def scm_instance(self):
1157 import rhodecode
1157 import rhodecode
1158 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1158 full_cache = str2bool(rhodecode.CONFIG.get('vcs_full_cache'))
1159 if full_cache:
1159 if full_cache:
1160 return self.scm_instance_cached()
1160 return self.scm_instance_cached()
1161 return self.__get_instance()
1161 return self.__get_instance()
1162
1162
1163 def scm_instance_cached(self, cache_map=None):
1163 def scm_instance_cached(self, cache_map=None):
1164 @cache_region('long_term')
1164 @cache_region('long_term')
1165 def _c(repo_name):
1165 def _c(repo_name):
1166 return self.__get_instance()
1166 return self.__get_instance()
1167 rn = self.repo_name
1167 rn = self.repo_name
1168 log.debug('Getting cached instance of repo')
1168 log.debug('Getting cached instance of repo')
1169
1169
1170 if cache_map:
1170 if cache_map:
1171 # get using prefilled cache_map
1171 # get using prefilled cache_map
1172 invalidate_repo = cache_map[self.repo_name]
1172 invalidate_repo = cache_map[self.repo_name]
1173 if invalidate_repo:
1173 if invalidate_repo:
1174 invalidate_repo = (None if invalidate_repo.cache_active
1174 invalidate_repo = (None if invalidate_repo.cache_active
1175 else invalidate_repo)
1175 else invalidate_repo)
1176 else:
1176 else:
1177 # get from invalidate
1177 # get from invalidate
1178 invalidate_repo = self.invalidate
1178 invalidate_repo = self.invalidate
1179
1179
1180 if invalidate_repo is not None:
1180 if invalidate_repo is not None:
1181 region_invalidate(_c, None, rn)
1181 region_invalidate(_c, None, rn)
1182 # update our cache
1182 # update our cache
1183 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1183 CacheInvalidation.set_valid(invalidate_repo.cache_key)
1184 return _c(rn)
1184 return _c(rn)
1185
1185
1186 def __get_instance(self):
1186 def __get_instance(self):
1187 repo_full_path = self.repo_full_path
1187 repo_full_path = self.repo_full_path
1188 try:
1188 try:
1189 alias = get_scm(repo_full_path)[0]
1189 alias = get_scm(repo_full_path)[0]
1190 log.debug('Creating instance of %s repository from %s'
1190 log.debug('Creating instance of %s repository from %s'
1191 % (alias, repo_full_path))
1191 % (alias, repo_full_path))
1192 backend = get_backend(alias)
1192 backend = get_backend(alias)
1193 except VCSError:
1193 except VCSError:
1194 log.error(traceback.format_exc())
1194 log.error(traceback.format_exc())
1195 log.error('Perhaps this repository is in db and not in '
1195 log.error('Perhaps this repository is in db and not in '
1196 'filesystem run rescan repositories with '
1196 'filesystem run rescan repositories with '
1197 '"destroy old data " option from admin panel')
1197 '"destroy old data " option from admin panel')
1198 return
1198 return
1199
1199
1200 if alias == 'hg':
1200 if alias == 'hg':
1201
1201
1202 repo = backend(safe_str(repo_full_path), create=False,
1202 repo = backend(safe_str(repo_full_path), create=False,
1203 baseui=self._ui)
1203 baseui=self._ui)
1204 # skip hidden web repository
1204 # skip hidden web repository
1205 if repo._get_hidden():
1205 if repo._get_hidden():
1206 return
1206 return
1207 else:
1207 else:
1208 repo = backend(repo_full_path, create=False)
1208 repo = backend(repo_full_path, create=False)
1209
1209
1210 return repo
1210 return repo
1211
1211
1212
1212
1213 class RepoGroup(Base, BaseModel):
1213 class RepoGroup(Base, BaseModel):
1214 __tablename__ = 'groups'
1214 __tablename__ = 'groups'
1215 __table_args__ = (
1215 __table_args__ = (
1216 UniqueConstraint('group_name', 'group_parent_id'),
1216 UniqueConstraint('group_name', 'group_parent_id'),
1217 CheckConstraint('group_id != group_parent_id'),
1217 CheckConstraint('group_id != group_parent_id'),
1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1218 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1219 'mysql_charset': 'utf8'},
1219 'mysql_charset': 'utf8'},
1220 )
1220 )
1221 __mapper_args__ = {'order_by': 'group_name'}
1221 __mapper_args__ = {'order_by': 'group_name'}
1222
1222
1223 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1223 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1224 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1224 group_name = Column("group_name", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
1225 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1225 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
1226 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1226 group_description = Column("group_description", String(10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1227 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1227 enable_locking = Column("enable_locking", Boolean(), nullable=False, unique=None, default=False)
1228
1228
1229 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1229 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
1230 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1230 users_group_to_perm = relationship('UserGroupRepoGroupToPerm', cascade='all')
1231
1231
1232 parent_group = relationship('RepoGroup', remote_side=group_id)
1232 parent_group = relationship('RepoGroup', remote_side=group_id)
1233
1233
1234 def __init__(self, group_name='', parent_group=None):
1234 def __init__(self, group_name='', parent_group=None):
1235 self.group_name = group_name
1235 self.group_name = group_name
1236 self.parent_group = parent_group
1236 self.parent_group = parent_group
1237
1237
1238 def __unicode__(self):
1238 def __unicode__(self):
1239 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1239 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
1240 self.group_name)
1240 self.group_name)
1241
1241
1242 @classmethod
1242 @classmethod
1243 def groups_choices(cls, groups=None, show_empty_group=True):
1243 def groups_choices(cls, groups=None, show_empty_group=True):
1244 from webhelpers.html import literal as _literal
1244 from webhelpers.html import literal as _literal
1245 if not groups:
1245 if not groups:
1246 groups = cls.query().all()
1246 groups = cls.query().all()
1247
1247
1248 repo_groups = []
1248 repo_groups = []
1249 if show_empty_group:
1249 if show_empty_group:
1250 repo_groups = [('-1', '-- %s --' % _('top level'))]
1250 repo_groups = [('-1', '-- %s --' % _('top level'))]
1251 sep = ' &raquo; '
1251 sep = ' &raquo; '
1252 _name = lambda k: _literal(sep.join(k))
1252 _name = lambda k: _literal(sep.join(k))
1253
1253
1254 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1254 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
1255 for x in groups])
1255 for x in groups])
1256
1256
1257 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1257 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
1258 return repo_groups
1258 return repo_groups
1259
1259
1260 @classmethod
1260 @classmethod
1261 def url_sep(cls):
1261 def url_sep(cls):
1262 return URL_SEP
1262 return URL_SEP
1263
1263
1264 @classmethod
1264 @classmethod
1265 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1265 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
1266 if case_insensitive:
1266 if case_insensitive:
1267 gr = cls.query()\
1267 gr = cls.query()\
1268 .filter(cls.group_name.ilike(group_name))
1268 .filter(cls.group_name.ilike(group_name))
1269 else:
1269 else:
1270 gr = cls.query()\
1270 gr = cls.query()\
1271 .filter(cls.group_name == group_name)
1271 .filter(cls.group_name == group_name)
1272 if cache:
1272 if cache:
1273 gr = gr.options(FromCache(
1273 gr = gr.options(FromCache(
1274 "sql_cache_short",
1274 "sql_cache_short",
1275 "get_group_%s" % _hash_key(group_name)
1275 "get_group_%s" % _hash_key(group_name)
1276 )
1276 )
1277 )
1277 )
1278 return gr.scalar()
1278 return gr.scalar()
1279
1279
1280 @property
1280 @property
1281 def parents(self):
1281 def parents(self):
1282 parents_recursion_limit = 5
1282 parents_recursion_limit = 5
1283 groups = []
1283 groups = []
1284 if self.parent_group is None:
1284 if self.parent_group is None:
1285 return groups
1285 return groups
1286 cur_gr = self.parent_group
1286 cur_gr = self.parent_group
1287 groups.insert(0, cur_gr)
1287 groups.insert(0, cur_gr)
1288 cnt = 0
1288 cnt = 0
1289 while 1:
1289 while 1:
1290 cnt += 1
1290 cnt += 1
1291 gr = getattr(cur_gr, 'parent_group', None)
1291 gr = getattr(cur_gr, 'parent_group', None)
1292 cur_gr = cur_gr.parent_group
1292 cur_gr = cur_gr.parent_group
1293 if gr is None:
1293 if gr is None:
1294 break
1294 break
1295 if cnt == parents_recursion_limit:
1295 if cnt == parents_recursion_limit:
1296 # this will prevent accidental infinit loops
1296 # this will prevent accidental infinit loops
1297 log.error('group nested more than %s' %
1297 log.error('group nested more than %s' %
1298 parents_recursion_limit)
1298 parents_recursion_limit)
1299 break
1299 break
1300
1300
1301 groups.insert(0, gr)
1301 groups.insert(0, gr)
1302 return groups
1302 return groups
1303
1303
1304 @property
1304 @property
1305 def children(self):
1305 def children(self):
1306 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1306 return RepoGroup.query().filter(RepoGroup.parent_group == self)
1307
1307
1308 @property
1308 @property
1309 def name(self):
1309 def name(self):
1310 return self.group_name.split(RepoGroup.url_sep())[-1]
1310 return self.group_name.split(RepoGroup.url_sep())[-1]
1311
1311
1312 @property
1312 @property
1313 def full_path(self):
1313 def full_path(self):
1314 return self.group_name
1314 return self.group_name
1315
1315
1316 @property
1316 @property
1317 def full_path_splitted(self):
1317 def full_path_splitted(self):
1318 return self.group_name.split(RepoGroup.url_sep())
1318 return self.group_name.split(RepoGroup.url_sep())
1319
1319
1320 @property
1320 @property
1321 def repositories(self):
1321 def repositories(self):
1322 return Repository.query()\
1322 return Repository.query()\
1323 .filter(Repository.group == self)\
1323 .filter(Repository.group == self)\
1324 .order_by(Repository.repo_name)
1324 .order_by(Repository.repo_name)
1325
1325
1326 @property
1326 @property
1327 def repositories_recursive_count(self):
1327 def repositories_recursive_count(self):
1328 cnt = self.repositories.count()
1328 cnt = self.repositories.count()
1329
1329
1330 def children_count(group):
1330 def children_count(group):
1331 cnt = 0
1331 cnt = 0
1332 for child in group.children:
1332 for child in group.children:
1333 cnt += child.repositories.count()
1333 cnt += child.repositories.count()
1334 cnt += children_count(child)
1334 cnt += children_count(child)
1335 return cnt
1335 return cnt
1336
1336
1337 return cnt + children_count(self)
1337 return cnt + children_count(self)
1338
1338
1339 def _recursive_objects(self, include_repos=True):
1339 def _recursive_objects(self, include_repos=True):
1340 all_ = []
1340 all_ = []
1341
1341
1342 def _get_members(root_gr):
1342 def _get_members(root_gr):
1343 if include_repos:
1343 if include_repos:
1344 for r in root_gr.repositories:
1344 for r in root_gr.repositories:
1345 all_.append(r)
1345 all_.append(r)
1346 childs = root_gr.children.all()
1346 childs = root_gr.children.all()
1347 if childs:
1347 if childs:
1348 for gr in childs:
1348 for gr in childs:
1349 all_.append(gr)
1349 all_.append(gr)
1350 _get_members(gr)
1350 _get_members(gr)
1351
1351
1352 _get_members(self)
1352 _get_members(self)
1353 return [self] + all_
1353 return [self] + all_
1354
1354
1355 def recursive_groups_and_repos(self):
1355 def recursive_groups_and_repos(self):
1356 """
1356 """
1357 Recursive return all groups, with repositories in those groups
1357 Recursive return all groups, with repositories in those groups
1358 """
1358 """
1359 return self._recursive_objects()
1359 return self._recursive_objects()
1360
1360
1361 def recursive_groups(self):
1361 def recursive_groups(self):
1362 """
1362 """
1363 Returns all children groups for this group including children of children
1363 Returns all children groups for this group including children of children
1364 """
1364 """
1365 return self._recursive_objects(include_repos=False)
1365 return self._recursive_objects(include_repos=False)
1366
1366
1367 def get_new_name(self, group_name):
1367 def get_new_name(self, group_name):
1368 """
1368 """
1369 returns new full group name based on parent and new name
1369 returns new full group name based on parent and new name
1370
1370
1371 :param group_name:
1371 :param group_name:
1372 """
1372 """
1373 path_prefix = (self.parent_group.full_path_splitted if
1373 path_prefix = (self.parent_group.full_path_splitted if
1374 self.parent_group else [])
1374 self.parent_group else [])
1375 return RepoGroup.url_sep().join(path_prefix + [group_name])
1375 return RepoGroup.url_sep().join(path_prefix + [group_name])
1376
1376
1377
1377
1378 class Permission(Base, BaseModel):
1378 class Permission(Base, BaseModel):
1379 __tablename__ = 'permissions'
1379 __tablename__ = 'permissions'
1380 __table_args__ = (
1380 __table_args__ = (
1381 Index('p_perm_name_idx', 'permission_name'),
1381 Index('p_perm_name_idx', 'permission_name'),
1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1382 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1383 'mysql_charset': 'utf8'},
1383 'mysql_charset': 'utf8'},
1384 )
1384 )
1385 PERMS = [
1385 PERMS = [
1386 ('repository.none', _('Repository no access')),
1386 ('repository.none', _('Repository no access')),
1387 ('repository.read', _('Repository read access')),
1387 ('repository.read', _('Repository read access')),
1388 ('repository.write', _('Repository write access')),
1388 ('repository.write', _('Repository write access')),
1389 ('repository.admin', _('Repository admin access')),
1389 ('repository.admin', _('Repository admin access')),
1390
1390
1391 ('group.none', _('Repository group no access')),
1391 ('group.none', _('Repository group no access')),
1392 ('group.read', _('Repository group read access')),
1392 ('group.read', _('Repository group read access')),
1393 ('group.write', _('Repository group write access')),
1393 ('group.write', _('Repository group write access')),
1394 ('group.admin', _('Repository group admin access')),
1394 ('group.admin', _('Repository group admin access')),
1395
1395
1396 ('hg.admin', _('RhodeCode Administrator')),
1396 ('hg.admin', _('RhodeCode Administrator')),
1397 ('hg.create.none', _('Repository creation disabled')),
1397 ('hg.create.none', _('Repository creation disabled')),
1398 ('hg.create.repository', _('Repository creation enabled')),
1398 ('hg.create.repository', _('Repository creation enabled')),
1399 ('hg.fork.none', _('Repository forking disabled')),
1399 ('hg.fork.none', _('Repository forking disabled')),
1400 ('hg.fork.repository', _('Repository forking enabled')),
1400 ('hg.fork.repository', _('Repository forking enabled')),
1401 ('hg.register.none', _('Register disabled')),
1401 ('hg.register.none', _('Register disabled')),
1402 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1402 ('hg.register.manual_activate', _('Register new user with RhodeCode '
1403 'with manual activation')),
1403 'with manual activation')),
1404
1404
1405 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1405 ('hg.register.auto_activate', _('Register new user with RhodeCode '
1406 'with auto activation')),
1406 'with auto activation')),
1407 ]
1407 ]
1408
1408
1409 # defines which permissions are more important higher the more important
1409 # defines which permissions are more important higher the more important
1410 PERM_WEIGHTS = {
1410 PERM_WEIGHTS = {
1411 'repository.none': 0,
1411 'repository.none': 0,
1412 'repository.read': 1,
1412 'repository.read': 1,
1413 'repository.write': 3,
1413 'repository.write': 3,
1414 'repository.admin': 4,
1414 'repository.admin': 4,
1415
1415
1416 'group.none': 0,
1416 'group.none': 0,
1417 'group.read': 1,
1417 'group.read': 1,
1418 'group.write': 3,
1418 'group.write': 3,
1419 'group.admin': 4,
1419 'group.admin': 4,
1420
1420
1421 'hg.fork.none': 0,
1421 'hg.fork.none': 0,
1422 'hg.fork.repository': 1,
1422 'hg.fork.repository': 1,
1423 'hg.create.none': 0,
1423 'hg.create.none': 0,
1424 'hg.create.repository':1
1424 'hg.create.repository':1
1425 }
1425 }
1426
1426
1427 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1427 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1428 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1428 permission_name = Column("permission_name", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1429 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1429 permission_longname = Column("permission_longname", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1430
1430
1431 def __unicode__(self):
1431 def __unicode__(self):
1432 return u"<%s('%s:%s')>" % (
1432 return u"<%s('%s:%s')>" % (
1433 self.__class__.__name__, self.permission_id, self.permission_name
1433 self.__class__.__name__, self.permission_id, self.permission_name
1434 )
1434 )
1435
1435
1436 @classmethod
1436 @classmethod
1437 def get_by_key(cls, key):
1437 def get_by_key(cls, key):
1438 return cls.query().filter(cls.permission_name == key).scalar()
1438 return cls.query().filter(cls.permission_name == key).scalar()
1439
1439
1440 @classmethod
1440 @classmethod
1441 def get_default_perms(cls, default_user_id):
1441 def get_default_perms(cls, default_user_id):
1442 q = Session().query(UserRepoToPerm, Repository, cls)\
1442 q = Session().query(UserRepoToPerm, Repository, cls)\
1443 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1443 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
1444 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1444 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
1445 .filter(UserRepoToPerm.user_id == default_user_id)
1445 .filter(UserRepoToPerm.user_id == default_user_id)
1446
1446
1447 return q.all()
1447 return q.all()
1448
1448
1449 @classmethod
1449 @classmethod
1450 def get_default_group_perms(cls, default_user_id):
1450 def get_default_group_perms(cls, default_user_id):
1451 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1451 q = Session().query(UserRepoGroupToPerm, RepoGroup, cls)\
1452 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1452 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
1453 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1453 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
1454 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1454 .filter(UserRepoGroupToPerm.user_id == default_user_id)
1455
1455
1456 return q.all()
1456 return q.all()
1457
1457
1458
1458
1459 class UserRepoToPerm(Base, BaseModel):
1459 class UserRepoToPerm(Base, BaseModel):
1460 __tablename__ = 'repo_to_perm'
1460 __tablename__ = 'repo_to_perm'
1461 __table_args__ = (
1461 __table_args__ = (
1462 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1462 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
1463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1463 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1464 'mysql_charset': 'utf8'}
1464 'mysql_charset': 'utf8'}
1465 )
1465 )
1466 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1466 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1468 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1468 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1469 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1469 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1470
1470
1471 user = relationship('User')
1471 user = relationship('User')
1472 repository = relationship('Repository')
1472 repository = relationship('Repository')
1473 permission = relationship('Permission')
1473 permission = relationship('Permission')
1474
1474
1475 @classmethod
1475 @classmethod
1476 def create(cls, user, repository, permission):
1476 def create(cls, user, repository, permission):
1477 n = cls()
1477 n = cls()
1478 n.user = user
1478 n.user = user
1479 n.repository = repository
1479 n.repository = repository
1480 n.permission = permission
1480 n.permission = permission
1481 Session().add(n)
1481 Session().add(n)
1482 return n
1482 return n
1483
1483
1484 def __unicode__(self):
1484 def __unicode__(self):
1485 return u'<user:%s => %s >' % (self.user, self.repository)
1485 return u'<user:%s => %s >' % (self.user, self.repository)
1486
1486
1487
1487
1488 class UserToPerm(Base, BaseModel):
1488 class UserToPerm(Base, BaseModel):
1489 __tablename__ = 'user_to_perm'
1489 __tablename__ = 'user_to_perm'
1490 __table_args__ = (
1490 __table_args__ = (
1491 UniqueConstraint('user_id', 'permission_id'),
1491 UniqueConstraint('user_id', 'permission_id'),
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1492 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1493 'mysql_charset': 'utf8'}
1493 'mysql_charset': 'utf8'}
1494 )
1494 )
1495 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1495 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1496 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1496 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1497 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1497 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1498
1498
1499 user = relationship('User')
1499 user = relationship('User')
1500 permission = relationship('Permission', lazy='joined')
1500 permission = relationship('Permission', lazy='joined')
1501
1501
1502
1502
1503 class UserGroupRepoToPerm(Base, BaseModel):
1503 class UserGroupRepoToPerm(Base, BaseModel):
1504 __tablename__ = 'users_group_repo_to_perm'
1504 __tablename__ = 'users_group_repo_to_perm'
1505 __table_args__ = (
1505 __table_args__ = (
1506 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1506 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
1507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1507 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1508 'mysql_charset': 'utf8'}
1508 'mysql_charset': 'utf8'}
1509 )
1509 )
1510 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1510 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1511 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1511 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1512 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1512 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1513 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
1514
1514
1515 users_group = relationship('UserGroup')
1515 users_group = relationship('UserGroup')
1516 permission = relationship('Permission')
1516 permission = relationship('Permission')
1517 repository = relationship('Repository')
1517 repository = relationship('Repository')
1518
1518
1519 @classmethod
1519 @classmethod
1520 def create(cls, users_group, repository, permission):
1520 def create(cls, users_group, repository, permission):
1521 n = cls()
1521 n = cls()
1522 n.users_group = users_group
1522 n.users_group = users_group
1523 n.repository = repository
1523 n.repository = repository
1524 n.permission = permission
1524 n.permission = permission
1525 Session().add(n)
1525 Session().add(n)
1526 return n
1526 return n
1527
1527
1528 def __unicode__(self):
1528 def __unicode__(self):
1529 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1529 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
1530
1530
1531
1531
1532 class UserGroupToPerm(Base, BaseModel):
1532 class UserGroupToPerm(Base, BaseModel):
1533 __tablename__ = 'users_group_to_perm'
1533 __tablename__ = 'users_group_to_perm'
1534 __table_args__ = (
1534 __table_args__ = (
1535 UniqueConstraint('users_group_id', 'permission_id',),
1535 UniqueConstraint('users_group_id', 'permission_id',),
1536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1536 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1537 'mysql_charset': 'utf8'}
1537 'mysql_charset': 'utf8'}
1538 )
1538 )
1539 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1539 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1540 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1540 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1541 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1541 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1542
1542
1543 users_group = relationship('UserGroup')
1543 users_group = relationship('UserGroup')
1544 permission = relationship('Permission')
1544 permission = relationship('Permission')
1545
1545
1546
1546
1547 class UserRepoGroupToPerm(Base, BaseModel):
1547 class UserRepoGroupToPerm(Base, BaseModel):
1548 __tablename__ = 'user_repo_group_to_perm'
1548 __tablename__ = 'user_repo_group_to_perm'
1549 __table_args__ = (
1549 __table_args__ = (
1550 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1550 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1551 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1552 'mysql_charset': 'utf8'}
1552 'mysql_charset': 'utf8'}
1553 )
1553 )
1554
1554
1555 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1555 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1556 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1556 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1557 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1557 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1558 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1558 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1559
1559
1560 user = relationship('User')
1560 user = relationship('User')
1561 group = relationship('RepoGroup')
1561 group = relationship('RepoGroup')
1562 permission = relationship('Permission')
1562 permission = relationship('Permission')
1563
1563
1564
1564
1565 class UserGroupRepoGroupToPerm(Base, BaseModel):
1565 class UserGroupRepoGroupToPerm(Base, BaseModel):
1566 __tablename__ = 'users_group_repo_group_to_perm'
1566 __tablename__ = 'users_group_repo_group_to_perm'
1567 __table_args__ = (
1567 __table_args__ = (
1568 UniqueConstraint('users_group_id', 'group_id'),
1568 UniqueConstraint('users_group_id', 'group_id'),
1569 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1569 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1570 'mysql_charset': 'utf8'}
1570 'mysql_charset': 'utf8'}
1571 )
1571 )
1572
1572
1573 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)
1573 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_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1574 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1575 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1575 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1576 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1576 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1577
1577
1578 users_group = relationship('UserGroup')
1578 users_group = relationship('UserGroup')
1579 permission = relationship('Permission')
1579 permission = relationship('Permission')
1580 group = relationship('RepoGroup')
1580 group = relationship('RepoGroup')
1581
1581
1582
1582
1583 class Statistics(Base, BaseModel):
1583 class Statistics(Base, BaseModel):
1584 __tablename__ = 'statistics'
1584 __tablename__ = 'statistics'
1585 __table_args__ = (
1585 __table_args__ = (
1586 UniqueConstraint('repository_id'),
1586 UniqueConstraint('repository_id'),
1587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1587 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1588 'mysql_charset': 'utf8'}
1588 'mysql_charset': 'utf8'}
1589 )
1589 )
1590 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1590 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1591 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1591 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1592 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1592 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1593 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1593 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1594 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1594 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1595 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1595 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1596
1596
1597 repository = relationship('Repository', single_parent=True)
1597 repository = relationship('Repository', single_parent=True)
1598
1598
1599
1599
1600 class UserFollowing(Base, BaseModel):
1600 class UserFollowing(Base, BaseModel):
1601 __tablename__ = 'user_followings'
1601 __tablename__ = 'user_followings'
1602 __table_args__ = (
1602 __table_args__ = (
1603 UniqueConstraint('user_id', 'follows_repository_id'),
1603 UniqueConstraint('user_id', 'follows_repository_id'),
1604 UniqueConstraint('user_id', 'follows_user_id'),
1604 UniqueConstraint('user_id', 'follows_user_id'),
1605 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1605 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1606 'mysql_charset': 'utf8'}
1606 'mysql_charset': 'utf8'}
1607 )
1607 )
1608
1608
1609 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1609 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1610 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1611 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1611 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1612 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1612 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1613 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1613 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1614
1614
1615 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1615 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1616
1616
1617 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1617 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1618 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1618 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1619
1619
1620 @classmethod
1620 @classmethod
1621 def get_repo_followers(cls, repo_id):
1621 def get_repo_followers(cls, repo_id):
1622 return cls.query().filter(cls.follows_repo_id == repo_id)
1622 return cls.query().filter(cls.follows_repo_id == repo_id)
1623
1623
1624
1624
1625 class CacheInvalidation(Base, BaseModel):
1625 class CacheInvalidation(Base, BaseModel):
1626 __tablename__ = 'cache_invalidation'
1626 __tablename__ = 'cache_invalidation'
1627 __table_args__ = (
1627 __table_args__ = (
1628 UniqueConstraint('cache_key'),
1628 UniqueConstraint('cache_key'),
1629 Index('key_idx', 'cache_key'),
1629 Index('key_idx', 'cache_key'),
1630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1630 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1631 'mysql_charset': 'utf8'},
1631 'mysql_charset': 'utf8'},
1632 )
1632 )
1633 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1633 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1634 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1634 cache_key = Column("cache_key", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1635 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1635 cache_args = Column("cache_args", String(255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1636 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1636 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1637
1637
1638 def __init__(self, cache_key, cache_args=''):
1638 def __init__(self, cache_key, cache_args=''):
1639 self.cache_key = cache_key
1639 self.cache_key = cache_key
1640 self.cache_args = cache_args
1640 self.cache_args = cache_args
1641 self.cache_active = False
1641 self.cache_active = False
1642
1642
1643 def __unicode__(self):
1643 def __unicode__(self):
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1644 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1645 self.cache_id, self.cache_key)
1645 self.cache_id, self.cache_key)
1646
1646
1647 @property
1647 @property
1648 def prefix(self):
1648 def prefix(self):
1649 _split = self.cache_key.split(self.cache_args, 1)
1649 _split = self.cache_key.split(self.cache_args, 1)
1650 if _split and len(_split) == 2:
1650 if _split and len(_split) == 2:
1651 return _split[0]
1651 return _split[0]
1652 return ''
1652 return ''
1653
1653
1654 @classmethod
1654 @classmethod
1655 def clear_cache(cls):
1655 def clear_cache(cls):
1656 cls.query().delete()
1656 cls.query().delete()
1657
1657
1658 @classmethod
1658 @classmethod
1659 def _get_key(cls, key):
1659 def _get_key(cls, key):
1660 """
1660 """
1661 Wrapper for generating a key, together with a prefix
1661 Wrapper for generating a key, together with a prefix
1662
1662
1663 :param key:
1663 :param key:
1664 """
1664 """
1665 import rhodecode
1665 import rhodecode
1666 prefix = ''
1666 prefix = ''
1667 org_key = key
1667 org_key = key
1668 iid = rhodecode.CONFIG.get('instance_id')
1668 iid = rhodecode.CONFIG.get('instance_id')
1669 if iid:
1669 if iid:
1670 prefix = iid
1670 prefix = iid
1671
1671
1672 return "%s%s" % (prefix, key), prefix, org_key
1672 return "%s%s" % (prefix, key), prefix, org_key
1673
1673
1674 @classmethod
1674 @classmethod
1675 def get_by_key(cls, key):
1675 def get_by_key(cls, key):
1676 return cls.query().filter(cls.cache_key == key).scalar()
1676 return cls.query().filter(cls.cache_key == key).scalar()
1677
1677
1678 @classmethod
1678 @classmethod
1679 def get_by_repo_name(cls, repo_name):
1679 def get_by_repo_name(cls, repo_name):
1680 return cls.query().filter(cls.cache_args == repo_name).all()
1680 return cls.query().filter(cls.cache_args == repo_name).all()
1681
1681
1682 @classmethod
1682 @classmethod
1683 def _get_or_create_key(cls, key, repo_name, commit=True):
1683 def _get_or_create_key(cls, key, repo_name, commit=True):
1684 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1684 inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar()
1685 if not inv_obj:
1685 if not inv_obj:
1686 try:
1686 try:
1687 inv_obj = CacheInvalidation(key, repo_name)
1687 inv_obj = CacheInvalidation(key, repo_name)
1688 Session().add(inv_obj)
1688 Session().add(inv_obj)
1689 if commit:
1689 if commit:
1690 Session().commit()
1690 Session().commit()
1691 except Exception:
1691 except Exception:
1692 log.error(traceback.format_exc())
1692 log.error(traceback.format_exc())
1693 Session().rollback()
1693 Session().rollback()
1694 return inv_obj
1694 return inv_obj
1695
1695
1696 @classmethod
1696 @classmethod
1697 def invalidate(cls, key):
1697 def invalidate(cls, key):
1698 """
1698 """
1699 Returns Invalidation object if this given key should be invalidated
1699 Returns Invalidation object if this given key should be invalidated
1700 None otherwise. `cache_active = False` means that this cache
1700 None otherwise. `cache_active = False` means that this cache
1701 state is not valid and needs to be invalidated
1701 state is not valid and needs to be invalidated
1702
1702
1703 :param key:
1703 :param key:
1704 """
1704 """
1705 repo_name = key
1705 repo_name = key
1706 repo_name = remove_suffix(repo_name, '_README')
1706 repo_name = remove_suffix(repo_name, '_README')
1707 repo_name = remove_suffix(repo_name, '_RSS')
1707 repo_name = remove_suffix(repo_name, '_RSS')
1708 repo_name = remove_suffix(repo_name, '_ATOM')
1708 repo_name = remove_suffix(repo_name, '_ATOM')
1709
1709
1710 # adds instance prefix
1710 # adds instance prefix
1711 key, _prefix, _org_key = cls._get_key(key)
1711 key, _prefix, _org_key = cls._get_key(key)
1712 inv = cls._get_or_create_key(key, repo_name)
1712 inv = cls._get_or_create_key(key, repo_name)
1713
1713
1714 if inv and inv.cache_active is False:
1714 if inv and inv.cache_active is False:
1715 return inv
1715 return inv
1716
1716
1717 @classmethod
1717 @classmethod
1718 def set_invalidate(cls, key=None, repo_name=None):
1718 def set_invalidate(cls, key=None, repo_name=None):
1719 """
1719 """
1720 Mark this Cache key for invalidation, either by key or whole
1720 Mark this Cache key for invalidation, either by key or whole
1721 cache sets based on repo_name
1721 cache sets based on repo_name
1722
1722
1723 :param key:
1723 :param key:
1724 """
1724 """
1725 invalidated_keys = []
1725 invalidated_keys = []
1726 if key:
1726 if key:
1727 key, _prefix, _org_key = cls._get_key(key)
1727 key, _prefix, _org_key = cls._get_key(key)
1728 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1728 inv_objs = Session().query(cls).filter(cls.cache_key == key).all()
1729 elif repo_name:
1729 elif repo_name:
1730 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1730 inv_objs = Session().query(cls).filter(cls.cache_args == repo_name).all()
1731
1731
1732 try:
1732 try:
1733 for inv_obj in inv_objs:
1733 for inv_obj in inv_objs:
1734 inv_obj.cache_active = False
1734 inv_obj.cache_active = False
1735 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1735 log.debug('marking %s key for invalidation based on key=%s,repo_name=%s'
1736 % (inv_obj, key, safe_str(repo_name)))
1736 % (inv_obj, key, safe_str(repo_name)))
1737 invalidated_keys.append(inv_obj.cache_key)
1737 invalidated_keys.append(inv_obj.cache_key)
1738 Session().add(inv_obj)
1738 Session().add(inv_obj)
1739 Session().commit()
1739 Session().commit()
1740 except Exception:
1740 except Exception:
1741 log.error(traceback.format_exc())
1741 log.error(traceback.format_exc())
1742 Session().rollback()
1742 Session().rollback()
1743 return invalidated_keys
1743 return invalidated_keys
1744
1744
1745 @classmethod
1745 @classmethod
1746 def set_valid(cls, key):
1746 def set_valid(cls, key):
1747 """
1747 """
1748 Mark this cache key as active and currently cached
1748 Mark this cache key as active and currently cached
1749
1749
1750 :param key:
1750 :param key:
1751 """
1751 """
1752 inv_obj = cls.get_by_key(key)
1752 inv_obj = cls.get_by_key(key)
1753 inv_obj.cache_active = True
1753 inv_obj.cache_active = True
1754 Session().add(inv_obj)
1754 Session().add(inv_obj)
1755 Session().commit()
1755 Session().commit()
1756
1756
1757 @classmethod
1757 @classmethod
1758 def get_cache_map(cls):
1758 def get_cache_map(cls):
1759
1759
1760 class cachemapdict(dict):
1760 class cachemapdict(dict):
1761
1761
1762 def __init__(self, *args, **kwargs):
1762 def __init__(self, *args, **kwargs):
1763 fixkey = kwargs.get('fixkey')
1763 fixkey = kwargs.get('fixkey')
1764 if fixkey:
1764 if fixkey:
1765 del kwargs['fixkey']
1765 del kwargs['fixkey']
1766 self.fixkey = fixkey
1766 self.fixkey = fixkey
1767 super(cachemapdict, self).__init__(*args, **kwargs)
1767 super(cachemapdict, self).__init__(*args, **kwargs)
1768
1768
1769 def __getattr__(self, name):
1769 def __getattr__(self, name):
1770 key = name
1770 key = name
1771 if self.fixkey:
1771 if self.fixkey:
1772 key, _prefix, _org_key = cls._get_key(key)
1772 key, _prefix, _org_key = cls._get_key(key)
1773 if key in self.__dict__:
1773 if key in self.__dict__:
1774 return self.__dict__[key]
1774 return self.__dict__[key]
1775 else:
1775 else:
1776 return self[key]
1776 return self[key]
1777
1777
1778 def __getitem__(self, key):
1778 def __getitem__(self, key):
1779 if self.fixkey:
1779 if self.fixkey:
1780 key, _prefix, _org_key = cls._get_key(key)
1780 key, _prefix, _org_key = cls._get_key(key)
1781 try:
1781 try:
1782 return super(cachemapdict, self).__getitem__(key)
1782 return super(cachemapdict, self).__getitem__(key)
1783 except KeyError:
1783 except KeyError:
1784 return
1784 return
1785
1785
1786 cache_map = cachemapdict(fixkey=True)
1786 cache_map = cachemapdict(fixkey=True)
1787 for obj in cls.query().all():
1787 for obj in cls.query().all():
1788 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1788 cache_map[obj.cache_key] = cachemapdict(obj.get_dict())
1789 return cache_map
1789 return cache_map
1790
1790
1791
1791
1792 class ChangesetComment(Base, BaseModel):
1792 class ChangesetComment(Base, BaseModel):
1793 __tablename__ = 'changeset_comments'
1793 __tablename__ = 'changeset_comments'
1794 __table_args__ = (
1794 __table_args__ = (
1795 Index('cc_revision_idx', 'revision'),
1795 Index('cc_revision_idx', 'revision'),
1796 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1796 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1797 'mysql_charset': 'utf8'},
1797 'mysql_charset': 'utf8'},
1798 )
1798 )
1799 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1799 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1800 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1800 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1801 revision = Column('revision', String(40), nullable=True)
1801 revision = Column('revision', String(40), nullable=True)
1802 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1802 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1803 line_no = Column('line_no', Unicode(10), nullable=True)
1803 line_no = Column('line_no', Unicode(10), nullable=True)
1804 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1804 hl_lines = Column('hl_lines', Unicode(512), nullable=True)
1805 f_path = Column('f_path', Unicode(1000), nullable=True)
1805 f_path = Column('f_path', Unicode(1000), nullable=True)
1806 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1806 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1807 text = Column('text', UnicodeText(25000), nullable=False)
1807 text = Column('text', UnicodeText(25000), nullable=False)
1808 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1808 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1809 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1809 modified_at = Column('modified_at', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1810
1810
1811 author = relationship('User', lazy='joined')
1811 author = relationship('User', lazy='joined')
1812 repo = relationship('Repository')
1812 repo = relationship('Repository')
1813 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1813 status_change = relationship('ChangesetStatus', cascade="all, delete, delete-orphan")
1814 pull_request = relationship('PullRequest', lazy='joined')
1814 pull_request = relationship('PullRequest', lazy='joined')
1815
1815
1816 @classmethod
1816 @classmethod
1817 def get_users(cls, revision=None, pull_request_id=None):
1817 def get_users(cls, revision=None, pull_request_id=None):
1818 """
1818 """
1819 Returns user associated with this ChangesetComment. ie those
1819 Returns user associated with this ChangesetComment. ie those
1820 who actually commented
1820 who actually commented
1821
1821
1822 :param cls:
1822 :param cls:
1823 :param revision:
1823 :param revision:
1824 """
1824 """
1825 q = Session().query(User)\
1825 q = Session().query(User)\
1826 .join(ChangesetComment.author)
1826 .join(ChangesetComment.author)
1827 if revision:
1827 if revision:
1828 q = q.filter(cls.revision == revision)
1828 q = q.filter(cls.revision == revision)
1829 elif pull_request_id:
1829 elif pull_request_id:
1830 q = q.filter(cls.pull_request_id == pull_request_id)
1830 q = q.filter(cls.pull_request_id == pull_request_id)
1831 return q.all()
1831 return q.all()
1832
1832
1833
1833
1834 class ChangesetStatus(Base, BaseModel):
1834 class ChangesetStatus(Base, BaseModel):
1835 __tablename__ = 'changeset_statuses'
1835 __tablename__ = 'changeset_statuses'
1836 __table_args__ = (
1836 __table_args__ = (
1837 Index('cs_revision_idx', 'revision'),
1837 Index('cs_revision_idx', 'revision'),
1838 Index('cs_version_idx', 'version'),
1838 Index('cs_version_idx', 'version'),
1839 UniqueConstraint('repo_id', 'revision', 'version'),
1839 UniqueConstraint('repo_id', 'revision', 'version'),
1840 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1840 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1841 'mysql_charset': 'utf8'}
1841 'mysql_charset': 'utf8'}
1842 )
1842 )
1843 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1843 STATUS_NOT_REVIEWED = DEFAULT = 'not_reviewed'
1844 STATUS_APPROVED = 'approved'
1844 STATUS_APPROVED = 'approved'
1845 STATUS_REJECTED = 'rejected'
1845 STATUS_REJECTED = 'rejected'
1846 STATUS_UNDER_REVIEW = 'under_review'
1846 STATUS_UNDER_REVIEW = 'under_review'
1847
1847
1848 STATUSES = [
1848 STATUSES = [
1849 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1849 (STATUS_NOT_REVIEWED, _("Not Reviewed")), # (no icon) and default
1850 (STATUS_APPROVED, _("Approved")),
1850 (STATUS_APPROVED, _("Approved")),
1851 (STATUS_REJECTED, _("Rejected")),
1851 (STATUS_REJECTED, _("Rejected")),
1852 (STATUS_UNDER_REVIEW, _("Under Review")),
1852 (STATUS_UNDER_REVIEW, _("Under Review")),
1853 ]
1853 ]
1854
1854
1855 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1855 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1856 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1856 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1857 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1857 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1858 revision = Column('revision', String(40), nullable=False)
1858 revision = Column('revision', String(40), nullable=False)
1859 status = Column('status', String(128), nullable=False, default=DEFAULT)
1859 status = Column('status', String(128), nullable=False, default=DEFAULT)
1860 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1860 changeset_comment_id = Column('changeset_comment_id', Integer(), ForeignKey('changeset_comments.comment_id'))
1861 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1861 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1862 version = Column('version', Integer(), nullable=False, default=0)
1862 version = Column('version', Integer(), nullable=False, default=0)
1863 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1863 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=True)
1864
1864
1865 author = relationship('User', lazy='joined')
1865 author = relationship('User', lazy='joined')
1866 repo = relationship('Repository')
1866 repo = relationship('Repository')
1867 comment = relationship('ChangesetComment', lazy='joined')
1867 comment = relationship('ChangesetComment', lazy='joined')
1868 pull_request = relationship('PullRequest', lazy='joined')
1868 pull_request = relationship('PullRequest', lazy='joined')
1869
1869
1870 def __unicode__(self):
1870 def __unicode__(self):
1871 return u"<%s('%s:%s')>" % (
1871 return u"<%s('%s:%s')>" % (
1872 self.__class__.__name__,
1872 self.__class__.__name__,
1873 self.status, self.author
1873 self.status, self.author
1874 )
1874 )
1875
1875
1876 @classmethod
1876 @classmethod
1877 def get_status_lbl(cls, value):
1877 def get_status_lbl(cls, value):
1878 return dict(cls.STATUSES).get(value)
1878 return dict(cls.STATUSES).get(value)
1879
1879
1880 @property
1880 @property
1881 def status_lbl(self):
1881 def status_lbl(self):
1882 return ChangesetStatus.get_status_lbl(self.status)
1882 return ChangesetStatus.get_status_lbl(self.status)
1883
1883
1884
1884
1885 class PullRequest(Base, BaseModel):
1885 class PullRequest(Base, BaseModel):
1886 __tablename__ = 'pull_requests'
1886 __tablename__ = 'pull_requests'
1887 __table_args__ = (
1887 __table_args__ = (
1888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1888 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1889 'mysql_charset': 'utf8'},
1889 'mysql_charset': 'utf8'},
1890 )
1890 )
1891
1891
1892 STATUS_NEW = u'new'
1892 STATUS_NEW = u'new'
1893 STATUS_OPEN = u'open'
1893 STATUS_OPEN = u'open'
1894 STATUS_CLOSED = u'closed'
1894 STATUS_CLOSED = u'closed'
1895
1895
1896 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1896 pull_request_id = Column('pull_request_id', Integer(), nullable=False, primary_key=True)
1897 title = Column('title', Unicode(256), nullable=True)
1897 title = Column('title', Unicode(256), nullable=True)
1898 description = Column('description', UnicodeText(10240), nullable=True)
1898 description = Column('description', UnicodeText(10240), nullable=True)
1899 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1899 status = Column('status', Unicode(256), nullable=False, default=STATUS_NEW)
1900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1900 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1901 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1901 updated_on = Column('updated_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1902 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1902 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1903 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1903 _revisions = Column('revisions', UnicodeText(20500)) # 500 revisions max
1904 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1904 org_repo_id = Column('org_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1905 org_ref = Column('org_ref', Unicode(256), nullable=False)
1905 org_ref = Column('org_ref', Unicode(256), nullable=False)
1906 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1906 other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1907 other_ref = Column('other_ref', Unicode(256), nullable=False)
1907 other_ref = Column('other_ref', Unicode(256), nullable=False)
1908
1908
1909 @hybrid_property
1909 @hybrid_property
1910 def revisions(self):
1910 def revisions(self):
1911 return self._revisions.split(':')
1911 return self._revisions.split(':')
1912
1912
1913 @revisions.setter
1913 @revisions.setter
1914 def revisions(self, val):
1914 def revisions(self, val):
1915 self._revisions = ':'.join(val)
1915 self._revisions = ':'.join(val)
1916
1916
1917 @property
1917 @property
1918 def org_ref_parts(self):
1918 def org_ref_parts(self):
1919 return self.org_ref.split(':')
1919 return self.org_ref.split(':')
1920
1920
1921 @property
1921 @property
1922 def other_ref_parts(self):
1922 def other_ref_parts(self):
1923 return self.other_ref.split(':')
1923 return self.other_ref.split(':')
1924
1924
1925 author = relationship('User', lazy='joined')
1925 author = relationship('User', lazy='joined')
1926 reviewers = relationship('PullRequestReviewers',
1926 reviewers = relationship('PullRequestReviewers',
1927 cascade="all, delete, delete-orphan")
1927 cascade="all, delete, delete-orphan")
1928 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1928 org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
1929 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1929 other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
1930 statuses = relationship('ChangesetStatus')
1930 statuses = relationship('ChangesetStatus')
1931 comments = relationship('ChangesetComment',
1931 comments = relationship('ChangesetComment',
1932 cascade="all, delete, delete-orphan")
1932 cascade="all, delete, delete-orphan")
1933
1933
1934 def is_closed(self):
1934 def is_closed(self):
1935 return self.status == self.STATUS_CLOSED
1935 return self.status == self.STATUS_CLOSED
1936
1936
1937 @property
1937 @property
1938 def last_review_status(self):
1938 def last_review_status(self):
1939 return self.statuses[-1].status if self.statuses else ''
1939 return self.statuses[-1].status if self.statuses else ''
1940
1940
1941 def __json__(self):
1941 def __json__(self):
1942 return dict(
1942 return dict(
1943 revisions=self.revisions
1943 revisions=self.revisions
1944 )
1944 )
1945
1945
1946
1946
1947 class PullRequestReviewers(Base, BaseModel):
1947 class PullRequestReviewers(Base, BaseModel):
1948 __tablename__ = 'pull_request_reviewers'
1948 __tablename__ = 'pull_request_reviewers'
1949 __table_args__ = (
1949 __table_args__ = (
1950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1950 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1951 'mysql_charset': 'utf8'},
1951 'mysql_charset': 'utf8'},
1952 )
1952 )
1953
1953
1954 def __init__(self, user=None, pull_request=None):
1954 def __init__(self, user=None, pull_request=None):
1955 self.user = user
1955 self.user = user
1956 self.pull_request = pull_request
1956 self.pull_request = pull_request
1957
1957
1958 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1958 pull_requests_reviewers_id = Column('pull_requests_reviewers_id', Integer(), nullable=False, primary_key=True)
1959 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1959 pull_request_id = Column("pull_request_id", Integer(), ForeignKey('pull_requests.pull_request_id'), nullable=False)
1960 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1960 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=True)
1961
1961
1962 user = relationship('User')
1962 user = relationship('User')
1963 pull_request = relationship('PullRequest')
1963 pull_request = relationship('PullRequest')
1964
1964
1965
1965
1966 class Notification(Base, BaseModel):
1966 class Notification(Base, BaseModel):
1967 __tablename__ = 'notifications'
1967 __tablename__ = 'notifications'
1968 __table_args__ = (
1968 __table_args__ = (
1969 Index('notification_type_idx', 'type'),
1969 Index('notification_type_idx', 'type'),
1970 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1970 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1971 'mysql_charset': 'utf8'},
1971 'mysql_charset': 'utf8'},
1972 )
1972 )
1973
1973
1974 TYPE_CHANGESET_COMMENT = u'cs_comment'
1974 TYPE_CHANGESET_COMMENT = u'cs_comment'
1975 TYPE_MESSAGE = u'message'
1975 TYPE_MESSAGE = u'message'
1976 TYPE_MENTION = u'mention'
1976 TYPE_MENTION = u'mention'
1977 TYPE_REGISTRATION = u'registration'
1977 TYPE_REGISTRATION = u'registration'
1978 TYPE_PULL_REQUEST = u'pull_request'
1978 TYPE_PULL_REQUEST = u'pull_request'
1979 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1979 TYPE_PULL_REQUEST_COMMENT = u'pull_request_comment'
1980
1980
1981 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1981 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1982 subject = Column('subject', Unicode(512), nullable=True)
1982 subject = Column('subject', Unicode(512), nullable=True)
1983 body = Column('body', UnicodeText(50000), nullable=True)
1983 body = Column('body', UnicodeText(50000), nullable=True)
1984 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1984 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1985 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1985 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1986 type_ = Column('type', Unicode(256))
1986 type_ = Column('type', Unicode(256))
1987
1987
1988 created_by_user = relationship('User')
1988 created_by_user = relationship('User')
1989 notifications_to_users = relationship('UserNotification', lazy='joined',
1989 notifications_to_users = relationship('UserNotification', lazy='joined',
1990 cascade="all, delete, delete-orphan")
1990 cascade="all, delete, delete-orphan")
1991
1991
1992 @property
1992 @property
1993 def recipients(self):
1993 def recipients(self):
1994 return [x.user for x in UserNotification.query()\
1994 return [x.user for x in UserNotification.query()\
1995 .filter(UserNotification.notification == self)\
1995 .filter(UserNotification.notification == self)\
1996 .order_by(UserNotification.user_id.asc()).all()]
1996 .order_by(UserNotification.user_id.asc()).all()]
1997
1997
1998 @classmethod
1998 @classmethod
1999 def create(cls, created_by, subject, body, recipients, type_=None):
1999 def create(cls, created_by, subject, body, recipients, type_=None):
2000 if type_ is None:
2000 if type_ is None:
2001 type_ = Notification.TYPE_MESSAGE
2001 type_ = Notification.TYPE_MESSAGE
2002
2002
2003 notification = cls()
2003 notification = cls()
2004 notification.created_by_user = created_by
2004 notification.created_by_user = created_by
2005 notification.subject = subject
2005 notification.subject = subject
2006 notification.body = body
2006 notification.body = body
2007 notification.type_ = type_
2007 notification.type_ = type_
2008 notification.created_on = datetime.datetime.now()
2008 notification.created_on = datetime.datetime.now()
2009
2009
2010 for u in recipients:
2010 for u in recipients:
2011 assoc = UserNotification()
2011 assoc = UserNotification()
2012 assoc.notification = notification
2012 assoc.notification = notification
2013 u.notifications.append(assoc)
2013 u.notifications.append(assoc)
2014 Session().add(notification)
2014 Session().add(notification)
2015 return notification
2015 return notification
2016
2016
2017 @property
2017 @property
2018 def description(self):
2018 def description(self):
2019 from rhodecode.model.notification import NotificationModel
2019 from rhodecode.model.notification import NotificationModel
2020 return NotificationModel().make_description(self)
2020 return NotificationModel().make_description(self)
2021
2021
2022
2022
2023 class UserNotification(Base, BaseModel):
2023 class UserNotification(Base, BaseModel):
2024 __tablename__ = 'user_to_notification'
2024 __tablename__ = 'user_to_notification'
2025 __table_args__ = (
2025 __table_args__ = (
2026 UniqueConstraint('user_id', 'notification_id'),
2026 UniqueConstraint('user_id', 'notification_id'),
2027 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2027 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2028 'mysql_charset': 'utf8'}
2028 'mysql_charset': 'utf8'}
2029 )
2029 )
2030 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2030 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
2031 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2031 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
2032 read = Column('read', Boolean, default=False)
2032 read = Column('read', Boolean, default=False)
2033 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2033 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
2034
2034
2035 user = relationship('User', lazy="joined")
2035 user = relationship('User', lazy="joined")
2036 notification = relationship('Notification', lazy="joined",
2036 notification = relationship('Notification', lazy="joined",
2037 order_by=lambda: Notification.created_on.desc(),)
2037 order_by=lambda: Notification.created_on.desc(),)
2038
2038
2039 def mark_as_read(self):
2039 def mark_as_read(self):
2040 self.read = True
2040 self.read = True
2041 Session().add(self)
2041 Session().add(self)
2042
2042
2043
2043
2044 class DbMigrateVersion(Base, BaseModel):
2044 class DbMigrateVersion(Base, BaseModel):
2045 __tablename__ = 'db_migrate_version'
2045 __tablename__ = 'db_migrate_version'
2046 __table_args__ = (
2046 __table_args__ = (
2047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2047 {'extend_existing': True, 'mysql_engine': 'InnoDB',
2048 'mysql_charset': 'utf8'},
2048 'mysql_charset': 'utf8'},
2049 )
2049 )
2050 repository_id = Column('repository_id', String(250), primary_key=True)
2050 repository_id = Column('repository_id', String(250), primary_key=True)
2051 repository_path = Column('repository_path', Text)
2051 repository_path = Column('repository_path', Text)
2052 version = Column('version', Integer)
2052 version = Column('version', Integer)
@@ -1,700 +1,700 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 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 shutil
27 import shutil
28 import logging
28 import logging
29 import traceback
29 import traceback
30 from datetime import datetime
30 from datetime import datetime
31
31
32 from rhodecode.lib.vcs.backends import get_backend
32 from rhodecode.lib.vcs.backends import get_backend
33 from rhodecode.lib.compat import json
33 from rhodecode.lib.compat import json
34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\
34 from rhodecode.lib.utils2 import LazyProperty, safe_str, safe_unicode,\
35 remove_prefix, obfuscate_url_pw
35 remove_prefix, obfuscate_url_pw
36 from rhodecode.lib.caching_query import FromCache
36 from rhodecode.lib.caching_query import FromCache
37 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
37 from rhodecode.lib.hooks import log_create_repository, log_delete_repository
38
38
39 from rhodecode.model import BaseModel
39 from rhodecode.model import BaseModel
40 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
40 from rhodecode.model.db import Repository, UserRepoToPerm, User, Permission, \
41 Statistics, UserGroup, UserGroupRepoToPerm, RhodeCodeUi, RepoGroup,\
41 Statistics, UserGroup, UserGroupRepoToPerm, RhodeCodeUi, RepoGroup,\
42 RhodeCodeSetting, RepositoryField
42 RhodeCodeSetting, RepositoryField
43 from rhodecode.lib import helpers as h
43 from rhodecode.lib import helpers as h
44 from rhodecode.lib.auth import HasRepoPermissionAny
44 from rhodecode.lib.auth import HasRepoPermissionAny
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48
48
49 class RepoModel(BaseModel):
49 class RepoModel(BaseModel):
50
50
51 cls = Repository
51 cls = Repository
52 URL_SEPARATOR = Repository.url_sep()
52 URL_SEPARATOR = Repository.url_sep()
53
53
54 def __get_users_group(self, users_group):
54 def __get_users_group(self, users_group):
55 return self._get_instance(UserGroup, users_group,
55 return self._get_instance(UserGroup, users_group,
56 callback=UserGroup.get_by_group_name)
56 callback=UserGroup.get_by_group_name)
57
57
58 def _get_repos_group(self, repos_group):
58 def _get_repos_group(self, repos_group):
59 return self._get_instance(RepoGroup, repos_group,
59 return self._get_instance(RepoGroup, repos_group,
60 callback=RepoGroup.get_by_group_name)
60 callback=RepoGroup.get_by_group_name)
61
61
62 @LazyProperty
62 @LazyProperty
63 def repos_path(self):
63 def repos_path(self):
64 """
64 """
65 Get's the repositories root path from database
65 Get's the repositories root path from database
66 """
66 """
67
67
68 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
68 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
69 return q.ui_value
69 return q.ui_value
70
70
71 def get(self, repo_id, cache=False):
71 def get(self, repo_id, cache=False):
72 repo = self.sa.query(Repository)\
72 repo = self.sa.query(Repository)\
73 .filter(Repository.repo_id == repo_id)
73 .filter(Repository.repo_id == repo_id)
74
74
75 if cache:
75 if cache:
76 repo = repo.options(FromCache("sql_cache_short",
76 repo = repo.options(FromCache("sql_cache_short",
77 "get_repo_%s" % repo_id))
77 "get_repo_%s" % repo_id))
78 return repo.scalar()
78 return repo.scalar()
79
79
80 def get_repo(self, repository):
80 def get_repo(self, repository):
81 return self._get_repo(repository)
81 return self._get_repo(repository)
82
82
83 def get_by_repo_name(self, repo_name, cache=False):
83 def get_by_repo_name(self, repo_name, cache=False):
84 repo = self.sa.query(Repository)\
84 repo = self.sa.query(Repository)\
85 .filter(Repository.repo_name == repo_name)
85 .filter(Repository.repo_name == repo_name)
86
86
87 if cache:
87 if cache:
88 repo = repo.options(FromCache("sql_cache_short",
88 repo = repo.options(FromCache("sql_cache_short",
89 "get_repo_%s" % repo_name))
89 "get_repo_%s" % repo_name))
90 return repo.scalar()
90 return repo.scalar()
91
91
92 def get_all_user_repos(self, user):
92 def get_all_user_repos(self, user):
93 """
93 """
94 Get's all repositories that user have at least read access
94 Get's all repositories that user have at least read access
95
95
96 :param user:
96 :param user:
97 :type user:
97 :type user:
98 """
98 """
99 from rhodecode.lib.auth import AuthUser
99 from rhodecode.lib.auth import AuthUser
100 user = self._get_user(user)
100 user = self._get_user(user)
101 repos = AuthUser(user_id=user.user_id).permissions['repositories']
101 repos = AuthUser(user_id=user.user_id).permissions['repositories']
102 access_check = lambda r: r[1] in ['repository.read',
102 access_check = lambda r: r[1] in ['repository.read',
103 'repository.write',
103 'repository.write',
104 'repository.admin']
104 'repository.admin']
105 repos = [x[0] for x in filter(access_check, repos.items())]
105 repos = [x[0] for x in filter(access_check, repos.items())]
106 return Repository.query().filter(Repository.repo_name.in_(repos))
106 return Repository.query().filter(Repository.repo_name.in_(repos))
107
107
108 def get_users_js(self):
108 def get_users_js(self):
109 users = self.sa.query(User).filter(User.active == True).all()
109 users = self.sa.query(User).filter(User.active == True).all()
110 return json.dumps([
110 return json.dumps([
111 {
111 {
112 'id': u.user_id,
112 'id': u.user_id,
113 'fname': u.name,
113 'fname': u.name,
114 'lname': u.lastname,
114 'lname': u.lastname,
115 'nname': u.username,
115 'nname': u.username,
116 'gravatar_lnk': h.gravatar_url(u.email, 14)
116 'gravatar_lnk': h.gravatar_url(u.email, 14)
117 } for u in users]
117 } for u in users]
118 )
118 )
119
119
120 def get_users_groups_js(self):
120 def get_users_groups_js(self):
121 users_groups = self.sa.query(UserGroup)\
121 users_groups = self.sa.query(UserGroup)\
122 .filter(UserGroup.users_group_active == True).all()
122 .filter(UserGroup.users_group_active == True).all()
123
123
124 return json.dumps([
124 return json.dumps([
125 {
125 {
126 'id': gr.users_group_id,
126 'id': gr.users_group_id,
127 'grname': gr.users_group_name,
127 'grname': gr.users_group_name,
128 'grmembers': len(gr.members),
128 'grmembers': len(gr.members),
129 } for gr in users_groups]
129 } for gr in users_groups]
130 )
130 )
131
131
132 @classmethod
132 @classmethod
133 def _render_datatable(cls, tmpl, *args, **kwargs):
133 def _render_datatable(cls, tmpl, *args, **kwargs):
134 import rhodecode
134 import rhodecode
135 from pylons import tmpl_context as c
135 from pylons import tmpl_context as c
136 from pylons.i18n.translation import _
136 from pylons.i18n.translation import _
137
137
138 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
138 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
139 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
139 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
140
140
141 tmpl = template.get_def(tmpl)
141 tmpl = template.get_def(tmpl)
142 kwargs.update(dict(_=_, h=h, c=c))
142 kwargs.update(dict(_=_, h=h, c=c))
143 return tmpl.render(*args, **kwargs)
143 return tmpl.render(*args, **kwargs)
144
144
145 @classmethod
145 @classmethod
146 def update_repoinfo(cls, repositories=None):
146 def update_repoinfo(cls, repositories=None):
147 if not repositories:
147 if not repositories:
148 repositories = Repository.getAll()
148 repositories = Repository.getAll()
149 for repo in repositories:
149 for repo in repositories:
150 repo.update_changeset_cache()
150 repo.update_changeset_cache()
151
151
152 def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
152 def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
153 super_user_actions=False):
153 super_user_actions=False):
154 _render = self._render_datatable
154 _render = self._render_datatable
155
155
156 def quick_menu(repo_name):
156 def quick_menu(repo_name):
157 return _render('quick_menu', repo_name)
157 return _render('quick_menu', repo_name)
158
158
159 def repo_lnk(name, rtype, private, fork_of):
159 def repo_lnk(name, rtype, private, fork_of):
160 return _render('repo_name', name, rtype, private, fork_of,
160 return _render('repo_name', name, rtype, private, fork_of,
161 short_name=not admin, admin=False)
161 short_name=not admin, admin=False)
162
162
163 def last_change(last_change):
163 def last_change(last_change):
164 return _render("last_change", last_change)
164 return _render("last_change", last_change)
165
165
166 def rss_lnk(repo_name):
166 def rss_lnk(repo_name):
167 return _render("rss", repo_name)
167 return _render("rss", repo_name)
168
168
169 def atom_lnk(repo_name):
169 def atom_lnk(repo_name):
170 return _render("atom", repo_name)
170 return _render("atom", repo_name)
171
171
172 def last_rev(repo_name, cs_cache):
172 def last_rev(repo_name, cs_cache):
173 return _render('revision', repo_name, cs_cache.get('revision'),
173 return _render('revision', repo_name, cs_cache.get('revision'),
174 cs_cache.get('raw_id'), cs_cache.get('author'),
174 cs_cache.get('raw_id'), cs_cache.get('author'),
175 cs_cache.get('message'))
175 cs_cache.get('message'))
176
176
177 def desc(desc):
177 def desc(desc):
178 from pylons import tmpl_context as c
178 from pylons import tmpl_context as c
179 if c.visual.stylify_metatags:
179 if c.visual.stylify_metatags:
180 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
180 return h.urlify_text(h.desc_stylize(h.truncate(desc, 60)))
181 else:
181 else:
182 return h.urlify_text(h.truncate(desc, 60))
182 return h.urlify_text(h.truncate(desc, 60))
183
183
184 def repo_actions(repo_name):
184 def repo_actions(repo_name):
185 return _render('repo_actions', repo_name, super_user_actions)
185 return _render('repo_actions', repo_name, super_user_actions)
186
186
187 def owner_actions(user_id, username):
187 def owner_actions(user_id, username):
188 return _render('user_name', user_id, username)
188 return _render('user_name', user_id, username)
189
189
190 repos_data = []
190 repos_data = []
191 for repo in repos_list:
191 for repo in repos_list:
192 if perm_check:
192 if perm_check:
193 # check permission at this level
193 # check permission at this level
194 if not HasRepoPermissionAny(
194 if not HasRepoPermissionAny(
195 'repository.read', 'repository.write', 'repository.admin'
195 'repository.read', 'repository.write', 'repository.admin'
196 )(repo.repo_name, 'get_repos_as_dict check'):
196 )(repo.repo_name, 'get_repos_as_dict check'):
197 continue
197 continue
198 cs_cache = repo.changeset_cache
198 cs_cache = repo.changeset_cache
199 row = {
199 row = {
200 "menu": quick_menu(repo.repo_name),
200 "menu": quick_menu(repo.repo_name),
201 "raw_name": repo.repo_name.lower(),
201 "raw_name": repo.repo_name.lower(),
202 "name": repo_lnk(repo.repo_name, repo.repo_type,
202 "name": repo_lnk(repo.repo_name, repo.repo_type,
203 repo.private, repo.fork),
203 repo.private, repo.fork),
204 "last_change": last_change(repo.last_db_change),
204 "last_change": last_change(repo.last_db_change),
205 "last_changeset": last_rev(repo.repo_name, cs_cache),
205 "last_changeset": last_rev(repo.repo_name, cs_cache),
206 "raw_tip": cs_cache.get('revision'),
206 "raw_tip": cs_cache.get('revision'),
207 "desc": desc(repo.description),
207 "desc": desc(repo.description),
208 "owner": h.person(repo.user.username),
208 "owner": h.person(repo.user.username),
209 "rss": rss_lnk(repo.repo_name),
209 "rss": rss_lnk(repo.repo_name),
210 "atom": atom_lnk(repo.repo_name),
210 "atom": atom_lnk(repo.repo_name),
211
211
212 }
212 }
213 if admin:
213 if admin:
214 row.update({
214 row.update({
215 "action": repo_actions(repo.repo_name),
215 "action": repo_actions(repo.repo_name),
216 "owner": owner_actions(repo.user.user_id,
216 "owner": owner_actions(repo.user.user_id,
217 h.person(repo.user.username))
217 h.person(repo.user.username))
218 })
218 })
219 repos_data.append(row)
219 repos_data.append(row)
220
220
221 return {
221 return {
222 "totalRecords": len(repos_list),
222 "totalRecords": len(repos_list),
223 "startIndex": 0,
223 "startIndex": 0,
224 "sort": "name",
224 "sort": "name",
225 "dir": "asc",
225 "dir": "asc",
226 "records": repos_data
226 "records": repos_data
227 }
227 }
228
228
229 def _get_defaults(self, repo_name):
229 def _get_defaults(self, repo_name):
230 """
230 """
231 Get's information about repository, and returns a dict for
231 Get's information about repository, and returns a dict for
232 usage in forms
232 usage in forms
233
233
234 :param repo_name:
234 :param repo_name:
235 """
235 """
236
236
237 repo_info = Repository.get_by_repo_name(repo_name)
237 repo_info = Repository.get_by_repo_name(repo_name)
238
238
239 if repo_info is None:
239 if repo_info is None:
240 return None
240 return None
241
241
242 defaults = repo_info.get_dict()
242 defaults = repo_info.get_dict()
243 group, repo_name = repo_info.groups_and_repo
243 group, repo_name, repo_name_full = repo_info.groups_and_repo
244 defaults['repo_name'] = repo_name
244 defaults['repo_name'] = repo_name
245 defaults['repo_group'] = getattr(group[-1] if group else None,
245 defaults['repo_group'] = getattr(group[-1] if group else None,
246 'group_id', None)
246 'group_id', None)
247
247
248 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
248 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
249 (1, 'repo_description'), (1, 'repo_enable_locking'),
249 (1, 'repo_description'), (1, 'repo_enable_locking'),
250 (1, 'repo_landing_rev'), (0, 'clone_uri'),
250 (1, 'repo_landing_rev'), (0, 'clone_uri'),
251 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
251 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
252 attr = k
252 attr = k
253 if strip:
253 if strip:
254 attr = remove_prefix(k, 'repo_')
254 attr = remove_prefix(k, 'repo_')
255
255
256 defaults[k] = defaults[attr]
256 defaults[k] = defaults[attr]
257
257
258 # fill owner
258 # fill owner
259 if repo_info.user:
259 if repo_info.user:
260 defaults.update({'user': repo_info.user.username})
260 defaults.update({'user': repo_info.user.username})
261 else:
261 else:
262 replacement_user = User.query().filter(User.admin ==
262 replacement_user = User.query().filter(User.admin ==
263 True).first().username
263 True).first().username
264 defaults.update({'user': replacement_user})
264 defaults.update({'user': replacement_user})
265
265
266 # fill repository users
266 # fill repository users
267 for p in repo_info.repo_to_perm:
267 for p in repo_info.repo_to_perm:
268 defaults.update({'u_perm_%s' % p.user.username:
268 defaults.update({'u_perm_%s' % p.user.username:
269 p.permission.permission_name})
269 p.permission.permission_name})
270
270
271 # fill repository groups
271 # fill repository groups
272 for p in repo_info.users_group_to_perm:
272 for p in repo_info.users_group_to_perm:
273 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
273 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
274 p.permission.permission_name})
274 p.permission.permission_name})
275
275
276 return defaults
276 return defaults
277
277
278 def update(self, org_repo_name, **kwargs):
278 def update(self, org_repo_name, **kwargs):
279 try:
279 try:
280 cur_repo = self.get_by_repo_name(org_repo_name, cache=False)
280 cur_repo = self.get_by_repo_name(org_repo_name, cache=False)
281
281
282 # update permissions
282 # update permissions
283 for member, perm, member_type in kwargs['perms_updates']:
283 for member, perm, member_type in kwargs['perms_updates']:
284 if member_type == 'user':
284 if member_type == 'user':
285 # this updates existing one
285 # this updates existing one
286 RepoModel().grant_user_permission(
286 RepoModel().grant_user_permission(
287 repo=cur_repo, user=member, perm=perm
287 repo=cur_repo, user=member, perm=perm
288 )
288 )
289 else:
289 else:
290 RepoModel().grant_users_group_permission(
290 RepoModel().grant_users_group_permission(
291 repo=cur_repo, group_name=member, perm=perm
291 repo=cur_repo, group_name=member, perm=perm
292 )
292 )
293 # set new permissions
293 # set new permissions
294 for member, perm, member_type in kwargs['perms_new']:
294 for member, perm, member_type in kwargs['perms_new']:
295 if member_type == 'user':
295 if member_type == 'user':
296 RepoModel().grant_user_permission(
296 RepoModel().grant_user_permission(
297 repo=cur_repo, user=member, perm=perm
297 repo=cur_repo, user=member, perm=perm
298 )
298 )
299 else:
299 else:
300 RepoModel().grant_users_group_permission(
300 RepoModel().grant_users_group_permission(
301 repo=cur_repo, group_name=member, perm=perm
301 repo=cur_repo, group_name=member, perm=perm
302 )
302 )
303
303
304 if 'user' in kwargs:
304 if 'user' in kwargs:
305 cur_repo.user = User.get_by_username(kwargs['user'])
305 cur_repo.user = User.get_by_username(kwargs['user'])
306
306
307 if 'repo_group' in kwargs:
307 if 'repo_group' in kwargs:
308 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
308 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
309
309
310 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
310 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
311 (1, 'repo_description'), (1, 'repo_enable_locking'),
311 (1, 'repo_description'), (1, 'repo_enable_locking'),
312 (1, 'repo_landing_rev'), (0, 'clone_uri'),
312 (1, 'repo_landing_rev'), (0, 'clone_uri'),
313 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
313 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
314 if k in kwargs:
314 if k in kwargs:
315 val = kwargs[k]
315 val = kwargs[k]
316 if strip:
316 if strip:
317 k = remove_prefix(k, 'repo_')
317 k = remove_prefix(k, 'repo_')
318 setattr(cur_repo, k, val)
318 setattr(cur_repo, k, val)
319
319
320 new_name = cur_repo.get_new_name(kwargs['repo_name'])
320 new_name = cur_repo.get_new_name(kwargs['repo_name'])
321 cur_repo.repo_name = new_name
321 cur_repo.repo_name = new_name
322
322
323 #handle extra fields
323 #handle extra fields
324 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
324 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX), kwargs):
325 k = RepositoryField.un_prefix_key(field)
325 k = RepositoryField.un_prefix_key(field)
326 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
326 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
327 if ex_field:
327 if ex_field:
328 ex_field.field_value = kwargs[field]
328 ex_field.field_value = kwargs[field]
329 self.sa.add(ex_field)
329 self.sa.add(ex_field)
330 self.sa.add(cur_repo)
330 self.sa.add(cur_repo)
331
331
332 if org_repo_name != new_name:
332 if org_repo_name != new_name:
333 # rename repository
333 # rename repository
334 self.__rename_repo(old=org_repo_name, new=new_name)
334 self.__rename_repo(old=org_repo_name, new=new_name)
335
335
336 return cur_repo
336 return cur_repo
337 except:
337 except:
338 log.error(traceback.format_exc())
338 log.error(traceback.format_exc())
339 raise
339 raise
340
340
341 def create_repo(self, repo_name, repo_type, description, owner,
341 def create_repo(self, repo_name, repo_type, description, owner,
342 private=False, clone_uri=None, repos_group=None,
342 private=False, clone_uri=None, repos_group=None,
343 landing_rev='tip', just_db=False, fork_of=None,
343 landing_rev='tip', just_db=False, fork_of=None,
344 copy_fork_permissions=False, enable_statistics=False,
344 copy_fork_permissions=False, enable_statistics=False,
345 enable_locking=False, enable_downloads=False):
345 enable_locking=False, enable_downloads=False):
346 """
346 """
347 Create repository
347 Create repository
348
348
349 """
349 """
350 from rhodecode.model.scm import ScmModel
350 from rhodecode.model.scm import ScmModel
351
351
352 owner = self._get_user(owner)
352 owner = self._get_user(owner)
353 fork_of = self._get_repo(fork_of)
353 fork_of = self._get_repo(fork_of)
354 repos_group = self._get_repos_group(repos_group)
354 repos_group = self._get_repos_group(repos_group)
355 try:
355 try:
356
356
357 # repo name is just a name of repository
357 # repo name is just a name of repository
358 # while repo_name_full is a full qualified name that is combined
358 # while repo_name_full is a full qualified name that is combined
359 # with name and path of group
359 # with name and path of group
360 repo_name_full = repo_name
360 repo_name_full = repo_name
361 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
361 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
362
362
363 new_repo = Repository()
363 new_repo = Repository()
364 new_repo.enable_statistics = False
364 new_repo.enable_statistics = False
365 new_repo.repo_name = repo_name_full
365 new_repo.repo_name = repo_name_full
366 new_repo.repo_type = repo_type
366 new_repo.repo_type = repo_type
367 new_repo.user = owner
367 new_repo.user = owner
368 new_repo.group = repos_group
368 new_repo.group = repos_group
369 new_repo.description = description or repo_name
369 new_repo.description = description or repo_name
370 new_repo.private = private
370 new_repo.private = private
371 new_repo.clone_uri = clone_uri
371 new_repo.clone_uri = clone_uri
372 new_repo.landing_rev = landing_rev
372 new_repo.landing_rev = landing_rev
373
373
374 new_repo.enable_statistics = enable_statistics
374 new_repo.enable_statistics = enable_statistics
375 new_repo.enable_locking = enable_locking
375 new_repo.enable_locking = enable_locking
376 new_repo.enable_downloads = enable_downloads
376 new_repo.enable_downloads = enable_downloads
377
377
378 if repos_group:
378 if repos_group:
379 new_repo.enable_locking = repos_group.enable_locking
379 new_repo.enable_locking = repos_group.enable_locking
380
380
381 if fork_of:
381 if fork_of:
382 parent_repo = fork_of
382 parent_repo = fork_of
383 new_repo.fork = parent_repo
383 new_repo.fork = parent_repo
384
384
385 self.sa.add(new_repo)
385 self.sa.add(new_repo)
386
386
387 def _create_default_perms():
387 def _create_default_perms():
388 # create default permission
388 # create default permission
389 repo_to_perm = UserRepoToPerm()
389 repo_to_perm = UserRepoToPerm()
390 default = 'repository.read'
390 default = 'repository.read'
391 for p in User.get_by_username('default').user_perms:
391 for p in User.get_by_username('default').user_perms:
392 if p.permission.permission_name.startswith('repository.'):
392 if p.permission.permission_name.startswith('repository.'):
393 default = p.permission.permission_name
393 default = p.permission.permission_name
394 break
394 break
395
395
396 default_perm = 'repository.none' if private else default
396 default_perm = 'repository.none' if private else default
397
397
398 repo_to_perm.permission_id = self.sa.query(Permission)\
398 repo_to_perm.permission_id = self.sa.query(Permission)\
399 .filter(Permission.permission_name == default_perm)\
399 .filter(Permission.permission_name == default_perm)\
400 .one().permission_id
400 .one().permission_id
401
401
402 repo_to_perm.repository = new_repo
402 repo_to_perm.repository = new_repo
403 repo_to_perm.user_id = User.get_by_username('default').user_id
403 repo_to_perm.user_id = User.get_by_username('default').user_id
404
404
405 self.sa.add(repo_to_perm)
405 self.sa.add(repo_to_perm)
406
406
407 if fork_of:
407 if fork_of:
408 if copy_fork_permissions:
408 if copy_fork_permissions:
409 repo = fork_of
409 repo = fork_of
410 user_perms = UserRepoToPerm.query()\
410 user_perms = UserRepoToPerm.query()\
411 .filter(UserRepoToPerm.repository == repo).all()
411 .filter(UserRepoToPerm.repository == repo).all()
412 group_perms = UserGroupRepoToPerm.query()\
412 group_perms = UserGroupRepoToPerm.query()\
413 .filter(UserGroupRepoToPerm.repository == repo).all()
413 .filter(UserGroupRepoToPerm.repository == repo).all()
414
414
415 for perm in user_perms:
415 for perm in user_perms:
416 UserRepoToPerm.create(perm.user, new_repo,
416 UserRepoToPerm.create(perm.user, new_repo,
417 perm.permission)
417 perm.permission)
418
418
419 for perm in group_perms:
419 for perm in group_perms:
420 UserGroupRepoToPerm.create(perm.users_group, new_repo,
420 UserGroupRepoToPerm.create(perm.users_group, new_repo,
421 perm.permission)
421 perm.permission)
422 else:
422 else:
423 _create_default_perms()
423 _create_default_perms()
424 else:
424 else:
425 _create_default_perms()
425 _create_default_perms()
426
426
427 if not just_db:
427 if not just_db:
428 self.__create_repo(repo_name, repo_type,
428 self.__create_repo(repo_name, repo_type,
429 repos_group,
429 repos_group,
430 clone_uri)
430 clone_uri)
431 log_create_repository(new_repo.get_dict(),
431 log_create_repository(new_repo.get_dict(),
432 created_by=owner.username)
432 created_by=owner.username)
433
433
434 # now automatically start following this repository as owner
434 # now automatically start following this repository as owner
435 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
435 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
436 owner.user_id)
436 owner.user_id)
437 return new_repo
437 return new_repo
438 except:
438 except:
439 log.error(traceback.format_exc())
439 log.error(traceback.format_exc())
440 raise
440 raise
441
441
442 def create(self, form_data, cur_user, just_db=False, fork=None):
442 def create(self, form_data, cur_user, just_db=False, fork=None):
443 """
443 """
444 Backward compatibility function, just a wrapper on top of create_repo
444 Backward compatibility function, just a wrapper on top of create_repo
445
445
446 :param form_data:
446 :param form_data:
447 :param cur_user:
447 :param cur_user:
448 :param just_db:
448 :param just_db:
449 :param fork:
449 :param fork:
450 """
450 """
451 owner = cur_user
451 owner = cur_user
452 repo_name = form_data['repo_name_full']
452 repo_name = form_data['repo_name_full']
453 repo_type = form_data['repo_type']
453 repo_type = form_data['repo_type']
454 description = form_data['repo_description']
454 description = form_data['repo_description']
455 private = form_data['repo_private']
455 private = form_data['repo_private']
456 clone_uri = form_data.get('clone_uri')
456 clone_uri = form_data.get('clone_uri')
457 repos_group = form_data['repo_group']
457 repos_group = form_data['repo_group']
458 landing_rev = form_data['repo_landing_rev']
458 landing_rev = form_data['repo_landing_rev']
459 copy_fork_permissions = form_data.get('copy_permissions')
459 copy_fork_permissions = form_data.get('copy_permissions')
460 fork_of = form_data.get('fork_parent_id')
460 fork_of = form_data.get('fork_parent_id')
461
461
462 ## repo creation defaults, private and repo_type are filled in form
462 ## repo creation defaults, private and repo_type are filled in form
463 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
463 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
464 enable_statistics = defs.get('repo_enable_statistics')
464 enable_statistics = defs.get('repo_enable_statistics')
465 enable_locking = defs.get('repo_enable_locking')
465 enable_locking = defs.get('repo_enable_locking')
466 enable_downloads = defs.get('repo_enable_downloads')
466 enable_downloads = defs.get('repo_enable_downloads')
467
467
468 return self.create_repo(
468 return self.create_repo(
469 repo_name, repo_type, description, owner, private, clone_uri,
469 repo_name, repo_type, description, owner, private, clone_uri,
470 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions,
470 repos_group, landing_rev, just_db, fork_of, copy_fork_permissions,
471 enable_statistics, enable_locking, enable_downloads
471 enable_statistics, enable_locking, enable_downloads
472 )
472 )
473
473
474 def create_fork(self, form_data, cur_user):
474 def create_fork(self, form_data, cur_user):
475 """
475 """
476 Simple wrapper into executing celery task for fork creation
476 Simple wrapper into executing celery task for fork creation
477
477
478 :param form_data:
478 :param form_data:
479 :param cur_user:
479 :param cur_user:
480 """
480 """
481 from rhodecode.lib.celerylib import tasks, run_task
481 from rhodecode.lib.celerylib import tasks, run_task
482 run_task(tasks.create_repo_fork, form_data, cur_user)
482 run_task(tasks.create_repo_fork, form_data, cur_user)
483
483
484 def delete(self, repo):
484 def delete(self, repo):
485 repo = self._get_repo(repo)
485 repo = self._get_repo(repo)
486 if repo:
486 if repo:
487 old_repo_dict = repo.get_dict()
487 old_repo_dict = repo.get_dict()
488 owner = repo.user
488 owner = repo.user
489 try:
489 try:
490 self.sa.delete(repo)
490 self.sa.delete(repo)
491 self.__delete_repo(repo)
491 self.__delete_repo(repo)
492 log_delete_repository(old_repo_dict,
492 log_delete_repository(old_repo_dict,
493 deleted_by=owner.username)
493 deleted_by=owner.username)
494 except:
494 except:
495 log.error(traceback.format_exc())
495 log.error(traceback.format_exc())
496 raise
496 raise
497
497
498 def grant_user_permission(self, repo, user, perm):
498 def grant_user_permission(self, repo, user, perm):
499 """
499 """
500 Grant permission for user on given repository, or update existing one
500 Grant permission for user on given repository, or update existing one
501 if found
501 if found
502
502
503 :param repo: Instance of Repository, repository_id, or repository name
503 :param repo: Instance of Repository, repository_id, or repository name
504 :param user: Instance of User, user_id or username
504 :param user: Instance of User, user_id or username
505 :param perm: Instance of Permission, or permission_name
505 :param perm: Instance of Permission, or permission_name
506 """
506 """
507 user = self._get_user(user)
507 user = self._get_user(user)
508 repo = self._get_repo(repo)
508 repo = self._get_repo(repo)
509 permission = self._get_perm(perm)
509 permission = self._get_perm(perm)
510
510
511 # check if we have that permission already
511 # check if we have that permission already
512 obj = self.sa.query(UserRepoToPerm)\
512 obj = self.sa.query(UserRepoToPerm)\
513 .filter(UserRepoToPerm.user == user)\
513 .filter(UserRepoToPerm.user == user)\
514 .filter(UserRepoToPerm.repository == repo)\
514 .filter(UserRepoToPerm.repository == repo)\
515 .scalar()
515 .scalar()
516 if obj is None:
516 if obj is None:
517 # create new !
517 # create new !
518 obj = UserRepoToPerm()
518 obj = UserRepoToPerm()
519 obj.repository = repo
519 obj.repository = repo
520 obj.user = user
520 obj.user = user
521 obj.permission = permission
521 obj.permission = permission
522 self.sa.add(obj)
522 self.sa.add(obj)
523 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
523 log.debug('Granted perm %s to %s on %s' % (perm, user, repo))
524
524
525 def revoke_user_permission(self, repo, user):
525 def revoke_user_permission(self, repo, user):
526 """
526 """
527 Revoke permission for user on given repository
527 Revoke permission for user on given repository
528
528
529 :param repo: Instance of Repository, repository_id, or repository name
529 :param repo: Instance of Repository, repository_id, or repository name
530 :param user: Instance of User, user_id or username
530 :param user: Instance of User, user_id or username
531 """
531 """
532
532
533 user = self._get_user(user)
533 user = self._get_user(user)
534 repo = self._get_repo(repo)
534 repo = self._get_repo(repo)
535
535
536 obj = self.sa.query(UserRepoToPerm)\
536 obj = self.sa.query(UserRepoToPerm)\
537 .filter(UserRepoToPerm.repository == repo)\
537 .filter(UserRepoToPerm.repository == repo)\
538 .filter(UserRepoToPerm.user == user)\
538 .filter(UserRepoToPerm.user == user)\
539 .scalar()
539 .scalar()
540 if obj:
540 if obj:
541 self.sa.delete(obj)
541 self.sa.delete(obj)
542 log.debug('Revoked perm on %s on %s' % (repo, user))
542 log.debug('Revoked perm on %s on %s' % (repo, user))
543
543
544 def grant_users_group_permission(self, repo, group_name, perm):
544 def grant_users_group_permission(self, repo, group_name, perm):
545 """
545 """
546 Grant permission for user group on given repository, or update
546 Grant permission for user group on given repository, or update
547 existing one if found
547 existing one if found
548
548
549 :param repo: Instance of Repository, repository_id, or repository name
549 :param repo: Instance of Repository, repository_id, or repository name
550 :param group_name: Instance of UserGroup, users_group_id,
550 :param group_name: Instance of UserGroup, users_group_id,
551 or user group name
551 or user group name
552 :param perm: Instance of Permission, or permission_name
552 :param perm: Instance of Permission, or permission_name
553 """
553 """
554 repo = self._get_repo(repo)
554 repo = self._get_repo(repo)
555 group_name = self.__get_users_group(group_name)
555 group_name = self.__get_users_group(group_name)
556 permission = self._get_perm(perm)
556 permission = self._get_perm(perm)
557
557
558 # check if we have that permission already
558 # check if we have that permission already
559 obj = self.sa.query(UserGroupRepoToPerm)\
559 obj = self.sa.query(UserGroupRepoToPerm)\
560 .filter(UserGroupRepoToPerm.users_group == group_name)\
560 .filter(UserGroupRepoToPerm.users_group == group_name)\
561 .filter(UserGroupRepoToPerm.repository == repo)\
561 .filter(UserGroupRepoToPerm.repository == repo)\
562 .scalar()
562 .scalar()
563
563
564 if obj is None:
564 if obj is None:
565 # create new
565 # create new
566 obj = UserGroupRepoToPerm()
566 obj = UserGroupRepoToPerm()
567
567
568 obj.repository = repo
568 obj.repository = repo
569 obj.users_group = group_name
569 obj.users_group = group_name
570 obj.permission = permission
570 obj.permission = permission
571 self.sa.add(obj)
571 self.sa.add(obj)
572 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
572 log.debug('Granted perm %s to %s on %s' % (perm, group_name, repo))
573
573
574 def revoke_users_group_permission(self, repo, group_name):
574 def revoke_users_group_permission(self, repo, group_name):
575 """
575 """
576 Revoke permission for user group on given repository
576 Revoke permission for user group on given repository
577
577
578 :param repo: Instance of Repository, repository_id, or repository name
578 :param repo: Instance of Repository, repository_id, or repository name
579 :param group_name: Instance of UserGroup, users_group_id,
579 :param group_name: Instance of UserGroup, users_group_id,
580 or user group name
580 or user group name
581 """
581 """
582 repo = self._get_repo(repo)
582 repo = self._get_repo(repo)
583 group_name = self.__get_users_group(group_name)
583 group_name = self.__get_users_group(group_name)
584
584
585 obj = self.sa.query(UserGroupRepoToPerm)\
585 obj = self.sa.query(UserGroupRepoToPerm)\
586 .filter(UserGroupRepoToPerm.repository == repo)\
586 .filter(UserGroupRepoToPerm.repository == repo)\
587 .filter(UserGroupRepoToPerm.users_group == group_name)\
587 .filter(UserGroupRepoToPerm.users_group == group_name)\
588 .scalar()
588 .scalar()
589 if obj:
589 if obj:
590 self.sa.delete(obj)
590 self.sa.delete(obj)
591 log.debug('Revoked perm to %s on %s' % (repo, group_name))
591 log.debug('Revoked perm to %s on %s' % (repo, group_name))
592
592
593 def delete_stats(self, repo_name):
593 def delete_stats(self, repo_name):
594 """
594 """
595 removes stats for given repo
595 removes stats for given repo
596
596
597 :param repo_name:
597 :param repo_name:
598 """
598 """
599 repo = self._get_repo(repo_name)
599 repo = self._get_repo(repo_name)
600 try:
600 try:
601 obj = self.sa.query(Statistics)\
601 obj = self.sa.query(Statistics)\
602 .filter(Statistics.repository == repo).scalar()
602 .filter(Statistics.repository == repo).scalar()
603 if obj:
603 if obj:
604 self.sa.delete(obj)
604 self.sa.delete(obj)
605 except:
605 except:
606 log.error(traceback.format_exc())
606 log.error(traceback.format_exc())
607 raise
607 raise
608
608
609 def __create_repo(self, repo_name, alias, parent, clone_uri=False):
609 def __create_repo(self, repo_name, alias, parent, clone_uri=False):
610 """
610 """
611 makes repository on filesystem. It's group aware means it'll create
611 makes repository on filesystem. It's group aware means it'll create
612 a repository within a group, and alter the paths accordingly of
612 a repository within a group, and alter the paths accordingly of
613 group location
613 group location
614
614
615 :param repo_name:
615 :param repo_name:
616 :param alias:
616 :param alias:
617 :param parent_id:
617 :param parent_id:
618 :param clone_uri:
618 :param clone_uri:
619 """
619 """
620 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
620 from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group
621 from rhodecode.model.scm import ScmModel
621 from rhodecode.model.scm import ScmModel
622
622
623 if parent:
623 if parent:
624 new_parent_path = os.sep.join(parent.full_path_splitted)
624 new_parent_path = os.sep.join(parent.full_path_splitted)
625 else:
625 else:
626 new_parent_path = ''
626 new_parent_path = ''
627
627
628 # we need to make it str for mercurial
628 # we need to make it str for mercurial
629 repo_path = os.path.join(*map(lambda x: safe_str(x),
629 repo_path = os.path.join(*map(lambda x: safe_str(x),
630 [self.repos_path, new_parent_path, repo_name]))
630 [self.repos_path, new_parent_path, repo_name]))
631
631
632 # check if this path is not a repository
632 # check if this path is not a repository
633 if is_valid_repo(repo_path, self.repos_path):
633 if is_valid_repo(repo_path, self.repos_path):
634 raise Exception('This path %s is a valid repository' % repo_path)
634 raise Exception('This path %s is a valid repository' % repo_path)
635
635
636 # check if this path is a group
636 # check if this path is a group
637 if is_valid_repos_group(repo_path, self.repos_path):
637 if is_valid_repos_group(repo_path, self.repos_path):
638 raise Exception('This path %s is a valid group' % repo_path)
638 raise Exception('This path %s is a valid group' % repo_path)
639
639
640 log.info('creating repo %s in %s @ %s' % (
640 log.info('creating repo %s in %s @ %s' % (
641 repo_name, safe_unicode(repo_path),
641 repo_name, safe_unicode(repo_path),
642 obfuscate_url_pw(clone_uri)
642 obfuscate_url_pw(clone_uri)
643 )
643 )
644 )
644 )
645 backend = get_backend(alias)
645 backend = get_backend(alias)
646 if alias == 'hg':
646 if alias == 'hg':
647 backend(repo_path, create=True, src_url=clone_uri)
647 backend(repo_path, create=True, src_url=clone_uri)
648 elif alias == 'git':
648 elif alias == 'git':
649 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
649 r = backend(repo_path, create=True, src_url=clone_uri, bare=True)
650 # add rhodecode hook into this repo
650 # add rhodecode hook into this repo
651 ScmModel().install_git_hook(repo=r)
651 ScmModel().install_git_hook(repo=r)
652 else:
652 else:
653 raise Exception('Undefined alias %s' % alias)
653 raise Exception('Undefined alias %s' % alias)
654
654
655 def __rename_repo(self, old, new):
655 def __rename_repo(self, old, new):
656 """
656 """
657 renames repository on filesystem
657 renames repository on filesystem
658
658
659 :param old: old name
659 :param old: old name
660 :param new: new name
660 :param new: new name
661 """
661 """
662 log.info('renaming repo from %s to %s' % (old, new))
662 log.info('renaming repo from %s to %s' % (old, new))
663
663
664 old_path = os.path.join(self.repos_path, old)
664 old_path = os.path.join(self.repos_path, old)
665 new_path = os.path.join(self.repos_path, new)
665 new_path = os.path.join(self.repos_path, new)
666 if os.path.isdir(new_path):
666 if os.path.isdir(new_path):
667 raise Exception(
667 raise Exception(
668 'Was trying to rename to already existing dir %s' % new_path
668 'Was trying to rename to already existing dir %s' % new_path
669 )
669 )
670 shutil.move(old_path, new_path)
670 shutil.move(old_path, new_path)
671
671
672 def __delete_repo(self, repo):
672 def __delete_repo(self, repo):
673 """
673 """
674 removes repo from filesystem, the removal is acctually made by
674 removes repo from filesystem, the removal is acctually made by
675 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
675 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
676 repository is no longer valid for rhodecode, can be undeleted later on
676 repository is no longer valid for rhodecode, can be undeleted later on
677 by reverting the renames on this repository
677 by reverting the renames on this repository
678
678
679 :param repo: repo object
679 :param repo: repo object
680 """
680 """
681 rm_path = os.path.join(self.repos_path, repo.repo_name)
681 rm_path = os.path.join(self.repos_path, repo.repo_name)
682 log.info("Removing %s" % (rm_path))
682 log.info("Removing %s" % (rm_path))
683 # disable hg/git internal that it doesn't get detected as repo
683 # disable hg/git internal that it doesn't get detected as repo
684 alias = repo.repo_type
684 alias = repo.repo_type
685
685
686 bare = getattr(repo.scm_instance, 'bare', False)
686 bare = getattr(repo.scm_instance, 'bare', False)
687
687
688 if not bare:
688 if not bare:
689 # skip this for bare git repos
689 # skip this for bare git repos
690 shutil.move(os.path.join(rm_path, '.%s' % alias),
690 shutil.move(os.path.join(rm_path, '.%s' % alias),
691 os.path.join(rm_path, 'rm__.%s' % alias))
691 os.path.join(rm_path, 'rm__.%s' % alias))
692 # disable repo
692 # disable repo
693 _now = datetime.now()
693 _now = datetime.now()
694 _ms = str(_now.microsecond).rjust(6, '0')
694 _ms = str(_now.microsecond).rjust(6, '0')
695 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
695 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
696 repo.just_name)
696 repo.just_name)
697 if repo.group:
697 if repo.group:
698 args = repo.group.full_path_splitted + [_d]
698 args = repo.group.full_path_splitted + [_d]
699 _d = os.path.join(*args)
699 _d = os.path.join(*args)
700 shutil.move(rm_path, os.path.join(self.repos_path, _d))
700 shutil.move(rm_path, os.path.join(self.repos_path, _d))
@@ -1,355 +1,355 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <!-- HEADER -->
4 <!-- HEADER -->
5 <div id="header-dd"></div>
5 <div id="header-dd"></div>
6 <div id="header">
6 <div id="header">
7 <div id="header-inner" class="title">
7 <div id="header-inner" class="title">
8 <div id="logo">
8 <div id="logo">
9 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
9 <h1><a href="${h.url('home')}">${c.rhodecode_name}</a></h1>
10 </div>
10 </div>
11 <!-- MENU -->
11 <!-- MENU -->
12 ${self.page_nav()}
12 ${self.page_nav()}
13 <!-- END MENU -->
13 <!-- END MENU -->
14 ${self.body()}
14 ${self.body()}
15 </div>
15 </div>
16 </div>
16 </div>
17 <!-- END HEADER -->
17 <!-- END HEADER -->
18
18
19 <!-- CONTENT -->
19 <!-- CONTENT -->
20 <div id="content">
20 <div id="content">
21 <div class="flash_msg">
21 <div class="flash_msg">
22 <% messages = h.flash.pop_messages() %>
22 <% messages = h.flash.pop_messages() %>
23 % if messages:
23 % if messages:
24 <ul id="flash-messages">
24 <ul id="flash-messages">
25 % for message in messages:
25 % for message in messages:
26 <li class="${message.category}_msg">${message}</li>
26 <li class="${message.category}_msg">${message}</li>
27 % endfor
27 % endfor
28 </ul>
28 </ul>
29 % endif
29 % endif
30 </div>
30 </div>
31 <div id="main">
31 <div id="main">
32 ${next.main()}
32 ${next.main()}
33 </div>
33 </div>
34 </div>
34 </div>
35 <!-- END CONTENT -->
35 <!-- END CONTENT -->
36
36
37 <!-- FOOTER -->
37 <!-- FOOTER -->
38 <div id="footer">
38 <div id="footer">
39 <div id="footer-inner" class="title">
39 <div id="footer-inner" class="title">
40 <div>
40 <div>
41 <p class="footer-link">
41 <p class="footer-link">
42 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
42 <a href="${h.url('bugtracker')}">${_('Submit a bug')}</a>
43 </p>
43 </p>
44 <p class="footer-link-right">
44 <p class="footer-link-right">
45 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
45 <a href="${h.url('rhodecode_official')}">RhodeCode${'-%s' % c.rhodecode_instanceid if c.rhodecode_instanceid else ''}</a>
46 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
46 ${c.rhodecode_version} &copy; 2010-${h.datetime.today().year} by Marcin Kuzminski
47 </p>
47 </p>
48 </div>
48 </div>
49 </div>
49 </div>
50 </div>
50 </div>
51 <!-- END FOOTER -->
51 <!-- END FOOTER -->
52
52
53 ### MAKO DEFS ###
53 ### MAKO DEFS ###
54 <%def name="page_nav()">
54 <%def name="page_nav()">
55 ${self.menu()}
55 ${self.menu()}
56 </%def>
56 </%def>
57
57
58 <%def name="breadcrumbs()">
58 <%def name="breadcrumbs()">
59 <div class="breadcrumbs">
59 <div class="breadcrumbs">
60 ${self.breadcrumbs_links()}
60 ${self.breadcrumbs_links()}
61 </div>
61 </div>
62 </%def>
62 </%def>
63
63
64 <%def name="context_bar(current=None)">
64 <%def name="context_bar(current=None)">
65 %if c.repo_name:
65 %if c.repo_name:
66 ${repo_context_bar(current)}
66 ${repo_context_bar(current)}
67 %endif
67 %endif
68 </%def>
68 </%def>
69
69
70 <%def name="admin_menu()">
70 <%def name="admin_menu()">
71 <ul class="admin_menu">
71 <ul class="admin_menu">
72 <li>${h.link_to(_('admin journal'),h.url('admin_home'),class_='journal ')}</li>
72 <li>${h.link_to(_('admin journal'),h.url('admin_home'),class_='journal ')}</li>
73 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
73 <li>${h.link_to(_('repositories'),h.url('repos'),class_='repos')}</li>
74 <li>${h.link_to(_('repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
74 <li>${h.link_to(_('repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
75 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
75 <li>${h.link_to(_('users'),h.url('users'),class_='users')}</li>
76 <li>${h.link_to(_('user groups'),h.url('users_groups'),class_='groups')}</li>
76 <li>${h.link_to(_('user groups'),h.url('users_groups'),class_='groups')}</li>
77 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
77 <li>${h.link_to(_('permissions'),h.url('edit_permission',id='default'),class_='permissions')}</li>
78 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
78 <li>${h.link_to(_('ldap'),h.url('ldap_home'),class_='ldap')}</li>
79 <li>${h.link_to(_('defaults'),h.url('defaults'),class_='defaults')}</li>
79 <li>${h.link_to(_('defaults'),h.url('defaults'),class_='defaults')}</li>
80 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
80 <li class="last">${h.link_to(_('settings'),h.url('admin_settings'),class_='settings')}</li>
81 </ul>
81 </ul>
82 </%def>
82 </%def>
83
83
84 <%def name="admin_menu_simple()">
84 <%def name="admin_menu_simple()">
85 <ul>
85 <ul>
86 <li>${h.link_to(_('repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
86 <li>${h.link_to(_('repository groups'),h.url('repos_groups'),class_='repos_groups')}</li>
87 </ul>
87 </ul>
88 </%def>
88 </%def>
89
89
90 <%def name="repo_context_bar(current=None)">
90 <%def name="repo_context_bar(current=None)">
91 <%
91 <%
92 def follow_class():
92 def follow_class():
93 if c.repository_following:
93 if c.repository_following:
94 return h.literal('following')
94 return h.literal('following')
95 else:
95 else:
96 return h.literal('follow')
96 return h.literal('follow')
97 %>
97 %>
98 <%
98 <%
99 def is_current(selected):
99 def is_current(selected):
100 if selected == current:
100 if selected == current:
101 return h.literal('class="current"')
101 return h.literal('class="current"')
102 %>
102 %>
103
103
104 <!--- CONTEXT BAR -->
104 <!--- CONTEXT BAR -->
105 <div id="context-bar" class="box">
105 <div id="context-bar" class="box">
106 <div id="context-top">
106 <div id="context-top">
107 <div id="breadcrumbs">
107 <div id="breadcrumbs">
108 ${h.link_to(_(u'Repositories'),h.url('home'))}
108 ${h.link_to(_(u'Repositories'),h.url('home'))}
109 Β»
109 &raquo;
110 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
110 ${h.repo_link(c.rhodecode_db_repo.groups_and_repo)}
111 </div>
111 </div>
112 ## TODO: this check feels wrong, it would be better to have a check for permissions
112 ## TODO: this check feels wrong, it would be better to have a check for permissions
113 ## also it feels like a job for the controller
113 ## also it feels like a job for the controller
114 %if c.rhodecode_user.username != 'default':
114 %if c.rhodecode_user.username != 'default':
115 <ul id="context-actions" class="horizontal-list">
115 <ul id="context-actions" class="horizontal-list">
116 <li>
116 <li>
117 <button class="${follow_class()}" onclick="javascript:toggleFollowingRepo(this,${c.rhodecode_db_repo.repo_id},'${str(h.get_token())}');">
117 <button class="${follow_class()}" onclick="javascript:toggleFollowingRepo(this,${c.rhodecode_db_repo.repo_id},'${str(h.get_token())}');">
118 <!--span class="icon show-follow follow"></span>
118 <!--span class="icon show-follow follow"></span>
119 <span class="icon show-following following"></span-->
119 <span class="icon show-following following"></span-->
120 <span class="show-follow">${_('Follow')}</span>
120 <span class="show-follow">${_('Follow')}</span>
121 <span class="show-following">${_('Unfollow')}</span>
121 <span class="show-following">${_('Unfollow')}</span>
122 </button>
122 </button>
123 </li>
123 </li>
124 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}" class="fork">${_('Fork')}</a></li>
124 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}" class="fork">${_('Fork')}</a></li>
125 %if h.is_hg(c.rhodecode_repo):
125 %if h.is_hg(c.rhodecode_repo):
126 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="pull-request">${_('Create Pull Request')}</a></li>
126 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="pull-request">${_('Create Pull Request')}</a></li>
127 %endif
127 %endif
128 </ul>
128 </ul>
129 %endif
129 %endif
130 </div>
130 </div>
131 <div id="context-state">
131 <div id="context-state">
132 <ul id="context-pages" class="horizontal-list">
132 <ul id="context-pages" class="horizontal-list">
133 <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}" class="summary">${_('Summary')}</a></li>
133 <li ${is_current('summary')}><a href="${h.url('summary_home', repo_name=c.repo_name)}" class="summary">${_('Summary')}</a></li>
134 <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}" class="changelogs">${_('Changelog')}</a></li>
134 <li ${is_current('changelog')}><a href="${h.url('changelog_home', repo_name=c.repo_name)}" class="changelogs">${_('Changelog')}</a></li>
135 <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name)}" class="files"></span>${_('Files')}</a></li>
135 <li ${is_current('files')}><a href="${h.url('files_home', repo_name=c.repo_name)}" class="files"></span>${_('Files')}</a></li>
136 <li ${is_current('switch-to')}>
136 <li ${is_current('switch-to')}>
137 <a href="#" id="branch_tag_switcher_2" class="dropdown switch-to"></span>${_('Switch To')}</a>
137 <a href="#" id="branch_tag_switcher_2" class="dropdown switch-to"></span>${_('Switch To')}</a>
138 <ul id="switch_to_list_2" class="switch_to submenu">
138 <ul id="switch_to_list_2" class="switch_to submenu">
139 <li><a href="#">${_('loading...')}</a></li>
139 <li><a href="#">${_('loading...')}</a></li>
140 </ul>
140 </ul>
141 </li>
141 </li>
142 <li ${is_current('options')}>
142 <li ${is_current('options')}>
143 <a href="#" class="dropdown options"></span>${_('Options')}</a>
143 <a href="#" class="dropdown options"></span>${_('Options')}</a>
144 <ul>
144 <ul>
145 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
145 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
146 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
146 %if h.HasPermissionAll('hg.admin')('access settings on repository'):
147 <li>${h.link_to(_('Settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
147 <li>${h.link_to(_('Settings'),h.url('edit_repo',repo_name=c.repo_name),class_='settings')}</li>
148 %else:
148 %else:
149 <li>${h.link_to(_('Settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
149 <li>${h.link_to(_('Settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li>
150 %endif
150 %endif
151 %endif
151 %endif
152 %if c.rhodecode_db_repo.fork:
152 %if c.rhodecode_db_repo.fork:
153 <li>${h.link_to(_('Compare fork'),h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default'),class_='compare_request')}</li>
153 <li>${h.link_to(_('Compare fork'),h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,org_ref_type='branch',org_ref='default',other_repo=c.repo_name,other_ref_type='branch',other_ref=request.GET.get('branch') or 'default'),class_='compare_request')}</li>
154 %endif
154 %endif
155 <li>${h.link_to(_('Lightweight changelog'),h.url('shortlog_home',repo_name=c.repo_name),class_='shortlog')}</li>
155 <li>${h.link_to(_('Lightweight changelog'),h.url('shortlog_home',repo_name=c.repo_name),class_='shortlog')}</li>
156 <li>${h.link_to(_('Search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li>
156 <li>${h.link_to(_('Search'),h.url('search_repo',repo_name=c.repo_name),class_='search')}</li>
157
157
158 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
158 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
159 %if c.rhodecode_db_repo.locked[0]:
159 %if c.rhodecode_db_repo.locked[0]:
160 <li>${h.link_to(_('Unlock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_del')}</li>
160 <li>${h.link_to(_('Unlock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_del')}</li>
161 %else:
161 %else:
162 <li>${h.link_to(_('Lock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_add')}</li>
162 <li>${h.link_to(_('Lock'), h.url('toggle_locking',repo_name=c.repo_name),class_='locking_add')}</li>
163 %endif
163 %endif
164 %endif
164 %endif
165 </ul>
165 </ul>
166 </li>
166 </li>
167 <li ${is_current('showpullrequest')}>
167 <li ${is_current('showpullrequest')}>
168 <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests')}" class="pull-request">${_('Pull Requests')}
168 <a href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests')}" class="pull-request">${_('Pull Requests')}
169 %if c.repository_pull_requests:
169 %if c.repository_pull_requests:
170 <span>${c.repository_pull_requests}</span>
170 <span>${c.repository_pull_requests}</span>
171 %endif
171 %endif
172 </a>
172 </a>
173 </li>
173 </li>
174 </ul>
174 </ul>
175 </div>
175 </div>
176 </div>
176 </div>
177 <script type="text/javascript">
177 <script type="text/javascript">
178 YUE.on('branch_tag_switcher_2','mouseover',function(){
178 YUE.on('branch_tag_switcher_2','mouseover',function(){
179 var loaded = YUD.hasClass('branch_tag_switcher_2','loaded');
179 var loaded = YUD.hasClass('branch_tag_switcher_2','loaded');
180 if(!loaded){
180 if(!loaded){
181 YUD.addClass('branch_tag_switcher_2','loaded');
181 YUD.addClass('branch_tag_switcher_2','loaded');
182 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list_2',
182 ypjax("${h.url('branch_tag_switcher',repo_name=c.repo_name)}",'switch_to_list_2',
183 function(o){},
183 function(o){},
184 function(o){YUD.removeClass('branch_tag_switcher_2','loaded');}
184 function(o){YUD.removeClass('branch_tag_switcher_2','loaded');}
185 ,null);
185 ,null);
186 }
186 }
187 return false;
187 return false;
188 });
188 });
189 </script>
189 </script>
190 <!--- END CONTEXT BAR -->
190 <!--- END CONTEXT BAR -->
191 </%def>
191 </%def>
192
192
193 <%def name="usermenu()">
193 <%def name="usermenu()">
194 ## USER MENU
194 ## USER MENU
195 <li>
195 <li>
196 <a class="menu_link childs" id="quick_login_link">
196 <a class="menu_link childs" id="quick_login_link">
197 <span class="icon">
197 <span class="icon">
198 <img src="${h.gravatar_url(c.rhodecode_user.email,20)}" alt="avatar">
198 <img src="${h.gravatar_url(c.rhodecode_user.email,20)}" alt="avatar">
199 </span>
199 </span>
200 %if c.rhodecode_user.username != 'default':
200 %if c.rhodecode_user.username != 'default':
201 <span class="menu_link_user">${c.rhodecode_user.username}</span>
201 <span class="menu_link_user">${c.rhodecode_user.username}</span>
202 %if c.unread_notifications != 0:
202 %if c.unread_notifications != 0:
203 <span class="menu_link_notifications">${c.unread_notifications}</span>
203 <span class="menu_link_notifications">${c.unread_notifications}</span>
204 %endif
204 %endif
205 %else:
205 %else:
206 <span>${_('Not logged in')}</span>
206 <span>${_('Not logged in')}</span>
207 %endif
207 %endif
208 </a>
208 </a>
209
209
210 <div class="user-menu">
210 <div class="user-menu">
211 <div id="quick_login">
211 <div id="quick_login">
212 %if c.rhodecode_user.username == 'default':
212 %if c.rhodecode_user.username == 'default':
213 <h4>${_('Login to your account')}</h4>
213 <h4>${_('Login to your account')}</h4>
214 ${h.form(h.url('login_home',came_from=h.url.current()))}
214 ${h.form(h.url('login_home',came_from=h.url.current()))}
215 <div class="form">
215 <div class="form">
216 <div class="fields">
216 <div class="fields">
217 <div class="field">
217 <div class="field">
218 <div class="label">
218 <div class="label">
219 <label for="username">${_('Username')}:</label>
219 <label for="username">${_('Username')}:</label>
220 </div>
220 </div>
221 <div class="input">
221 <div class="input">
222 ${h.text('username',class_='focus')}
222 ${h.text('username',class_='focus')}
223 </div>
223 </div>
224
224
225 </div>
225 </div>
226 <div class="field">
226 <div class="field">
227 <div class="label">
227 <div class="label">
228 <label for="password">${_('Password')}:</label>
228 <label for="password">${_('Password')}:</label>
229 </div>
229 </div>
230 <div class="input">
230 <div class="input">
231 ${h.password('password',class_='focus')}
231 ${h.password('password',class_='focus')}
232 </div>
232 </div>
233
233
234 </div>
234 </div>
235 <div class="buttons">
235 <div class="buttons">
236 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
236 <div class="password_forgoten">${h.link_to(_('Forgot password ?'),h.url('reset_password'))}</div>
237 <div class="register">
237 <div class="register">
238 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
238 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
239 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
239 ${h.link_to(_("Don't have an account ?"),h.url('register'))}
240 %endif
240 %endif
241 </div>
241 </div>
242 <div class="submit">
242 <div class="submit">
243 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
243 ${h.submit('sign_in',_('Log In'),class_="ui-btn xsmall")}
244 </div>
244 </div>
245 </div>
245 </div>
246 </div>
246 </div>
247 </div>
247 </div>
248 ${h.end_form()}
248 ${h.end_form()}
249 %else:
249 %else:
250 <div class="links_left">
250 <div class="links_left">
251 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
251 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
252 <div class="email">${c.rhodecode_user.email}</div>
252 <div class="email">${c.rhodecode_user.email}</div>
253 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
253 <div class="big_gravatar"><img alt="gravatar" src="${h.gravatar_url(c.rhodecode_user.email,48)}" /></div>
254 ##<div class="notifications"><a href="${h.url('notifications')}">${_('Notifications')}</a></div>
254 ##<div class="notifications"><a href="${h.url('notifications')}">${_('Notifications')}</a></div>
255 <div class="unread"><a href="${h.url('notifications')}">${_('Unread notifications')}: ${c.unread_notifications}</a></div>
255 <div class="unread"><a href="${h.url('notifications')}">${_('Unread notifications')}: ${c.unread_notifications}</a></div>
256 </div>
256 </div>
257 <div class="links_right">
257 <div class="links_right">
258 <ol class="links">
258 <ol class="links">
259 ##<li>${h.link_to(_(u'Home'),h.url('home'))}</li>
259 ##<li>${h.link_to(_(u'Home'),h.url('home'))}</li>
260 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
260 <li>${h.link_to(_(u'Journal'),h.url('journal'))}</li>
261 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
261 <li>${h.link_to(_(u'My account'),h.url('admin_settings_my_account'))}</li>
262 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
262 <li class="logout">${h.link_to(_(u'Log Out'),h.url('logout_home'))}</li>
263 </ol>
263 </ol>
264 </div>
264 </div>
265 %endif
265 %endif
266 </div>
266 </div>
267 </div>
267 </div>
268
268
269 </li>
269 </li>
270 </%def>
270 </%def>
271
271
272 <%def name="menu(current=None)">
272 <%def name="menu(current=None)">
273 <%
273 <%
274 def is_current(selected):
274 def is_current(selected):
275 if selected == current:
275 if selected == current:
276 return h.literal('class="current"')
276 return h.literal('class="current"')
277 %>
277 %>
278 <ul id="quick" class="horizontal-list">
278 <ul id="quick" class="horizontal-list">
279 <!-- repo switcher -->
279 <!-- repo switcher -->
280 <li ${is_current('home')}>
280 <li ${is_current('home')}>
281 <a class="menu_link repo_switcher childs" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}">
281 <a class="menu_link repo_switcher childs" id="repo_switcher" title="${_('Switch repository')}" href="${h.url('home')}">
282 ${_('Repositories')}
282 ${_('Repositories')}
283 </a>
283 </a>
284 <ul id="repo_switcher_list" class="repo_switcher">
284 <ul id="repo_switcher_list" class="repo_switcher">
285 <li>
285 <li>
286 <a href="#">${_('loading...')}</a>
286 <a href="#">${_('loading...')}</a>
287 </li>
287 </li>
288 </ul>
288 </ul>
289 </li>
289 </li>
290 ##ROOT MENU
290 ##ROOT MENU
291 %if c.rhodecode_user.username != 'default':
291 %if c.rhodecode_user.username != 'default':
292 <li ${is_current('journal')}>
292 <li ${is_current('journal')}>
293 <a class="menu_link journal" title="${_('Show recent activity')}" href="${h.url('journal')}">
293 <a class="menu_link journal" title="${_('Show recent activity')}" href="${h.url('journal')}">
294 ${_('Journal')}
294 ${_('Journal')}
295 </a>
295 </a>
296 </li>
296 </li>
297 %else:
297 %else:
298 <li ${is_current('journal')}>
298 <li ${is_current('journal')}>
299 <a class="menu_link journal" title="${_('Public journal')}" href="${h.url('public_journal')}">
299 <a class="menu_link journal" title="${_('Public journal')}" href="${h.url('public_journal')}">
300 ${_('Public journal')}
300 ${_('Public journal')}
301 </a>
301 </a>
302 </li>
302 </li>
303 %endif
303 %endif
304 <li ${is_current('search')}>
304 <li ${is_current('search')}>
305 <a class="menu_link search" title="${_('Search in repositories')}" href="${h.url('search')}">
305 <a class="menu_link search" title="${_('Search in repositories')}" href="${h.url('search')}">
306 ${_('Search')}
306 ${_('Search')}
307 </a>
307 </a>
308 </li>
308 </li>
309 % if h.HasPermissionAll('hg.admin')('access admin main page'):
309 % if h.HasPermissionAll('hg.admin')('access admin main page'):
310 <li ${is_current('admin')}>
310 <li ${is_current('admin')}>
311 <a class="menu_link admin childs" title="${_('Admin')}" href="${h.url('admin_home')}">
311 <a class="menu_link admin childs" title="${_('Admin')}" href="${h.url('admin_home')}">
312 ${_('Admin')}
312 ${_('Admin')}
313 </a>
313 </a>
314 ${admin_menu()}
314 ${admin_menu()}
315 </li>
315 </li>
316 % elif c.rhodecode_user.groups_admin:
316 % elif c.rhodecode_user.groups_admin:
317 <li ${is_current('admin')}>
317 <li ${is_current('admin')}>
318 <a class="menu_link admin childs" title="${_('Admin')}" href="${h.url('admin_home')}">
318 <a class="menu_link admin childs" title="${_('Admin')}" href="${h.url('admin_home')}">
319 ${_('Admin')}
319 ${_('Admin')}
320 </a>
320 </a>
321 ${admin_menu_simple()}
321 ${admin_menu_simple()}
322 </li>
322 </li>
323 % endif
323 % endif
324 ${usermenu()}
324 ${usermenu()}
325 <script type="text/javascript">
325 <script type="text/javascript">
326 YUE.on('repo_switcher','mouseover',function(){
326 YUE.on('repo_switcher','mouseover',function(){
327 var target = 'q_filter_rs';
327 var target = 'q_filter_rs';
328 var qfilter_activate = function(){
328 var qfilter_activate = function(){
329 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
329 var nodes = YUQ('ul#repo_switcher_list li a.repo_name');
330 var func = function(node){
330 var func = function(node){
331 return node.parentNode;
331 return node.parentNode;
332 }
332 }
333 q_filter(target,nodes,func);
333 q_filter(target,nodes,func);
334 }
334 }
335
335
336 var loaded = YUD.hasClass('repo_switcher','loaded');
336 var loaded = YUD.hasClass('repo_switcher','loaded');
337 if(!loaded){
337 if(!loaded){
338 YUD.addClass('repo_switcher','loaded');
338 YUD.addClass('repo_switcher','loaded');
339 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
339 ypjax("${h.url('repo_switcher')}",'repo_switcher_list',
340 function(o){qfilter_activate();YUD.get(target).focus()},
340 function(o){qfilter_activate();YUD.get(target).focus()},
341 function(o){YUD.removeClass('repo_switcher','loaded');}
341 function(o){YUD.removeClass('repo_switcher','loaded');}
342 ,null);
342 ,null);
343 }else{
343 }else{
344 YUD.get(target).focus();
344 YUD.get(target).focus();
345 }
345 }
346 return false;
346 return false;
347 });
347 });
348
348
349 YUE.on('header-dd', 'click',function(e){
349 YUE.on('header-dd', 'click',function(e){
350 YUD.addClass('header-inner', 'hover');
350 YUD.addClass('header-inner', 'hover');
351 YUD.addClass('content', 'hover');
351 YUD.addClass('content', 'hover');
352 });
352 });
353
353
354 </script>
354 </script>
355 </%def>
355 </%def>
General Comments 0
You need to be logged in to leave comments. Login now