##// END OF EJS Templates
security: fix possible XSS in the issue tracker URL.
marcink -
r3439:1755b780 default
parent child Browse files
Show More
@@ -1,2020 +1,2020 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import os
28 import os
29 import random
29 import random
30 import hashlib
30 import hashlib
31 import StringIO
31 import StringIO
32 import textwrap
32 import textwrap
33 import urllib
33 import urllib
34 import math
34 import math
35 import logging
35 import logging
36 import re
36 import re
37 import time
37 import time
38 import string
38 import string
39 import hashlib
39 import hashlib
40 from collections import OrderedDict
40 from collections import OrderedDict
41
41
42 import pygments
42 import pygments
43 import itertools
43 import itertools
44 import fnmatch
44 import fnmatch
45 import bleach
45 import bleach
46
46
47 from pyramid import compat
47 from pyramid import compat
48 from datetime import datetime
48 from datetime import datetime
49 from functools import partial
49 from functools import partial
50 from pygments.formatters.html import HtmlFormatter
50 from pygments.formatters.html import HtmlFormatter
51 from pygments.lexers import (
51 from pygments.lexers import (
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53
53
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55
55
56 from webhelpers.html import literal, HTML, escape
56 from webhelpers.html import literal, HTML, escape
57 from webhelpers.html.tools import *
57 from webhelpers.html.tools import *
58 from webhelpers.html.builder import make_tag
58 from webhelpers.html.builder import make_tag
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
62 submit, text, password, textarea, title, ul, xml_declaration, radio
62 submit, text, password, textarea, title, ul, xml_declaration, radio
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
67 replace_whitespace, urlify, truncate, wrap_paragraphs
67 replace_whitespace, urlify, truncate, wrap_paragraphs
68 from webhelpers.date import time_ago_in_words
68 from webhelpers.date import time_ago_in_words
69 from webhelpers.paginate import Page as _Page
69 from webhelpers.paginate import Page as _Page
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
72 from webhelpers2.number import format_byte_size
72 from webhelpers2.number import format_byte_size
73
73
74 from rhodecode.lib.action_parser import action_parser
74 from rhodecode.lib.action_parser import action_parser
75 from rhodecode.lib.ext_json import json
75 from rhodecode.lib.ext_json import json
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
79 AttributeDict, safe_int, md5, md5_safe
79 AttributeDict, safe_int, md5, md5_safe
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.model.changeset_status import ChangesetStatusModel
85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.db import Permission, User, Repository
86 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.repo_group import RepoGroupModel
87 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
89
89
90
90
91 log = logging.getLogger(__name__)
91 log = logging.getLogger(__name__)
92
92
93
93
94 DEFAULT_USER = User.DEFAULT_USER
94 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96
96
97
97
98 def asset(path, ver=None, **kwargs):
98 def asset(path, ver=None, **kwargs):
99 """
99 """
100 Helper to generate a static asset file path for rhodecode assets
100 Helper to generate a static asset file path for rhodecode assets
101
101
102 eg. h.asset('images/image.png', ver='3923')
102 eg. h.asset('images/image.png', ver='3923')
103
103
104 :param path: path of asset
104 :param path: path of asset
105 :param ver: optional version query param to append as ?ver=
105 :param ver: optional version query param to append as ?ver=
106 """
106 """
107 request = get_current_request()
107 request = get_current_request()
108 query = {}
108 query = {}
109 query.update(kwargs)
109 query.update(kwargs)
110 if ver:
110 if ver:
111 query = {'ver': ver}
111 query = {'ver': ver}
112 return request.static_path(
112 return request.static_path(
113 'rhodecode:public/{}'.format(path), _query=query)
113 'rhodecode:public/{}'.format(path), _query=query)
114
114
115
115
116 default_html_escape_table = {
116 default_html_escape_table = {
117 ord('&'): u'&amp;',
117 ord('&'): u'&amp;',
118 ord('<'): u'&lt;',
118 ord('<'): u'&lt;',
119 ord('>'): u'&gt;',
119 ord('>'): u'&gt;',
120 ord('"'): u'&quot;',
120 ord('"'): u'&quot;',
121 ord("'"): u'&#39;',
121 ord("'"): u'&#39;',
122 }
122 }
123
123
124
124
125 def html_escape(text, html_escape_table=default_html_escape_table):
125 def html_escape(text, html_escape_table=default_html_escape_table):
126 """Produce entities within text."""
126 """Produce entities within text."""
127 return text.translate(html_escape_table)
127 return text.translate(html_escape_table)
128
128
129
129
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 """
131 """
132 Truncate string ``s`` at the first occurrence of ``sub``.
132 Truncate string ``s`` at the first occurrence of ``sub``.
133
133
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 """
135 """
136 suffix_if_chopped = suffix_if_chopped or ''
136 suffix_if_chopped = suffix_if_chopped or ''
137 pos = s.find(sub)
137 pos = s.find(sub)
138 if pos == -1:
138 if pos == -1:
139 return s
139 return s
140
140
141 if inclusive:
141 if inclusive:
142 pos += len(sub)
142 pos += len(sub)
143
143
144 chopped = s[:pos]
144 chopped = s[:pos]
145 left = s[pos:].strip()
145 left = s[pos:].strip()
146
146
147 if left and suffix_if_chopped:
147 if left and suffix_if_chopped:
148 chopped += suffix_if_chopped
148 chopped += suffix_if_chopped
149
149
150 return chopped
150 return chopped
151
151
152
152
153 def shorter(text, size=20):
153 def shorter(text, size=20):
154 postfix = '...'
154 postfix = '...'
155 if len(text) > size:
155 if len(text) > size:
156 return text[:size - len(postfix)] + postfix
156 return text[:size - len(postfix)] + postfix
157 return text
157 return text
158
158
159
159
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
161 """
161 """
162 Reset button
162 Reset button
163 """
163 """
164 _set_input_attrs(attrs, type, name, value)
164 _set_input_attrs(attrs, type, name, value)
165 _set_id_attr(attrs, id, name)
165 _set_id_attr(attrs, id, name)
166 convert_boolean_attrs(attrs, ["disabled"])
166 convert_boolean_attrs(attrs, ["disabled"])
167 return HTML.input(**attrs)
167 return HTML.input(**attrs)
168
168
169 reset = _reset
169 reset = _reset
170 safeid = _make_safe_id_component
170 safeid = _make_safe_id_component
171
171
172
172
173 def branding(name, length=40):
173 def branding(name, length=40):
174 return truncate(name, length, indicator="")
174 return truncate(name, length, indicator="")
175
175
176
176
177 def FID(raw_id, path):
177 def FID(raw_id, path):
178 """
178 """
179 Creates a unique ID for filenode based on it's hash of path and commit
179 Creates a unique ID for filenode based on it's hash of path and commit
180 it's safe to use in urls
180 it's safe to use in urls
181
181
182 :param raw_id:
182 :param raw_id:
183 :param path:
183 :param path:
184 """
184 """
185
185
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
187
187
188
188
189 class _GetError(object):
189 class _GetError(object):
190 """Get error from form_errors, and represent it as span wrapped error
190 """Get error from form_errors, and represent it as span wrapped error
191 message
191 message
192
192
193 :param field_name: field to fetch errors for
193 :param field_name: field to fetch errors for
194 :param form_errors: form errors dict
194 :param form_errors: form errors dict
195 """
195 """
196
196
197 def __call__(self, field_name, form_errors):
197 def __call__(self, field_name, form_errors):
198 tmpl = """<span class="error_msg">%s</span>"""
198 tmpl = """<span class="error_msg">%s</span>"""
199 if form_errors and field_name in form_errors:
199 if form_errors and field_name in form_errors:
200 return literal(tmpl % form_errors.get(field_name))
200 return literal(tmpl % form_errors.get(field_name))
201
201
202 get_error = _GetError()
202 get_error = _GetError()
203
203
204
204
205 class _ToolTip(object):
205 class _ToolTip(object):
206
206
207 def __call__(self, tooltip_title, trim_at=50):
207 def __call__(self, tooltip_title, trim_at=50):
208 """
208 """
209 Special function just to wrap our text into nice formatted
209 Special function just to wrap our text into nice formatted
210 autowrapped text
210 autowrapped text
211
211
212 :param tooltip_title:
212 :param tooltip_title:
213 """
213 """
214 tooltip_title = escape(tooltip_title)
214 tooltip_title = escape(tooltip_title)
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
216 return tooltip_title
216 return tooltip_title
217 tooltip = _ToolTip()
217 tooltip = _ToolTip()
218
218
219
219
220 def files_breadcrumbs(repo_name, commit_id, file_path):
220 def files_breadcrumbs(repo_name, commit_id, file_path):
221 if isinstance(file_path, str):
221 if isinstance(file_path, str):
222 file_path = safe_unicode(file_path)
222 file_path = safe_unicode(file_path)
223
223
224 # TODO: johbo: Is this always a url like path, or is this operating
224 # TODO: johbo: Is this always a url like path, or is this operating
225 # system dependent?
225 # system dependent?
226 path_segments = file_path.split('/')
226 path_segments = file_path.split('/')
227
227
228 repo_name_html = escape(repo_name)
228 repo_name_html = escape(repo_name)
229 if len(path_segments) == 1 and path_segments[0] == '':
229 if len(path_segments) == 1 and path_segments[0] == '':
230 url_segments = [repo_name_html]
230 url_segments = [repo_name_html]
231 else:
231 else:
232 url_segments = [
232 url_segments = [
233 link_to(
233 link_to(
234 repo_name_html,
234 repo_name_html,
235 route_path(
235 route_path(
236 'repo_files',
236 'repo_files',
237 repo_name=repo_name,
237 repo_name=repo_name,
238 commit_id=commit_id,
238 commit_id=commit_id,
239 f_path=''),
239 f_path=''),
240 class_='pjax-link')]
240 class_='pjax-link')]
241
241
242 last_cnt = len(path_segments) - 1
242 last_cnt = len(path_segments) - 1
243 for cnt, segment in enumerate(path_segments):
243 for cnt, segment in enumerate(path_segments):
244 if not segment:
244 if not segment:
245 continue
245 continue
246 segment_html = escape(segment)
246 segment_html = escape(segment)
247
247
248 if cnt != last_cnt:
248 if cnt != last_cnt:
249 url_segments.append(
249 url_segments.append(
250 link_to(
250 link_to(
251 segment_html,
251 segment_html,
252 route_path(
252 route_path(
253 'repo_files',
253 'repo_files',
254 repo_name=repo_name,
254 repo_name=repo_name,
255 commit_id=commit_id,
255 commit_id=commit_id,
256 f_path='/'.join(path_segments[:cnt + 1])),
256 f_path='/'.join(path_segments[:cnt + 1])),
257 class_='pjax-link'))
257 class_='pjax-link'))
258 else:
258 else:
259 url_segments.append(segment_html)
259 url_segments.append(segment_html)
260
260
261 return literal('/'.join(url_segments))
261 return literal('/'.join(url_segments))
262
262
263
263
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
265 """
265 """
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
267
267
268 If ``outfile`` is given and a valid file object (an object
268 If ``outfile`` is given and a valid file object (an object
269 with a ``write`` method), the result will be written to it, otherwise
269 with a ``write`` method), the result will be written to it, otherwise
270 it is returned as a string.
270 it is returned as a string.
271 """
271 """
272 if use_hl_filter:
272 if use_hl_filter:
273 # add HL filter
273 # add HL filter
274 from rhodecode.lib.index import search_utils
274 from rhodecode.lib.index import search_utils
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
276 return pygments.format(pygments.lex(code, lexer), formatter)
276 return pygments.format(pygments.lex(code, lexer), formatter)
277
277
278
278
279 class CodeHtmlFormatter(HtmlFormatter):
279 class CodeHtmlFormatter(HtmlFormatter):
280 """
280 """
281 My code Html Formatter for source codes
281 My code Html Formatter for source codes
282 """
282 """
283
283
284 def wrap(self, source, outfile):
284 def wrap(self, source, outfile):
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
286
286
287 def _wrap_code(self, source):
287 def _wrap_code(self, source):
288 for cnt, it in enumerate(source):
288 for cnt, it in enumerate(source):
289 i, t = it
289 i, t = it
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
291 yield i, t
291 yield i, t
292
292
293 def _wrap_tablelinenos(self, inner):
293 def _wrap_tablelinenos(self, inner):
294 dummyoutfile = StringIO.StringIO()
294 dummyoutfile = StringIO.StringIO()
295 lncount = 0
295 lncount = 0
296 for t, line in inner:
296 for t, line in inner:
297 if t:
297 if t:
298 lncount += 1
298 lncount += 1
299 dummyoutfile.write(line)
299 dummyoutfile.write(line)
300
300
301 fl = self.linenostart
301 fl = self.linenostart
302 mw = len(str(lncount + fl - 1))
302 mw = len(str(lncount + fl - 1))
303 sp = self.linenospecial
303 sp = self.linenospecial
304 st = self.linenostep
304 st = self.linenostep
305 la = self.lineanchors
305 la = self.lineanchors
306 aln = self.anchorlinenos
306 aln = self.anchorlinenos
307 nocls = self.noclasses
307 nocls = self.noclasses
308 if sp:
308 if sp:
309 lines = []
309 lines = []
310
310
311 for i in range(fl, fl + lncount):
311 for i in range(fl, fl + lncount):
312 if i % st == 0:
312 if i % st == 0:
313 if i % sp == 0:
313 if i % sp == 0:
314 if aln:
314 if aln:
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
316 (la, i, mw, i))
316 (la, i, mw, i))
317 else:
317 else:
318 lines.append('<span class="special">%*d</span>' % (mw, i))
318 lines.append('<span class="special">%*d</span>' % (mw, i))
319 else:
319 else:
320 if aln:
320 if aln:
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
322 else:
322 else:
323 lines.append('%*d' % (mw, i))
323 lines.append('%*d' % (mw, i))
324 else:
324 else:
325 lines.append('')
325 lines.append('')
326 ls = '\n'.join(lines)
326 ls = '\n'.join(lines)
327 else:
327 else:
328 lines = []
328 lines = []
329 for i in range(fl, fl + lncount):
329 for i in range(fl, fl + lncount):
330 if i % st == 0:
330 if i % st == 0:
331 if aln:
331 if aln:
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
333 else:
333 else:
334 lines.append('%*d' % (mw, i))
334 lines.append('%*d' % (mw, i))
335 else:
335 else:
336 lines.append('')
336 lines.append('')
337 ls = '\n'.join(lines)
337 ls = '\n'.join(lines)
338
338
339 # in case you wonder about the seemingly redundant <div> here: since the
339 # in case you wonder about the seemingly redundant <div> here: since the
340 # content in the other cell also is wrapped in a div, some browsers in
340 # content in the other cell also is wrapped in a div, some browsers in
341 # some configurations seem to mess up the formatting...
341 # some configurations seem to mess up the formatting...
342 if nocls:
342 if nocls:
343 yield 0, ('<table class="%stable">' % self.cssclass +
343 yield 0, ('<table class="%stable">' % self.cssclass +
344 '<tr><td><div class="linenodiv" '
344 '<tr><td><div class="linenodiv" '
345 'style="background-color: #f0f0f0; padding-right: 10px">'
345 'style="background-color: #f0f0f0; padding-right: 10px">'
346 '<pre style="line-height: 125%">' +
346 '<pre style="line-height: 125%">' +
347 ls + '</pre></div></td><td id="hlcode" class="code">')
347 ls + '</pre></div></td><td id="hlcode" class="code">')
348 else:
348 else:
349 yield 0, ('<table class="%stable">' % self.cssclass +
349 yield 0, ('<table class="%stable">' % self.cssclass +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
351 ls + '</pre></div></td><td id="hlcode" class="code">')
351 ls + '</pre></div></td><td id="hlcode" class="code">')
352 yield 0, dummyoutfile.getvalue()
352 yield 0, dummyoutfile.getvalue()
353 yield 0, '</td></tr></table>'
353 yield 0, '</td></tr></table>'
354
354
355
355
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
357 def __init__(self, **kw):
357 def __init__(self, **kw):
358 # only show these line numbers if set
358 # only show these line numbers if set
359 self.only_lines = kw.pop('only_line_numbers', [])
359 self.only_lines = kw.pop('only_line_numbers', [])
360 self.query_terms = kw.pop('query_terms', [])
360 self.query_terms = kw.pop('query_terms', [])
361 self.max_lines = kw.pop('max_lines', 5)
361 self.max_lines = kw.pop('max_lines', 5)
362 self.line_context = kw.pop('line_context', 3)
362 self.line_context = kw.pop('line_context', 3)
363 self.url = kw.pop('url', None)
363 self.url = kw.pop('url', None)
364
364
365 super(CodeHtmlFormatter, self).__init__(**kw)
365 super(CodeHtmlFormatter, self).__init__(**kw)
366
366
367 def _wrap_code(self, source):
367 def _wrap_code(self, source):
368 for cnt, it in enumerate(source):
368 for cnt, it in enumerate(source):
369 i, t = it
369 i, t = it
370 t = '<pre>%s</pre>' % t
370 t = '<pre>%s</pre>' % t
371 yield i, t
371 yield i, t
372
372
373 def _wrap_tablelinenos(self, inner):
373 def _wrap_tablelinenos(self, inner):
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
375
375
376 last_shown_line_number = 0
376 last_shown_line_number = 0
377 current_line_number = 1
377 current_line_number = 1
378
378
379 for t, line in inner:
379 for t, line in inner:
380 if not t:
380 if not t:
381 yield t, line
381 yield t, line
382 continue
382 continue
383
383
384 if current_line_number in self.only_lines:
384 if current_line_number in self.only_lines:
385 if last_shown_line_number + 1 != current_line_number:
385 if last_shown_line_number + 1 != current_line_number:
386 yield 0, '<tr>'
386 yield 0, '<tr>'
387 yield 0, '<td class="line">...</td>'
387 yield 0, '<td class="line">...</td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
389 yield 0, '</tr>'
389 yield 0, '</tr>'
390
390
391 yield 0, '<tr>'
391 yield 0, '<tr>'
392 if self.url:
392 if self.url:
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
394 self.url, current_line_number, current_line_number)
394 self.url, current_line_number, current_line_number)
395 else:
395 else:
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
397 current_line_number)
397 current_line_number)
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
399 yield 0, '</tr>'
399 yield 0, '</tr>'
400
400
401 last_shown_line_number = current_line_number
401 last_shown_line_number = current_line_number
402
402
403 current_line_number += 1
403 current_line_number += 1
404
404
405 yield 0, '</table>'
405 yield 0, '</table>'
406
406
407
407
408 def hsv_to_rgb(h, s, v):
408 def hsv_to_rgb(h, s, v):
409 """ Convert hsv color values to rgb """
409 """ Convert hsv color values to rgb """
410
410
411 if s == 0.0:
411 if s == 0.0:
412 return v, v, v
412 return v, v, v
413 i = int(h * 6.0) # XXX assume int() truncates!
413 i = int(h * 6.0) # XXX assume int() truncates!
414 f = (h * 6.0) - i
414 f = (h * 6.0) - i
415 p = v * (1.0 - s)
415 p = v * (1.0 - s)
416 q = v * (1.0 - s * f)
416 q = v * (1.0 - s * f)
417 t = v * (1.0 - s * (1.0 - f))
417 t = v * (1.0 - s * (1.0 - f))
418 i = i % 6
418 i = i % 6
419 if i == 0:
419 if i == 0:
420 return v, t, p
420 return v, t, p
421 if i == 1:
421 if i == 1:
422 return q, v, p
422 return q, v, p
423 if i == 2:
423 if i == 2:
424 return p, v, t
424 return p, v, t
425 if i == 3:
425 if i == 3:
426 return p, q, v
426 return p, q, v
427 if i == 4:
427 if i == 4:
428 return t, p, v
428 return t, p, v
429 if i == 5:
429 if i == 5:
430 return v, p, q
430 return v, p, q
431
431
432
432
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
434 """
434 """
435 Generator for getting n of evenly distributed colors using
435 Generator for getting n of evenly distributed colors using
436 hsv color and golden ratio. It always return same order of colors
436 hsv color and golden ratio. It always return same order of colors
437
437
438 :param n: number of colors to generate
438 :param n: number of colors to generate
439 :param saturation: saturation of returned colors
439 :param saturation: saturation of returned colors
440 :param lightness: lightness of returned colors
440 :param lightness: lightness of returned colors
441 :returns: RGB tuple
441 :returns: RGB tuple
442 """
442 """
443
443
444 golden_ratio = 0.618033988749895
444 golden_ratio = 0.618033988749895
445 h = 0.22717784590367374
445 h = 0.22717784590367374
446
446
447 for _ in xrange(n):
447 for _ in xrange(n):
448 h += golden_ratio
448 h += golden_ratio
449 h %= 1
449 h %= 1
450 HSV_tuple = [h, saturation, lightness]
450 HSV_tuple = [h, saturation, lightness]
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
453
453
454
454
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
456 """
456 """
457 Returns a function which when called with an argument returns a unique
457 Returns a function which when called with an argument returns a unique
458 color for that argument, eg.
458 color for that argument, eg.
459
459
460 :param n: number of colors to generate
460 :param n: number of colors to generate
461 :param saturation: saturation of returned colors
461 :param saturation: saturation of returned colors
462 :param lightness: lightness of returned colors
462 :param lightness: lightness of returned colors
463 :returns: css RGB string
463 :returns: css RGB string
464
464
465 >>> color_hash = color_hasher()
465 >>> color_hash = color_hasher()
466 >>> color_hash('hello')
466 >>> color_hash('hello')
467 'rgb(34, 12, 59)'
467 'rgb(34, 12, 59)'
468 >>> color_hash('hello')
468 >>> color_hash('hello')
469 'rgb(34, 12, 59)'
469 'rgb(34, 12, 59)'
470 >>> color_hash('other')
470 >>> color_hash('other')
471 'rgb(90, 224, 159)'
471 'rgb(90, 224, 159)'
472 """
472 """
473
473
474 color_dict = {}
474 color_dict = {}
475 cgenerator = unique_color_generator(
475 cgenerator = unique_color_generator(
476 saturation=saturation, lightness=lightness)
476 saturation=saturation, lightness=lightness)
477
477
478 def get_color_string(thing):
478 def get_color_string(thing):
479 if thing in color_dict:
479 if thing in color_dict:
480 col = color_dict[thing]
480 col = color_dict[thing]
481 else:
481 else:
482 col = color_dict[thing] = cgenerator.next()
482 col = color_dict[thing] = cgenerator.next()
483 return "rgb(%s)" % (', '.join(col))
483 return "rgb(%s)" % (', '.join(col))
484
484
485 return get_color_string
485 return get_color_string
486
486
487
487
488 def get_lexer_safe(mimetype=None, filepath=None):
488 def get_lexer_safe(mimetype=None, filepath=None):
489 """
489 """
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
491 defaulting to plain text if none could be found
491 defaulting to plain text if none could be found
492 """
492 """
493 lexer = None
493 lexer = None
494 try:
494 try:
495 if mimetype:
495 if mimetype:
496 lexer = get_lexer_for_mimetype(mimetype)
496 lexer = get_lexer_for_mimetype(mimetype)
497 if not lexer:
497 if not lexer:
498 lexer = get_lexer_for_filename(filepath)
498 lexer = get_lexer_for_filename(filepath)
499 except pygments.util.ClassNotFound:
499 except pygments.util.ClassNotFound:
500 pass
500 pass
501
501
502 if not lexer:
502 if not lexer:
503 lexer = get_lexer_by_name('text')
503 lexer = get_lexer_by_name('text')
504
504
505 return lexer
505 return lexer
506
506
507
507
508 def get_lexer_for_filenode(filenode):
508 def get_lexer_for_filenode(filenode):
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
510 return lexer
510 return lexer
511
511
512
512
513 def pygmentize(filenode, **kwargs):
513 def pygmentize(filenode, **kwargs):
514 """
514 """
515 pygmentize function using pygments
515 pygmentize function using pygments
516
516
517 :param filenode:
517 :param filenode:
518 """
518 """
519 lexer = get_lexer_for_filenode(filenode)
519 lexer = get_lexer_for_filenode(filenode)
520 return literal(code_highlight(filenode.content, lexer,
520 return literal(code_highlight(filenode.content, lexer,
521 CodeHtmlFormatter(**kwargs)))
521 CodeHtmlFormatter(**kwargs)))
522
522
523
523
524 def is_following_repo(repo_name, user_id):
524 def is_following_repo(repo_name, user_id):
525 from rhodecode.model.scm import ScmModel
525 from rhodecode.model.scm import ScmModel
526 return ScmModel().is_following_repo(repo_name, user_id)
526 return ScmModel().is_following_repo(repo_name, user_id)
527
527
528
528
529 class _Message(object):
529 class _Message(object):
530 """A message returned by ``Flash.pop_messages()``.
530 """A message returned by ``Flash.pop_messages()``.
531
531
532 Converting the message to a string returns the message text. Instances
532 Converting the message to a string returns the message text. Instances
533 also have the following attributes:
533 also have the following attributes:
534
534
535 * ``message``: the message text.
535 * ``message``: the message text.
536 * ``category``: the category specified when the message was created.
536 * ``category``: the category specified when the message was created.
537 """
537 """
538
538
539 def __init__(self, category, message):
539 def __init__(self, category, message):
540 self.category = category
540 self.category = category
541 self.message = message
541 self.message = message
542
542
543 def __str__(self):
543 def __str__(self):
544 return self.message
544 return self.message
545
545
546 __unicode__ = __str__
546 __unicode__ = __str__
547
547
548 def __html__(self):
548 def __html__(self):
549 return escape(safe_unicode(self.message))
549 return escape(safe_unicode(self.message))
550
550
551
551
552 class Flash(object):
552 class Flash(object):
553 # List of allowed categories. If None, allow any category.
553 # List of allowed categories. If None, allow any category.
554 categories = ["warning", "notice", "error", "success"]
554 categories = ["warning", "notice", "error", "success"]
555
555
556 # Default category if none is specified.
556 # Default category if none is specified.
557 default_category = "notice"
557 default_category = "notice"
558
558
559 def __init__(self, session_key="flash", categories=None,
559 def __init__(self, session_key="flash", categories=None,
560 default_category=None):
560 default_category=None):
561 """
561 """
562 Instantiate a ``Flash`` object.
562 Instantiate a ``Flash`` object.
563
563
564 ``session_key`` is the key to save the messages under in the user's
564 ``session_key`` is the key to save the messages under in the user's
565 session.
565 session.
566
566
567 ``categories`` is an optional list which overrides the default list
567 ``categories`` is an optional list which overrides the default list
568 of categories.
568 of categories.
569
569
570 ``default_category`` overrides the default category used for messages
570 ``default_category`` overrides the default category used for messages
571 when none is specified.
571 when none is specified.
572 """
572 """
573 self.session_key = session_key
573 self.session_key = session_key
574 if categories is not None:
574 if categories is not None:
575 self.categories = categories
575 self.categories = categories
576 if default_category is not None:
576 if default_category is not None:
577 self.default_category = default_category
577 self.default_category = default_category
578 if self.categories and self.default_category not in self.categories:
578 if self.categories and self.default_category not in self.categories:
579 raise ValueError(
579 raise ValueError(
580 "unrecognized default category %r" % (self.default_category,))
580 "unrecognized default category %r" % (self.default_category,))
581
581
582 def pop_messages(self, session=None, request=None):
582 def pop_messages(self, session=None, request=None):
583 """
583 """
584 Return all accumulated messages and delete them from the session.
584 Return all accumulated messages and delete them from the session.
585
585
586 The return value is a list of ``Message`` objects.
586 The return value is a list of ``Message`` objects.
587 """
587 """
588 messages = []
588 messages = []
589
589
590 if not session:
590 if not session:
591 if not request:
591 if not request:
592 request = get_current_request()
592 request = get_current_request()
593 session = request.session
593 session = request.session
594
594
595 # Pop the 'old' pylons flash messages. They are tuples of the form
595 # Pop the 'old' pylons flash messages. They are tuples of the form
596 # (category, message)
596 # (category, message)
597 for cat, msg in session.pop(self.session_key, []):
597 for cat, msg in session.pop(self.session_key, []):
598 messages.append(_Message(cat, msg))
598 messages.append(_Message(cat, msg))
599
599
600 # Pop the 'new' pyramid flash messages for each category as list
600 # Pop the 'new' pyramid flash messages for each category as list
601 # of strings.
601 # of strings.
602 for cat in self.categories:
602 for cat in self.categories:
603 for msg in session.pop_flash(queue=cat):
603 for msg in session.pop_flash(queue=cat):
604 messages.append(_Message(cat, msg))
604 messages.append(_Message(cat, msg))
605 # Map messages from the default queue to the 'notice' category.
605 # Map messages from the default queue to the 'notice' category.
606 for msg in session.pop_flash():
606 for msg in session.pop_flash():
607 messages.append(_Message('notice', msg))
607 messages.append(_Message('notice', msg))
608
608
609 session.save()
609 session.save()
610 return messages
610 return messages
611
611
612 def json_alerts(self, session=None, request=None):
612 def json_alerts(self, session=None, request=None):
613 payloads = []
613 payloads = []
614 messages = flash.pop_messages(session=session, request=request)
614 messages = flash.pop_messages(session=session, request=request)
615 if messages:
615 if messages:
616 for message in messages:
616 for message in messages:
617 subdata = {}
617 subdata = {}
618 if hasattr(message.message, 'rsplit'):
618 if hasattr(message.message, 'rsplit'):
619 flash_data = message.message.rsplit('|DELIM|', 1)
619 flash_data = message.message.rsplit('|DELIM|', 1)
620 org_message = flash_data[0]
620 org_message = flash_data[0]
621 if len(flash_data) > 1:
621 if len(flash_data) > 1:
622 subdata = json.loads(flash_data[1])
622 subdata = json.loads(flash_data[1])
623 else:
623 else:
624 org_message = message.message
624 org_message = message.message
625 payloads.append({
625 payloads.append({
626 'message': {
626 'message': {
627 'message': u'{}'.format(org_message),
627 'message': u'{}'.format(org_message),
628 'level': message.category,
628 'level': message.category,
629 'force': True,
629 'force': True,
630 'subdata': subdata
630 'subdata': subdata
631 }
631 }
632 })
632 })
633 return json.dumps(payloads)
633 return json.dumps(payloads)
634
634
635 def __call__(self, message, category=None, ignore_duplicate=False,
635 def __call__(self, message, category=None, ignore_duplicate=False,
636 session=None, request=None):
636 session=None, request=None):
637
637
638 if not session:
638 if not session:
639 if not request:
639 if not request:
640 request = get_current_request()
640 request = get_current_request()
641 session = request.session
641 session = request.session
642
642
643 session.flash(
643 session.flash(
644 message, queue=category, allow_duplicate=not ignore_duplicate)
644 message, queue=category, allow_duplicate=not ignore_duplicate)
645
645
646
646
647 flash = Flash()
647 flash = Flash()
648
648
649 #==============================================================================
649 #==============================================================================
650 # SCM FILTERS available via h.
650 # SCM FILTERS available via h.
651 #==============================================================================
651 #==============================================================================
652 from rhodecode.lib.vcs.utils import author_name, author_email
652 from rhodecode.lib.vcs.utils import author_name, author_email
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
654 from rhodecode.model.db import User, ChangesetStatus
654 from rhodecode.model.db import User, ChangesetStatus
655
655
656 capitalize = lambda x: x.capitalize()
656 capitalize = lambda x: x.capitalize()
657 email = author_email
657 email = author_email
658 short_id = lambda x: x[:12]
658 short_id = lambda x: x[:12]
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
660
660
661
661
662 import pytz
662 import pytz
663 import tzlocal
663 import tzlocal
664 local_timezone = tzlocal.get_localzone()
664 local_timezone = tzlocal.get_localzone()
665
665
666
666
667 def age_component(datetime_iso, value=None, time_is_local=False):
667 def age_component(datetime_iso, value=None, time_is_local=False):
668 title = value or format_date(datetime_iso)
668 title = value or format_date(datetime_iso)
669 tzinfo = '+00:00'
669 tzinfo = '+00:00'
670
670
671 # detect if we have a timezone info, otherwise, add it
671 # detect if we have a timezone info, otherwise, add it
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
674 if force_timezone:
674 if force_timezone:
675 force_timezone = pytz.timezone(force_timezone)
675 force_timezone = pytz.timezone(force_timezone)
676 timezone = force_timezone or local_timezone
676 timezone = force_timezone or local_timezone
677 offset = timezone.localize(datetime_iso).strftime('%z')
677 offset = timezone.localize(datetime_iso).strftime('%z')
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
679
679
680 return literal(
680 return literal(
681 '<time class="timeago tooltip" '
681 '<time class="timeago tooltip" '
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
683 datetime_iso, title, tzinfo))
683 datetime_iso, title, tzinfo))
684
684
685
685
686 def _shorten_commit_id(commit_id, commit_len=None):
686 def _shorten_commit_id(commit_id, commit_len=None):
687 if commit_len is None:
687 if commit_len is None:
688 request = get_current_request()
688 request = get_current_request()
689 commit_len = request.call_context.visual.show_sha_length
689 commit_len = request.call_context.visual.show_sha_length
690 return commit_id[:commit_len]
690 return commit_id[:commit_len]
691
691
692
692
693 def show_id(commit, show_idx=None, commit_len=None):
693 def show_id(commit, show_idx=None, commit_len=None):
694 """
694 """
695 Configurable function that shows ID
695 Configurable function that shows ID
696 by default it's r123:fffeeefffeee
696 by default it's r123:fffeeefffeee
697
697
698 :param commit: commit instance
698 :param commit: commit instance
699 """
699 """
700 if show_idx is None:
700 if show_idx is None:
701 request = get_current_request()
701 request = get_current_request()
702 show_idx = request.call_context.visual.show_revision_number
702 show_idx = request.call_context.visual.show_revision_number
703
703
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
705 if show_idx:
705 if show_idx:
706 return 'r%s:%s' % (commit.idx, raw_id)
706 return 'r%s:%s' % (commit.idx, raw_id)
707 else:
707 else:
708 return '%s' % (raw_id, )
708 return '%s' % (raw_id, )
709
709
710
710
711 def format_date(date):
711 def format_date(date):
712 """
712 """
713 use a standardized formatting for dates used in RhodeCode
713 use a standardized formatting for dates used in RhodeCode
714
714
715 :param date: date/datetime object
715 :param date: date/datetime object
716 :return: formatted date
716 :return: formatted date
717 """
717 """
718
718
719 if date:
719 if date:
720 _fmt = "%a, %d %b %Y %H:%M:%S"
720 _fmt = "%a, %d %b %Y %H:%M:%S"
721 return safe_unicode(date.strftime(_fmt))
721 return safe_unicode(date.strftime(_fmt))
722
722
723 return u""
723 return u""
724
724
725
725
726 class _RepoChecker(object):
726 class _RepoChecker(object):
727
727
728 def __init__(self, backend_alias):
728 def __init__(self, backend_alias):
729 self._backend_alias = backend_alias
729 self._backend_alias = backend_alias
730
730
731 def __call__(self, repository):
731 def __call__(self, repository):
732 if hasattr(repository, 'alias'):
732 if hasattr(repository, 'alias'):
733 _type = repository.alias
733 _type = repository.alias
734 elif hasattr(repository, 'repo_type'):
734 elif hasattr(repository, 'repo_type'):
735 _type = repository.repo_type
735 _type = repository.repo_type
736 else:
736 else:
737 _type = repository
737 _type = repository
738 return _type == self._backend_alias
738 return _type == self._backend_alias
739
739
740
740
741 is_git = _RepoChecker('git')
741 is_git = _RepoChecker('git')
742 is_hg = _RepoChecker('hg')
742 is_hg = _RepoChecker('hg')
743 is_svn = _RepoChecker('svn')
743 is_svn = _RepoChecker('svn')
744
744
745
745
746 def get_repo_type_by_name(repo_name):
746 def get_repo_type_by_name(repo_name):
747 repo = Repository.get_by_repo_name(repo_name)
747 repo = Repository.get_by_repo_name(repo_name)
748 if repo:
748 if repo:
749 return repo.repo_type
749 return repo.repo_type
750
750
751
751
752 def is_svn_without_proxy(repository):
752 def is_svn_without_proxy(repository):
753 if is_svn(repository):
753 if is_svn(repository):
754 from rhodecode.model.settings import VcsSettingsModel
754 from rhodecode.model.settings import VcsSettingsModel
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
757 return False
757 return False
758
758
759
759
760 def discover_user(author):
760 def discover_user(author):
761 """
761 """
762 Tries to discover RhodeCode User based on the autho string. Author string
762 Tries to discover RhodeCode User based on the autho string. Author string
763 is typically `FirstName LastName <email@address.com>`
763 is typically `FirstName LastName <email@address.com>`
764 """
764 """
765
765
766 # if author is already an instance use it for extraction
766 # if author is already an instance use it for extraction
767 if isinstance(author, User):
767 if isinstance(author, User):
768 return author
768 return author
769
769
770 # Valid email in the attribute passed, see if they're in the system
770 # Valid email in the attribute passed, see if they're in the system
771 _email = author_email(author)
771 _email = author_email(author)
772 if _email != '':
772 if _email != '':
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
774 if user is not None:
774 if user is not None:
775 return user
775 return user
776
776
777 # Maybe it's a username, we try to extract it and fetch by username ?
777 # Maybe it's a username, we try to extract it and fetch by username ?
778 _author = author_name(author)
778 _author = author_name(author)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
780 if user is not None:
780 if user is not None:
781 return user
781 return user
782
782
783 return None
783 return None
784
784
785
785
786 def email_or_none(author):
786 def email_or_none(author):
787 # extract email from the commit string
787 # extract email from the commit string
788 _email = author_email(author)
788 _email = author_email(author)
789
789
790 # If we have an email, use it, otherwise
790 # If we have an email, use it, otherwise
791 # see if it contains a username we can get an email from
791 # see if it contains a username we can get an email from
792 if _email != '':
792 if _email != '':
793 return _email
793 return _email
794 else:
794 else:
795 user = User.get_by_username(
795 user = User.get_by_username(
796 author_name(author), case_insensitive=True, cache=True)
796 author_name(author), case_insensitive=True, cache=True)
797
797
798 if user is not None:
798 if user is not None:
799 return user.email
799 return user.email
800
800
801 # No valid email, not a valid user in the system, none!
801 # No valid email, not a valid user in the system, none!
802 return None
802 return None
803
803
804
804
805 def link_to_user(author, length=0, **kwargs):
805 def link_to_user(author, length=0, **kwargs):
806 user = discover_user(author)
806 user = discover_user(author)
807 # user can be None, but if we have it already it means we can re-use it
807 # user can be None, but if we have it already it means we can re-use it
808 # in the person() function, so we save 1 intensive-query
808 # in the person() function, so we save 1 intensive-query
809 if user:
809 if user:
810 author = user
810 author = user
811
811
812 display_person = person(author, 'username_or_name_or_email')
812 display_person = person(author, 'username_or_name_or_email')
813 if length:
813 if length:
814 display_person = shorter(display_person, length)
814 display_person = shorter(display_person, length)
815
815
816 if user:
816 if user:
817 return link_to(
817 return link_to(
818 escape(display_person),
818 escape(display_person),
819 route_path('user_profile', username=user.username),
819 route_path('user_profile', username=user.username),
820 **kwargs)
820 **kwargs)
821 else:
821 else:
822 return escape(display_person)
822 return escape(display_person)
823
823
824
824
825 def link_to_group(users_group_name, **kwargs):
825 def link_to_group(users_group_name, **kwargs):
826 return link_to(
826 return link_to(
827 escape(users_group_name),
827 escape(users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
829 **kwargs)
829 **kwargs)
830
830
831
831
832 def person(author, show_attr="username_and_name"):
832 def person(author, show_attr="username_and_name"):
833 user = discover_user(author)
833 user = discover_user(author)
834 if user:
834 if user:
835 return getattr(user, show_attr)
835 return getattr(user, show_attr)
836 else:
836 else:
837 _author = author_name(author)
837 _author = author_name(author)
838 _email = email(author)
838 _email = email(author)
839 return _author or _email
839 return _author or _email
840
840
841
841
842 def author_string(email):
842 def author_string(email):
843 if email:
843 if email:
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
845 if user:
845 if user:
846 if user.first_name or user.last_name:
846 if user.first_name or user.last_name:
847 return '%s %s &lt;%s&gt;' % (
847 return '%s %s &lt;%s&gt;' % (
848 user.first_name, user.last_name, email)
848 user.first_name, user.last_name, email)
849 else:
849 else:
850 return email
850 return email
851 else:
851 else:
852 return email
852 return email
853 else:
853 else:
854 return None
854 return None
855
855
856
856
857 def person_by_id(id_, show_attr="username_and_name"):
857 def person_by_id(id_, show_attr="username_and_name"):
858 # attr to return from fetched user
858 # attr to return from fetched user
859 person_getter = lambda usr: getattr(usr, show_attr)
859 person_getter = lambda usr: getattr(usr, show_attr)
860
860
861 #maybe it's an ID ?
861 #maybe it's an ID ?
862 if str(id_).isdigit() or isinstance(id_, int):
862 if str(id_).isdigit() or isinstance(id_, int):
863 id_ = int(id_)
863 id_ = int(id_)
864 user = User.get(id_)
864 user = User.get(id_)
865 if user is not None:
865 if user is not None:
866 return person_getter(user)
866 return person_getter(user)
867 return id_
867 return id_
868
868
869
869
870 def gravatar_with_user(request, author, show_disabled=False):
870 def gravatar_with_user(request, author, show_disabled=False):
871 _render = request.get_partial_renderer(
871 _render = request.get_partial_renderer(
872 'rhodecode:templates/base/base.mako')
872 'rhodecode:templates/base/base.mako')
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
874
874
875
875
876 tags_paterns = OrderedDict((
876 tags_paterns = OrderedDict((
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
878 '<div class="metatag" tag="lang">\\2</div>')),
878 '<div class="metatag" tag="lang">\\2</div>')),
879
879
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
882
882
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
885
885
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
888
888
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
891
891
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
894
894
895 # label in grey
895 # label in grey
896 ('label', (re.compile(r'\[([a-z]+)\]'),
896 ('label', (re.compile(r'\[([a-z]+)\]'),
897 '<div class="metatag" tag="label">\\1</div>')),
897 '<div class="metatag" tag="label">\\1</div>')),
898
898
899 # generic catch all in grey
899 # generic catch all in grey
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
901 '<div class="metatag" tag="generic">\\1</div>')),
901 '<div class="metatag" tag="generic">\\1</div>')),
902 ))
902 ))
903
903
904
904
905 def extract_metatags(value):
905 def extract_metatags(value):
906 """
906 """
907 Extract supported meta-tags from given text value
907 Extract supported meta-tags from given text value
908 """
908 """
909 tags = []
909 tags = []
910 if not value:
910 if not value:
911 return tags, ''
911 return tags, ''
912
912
913 for key, val in tags_paterns.items():
913 for key, val in tags_paterns.items():
914 pat, replace_html = val
914 pat, replace_html = val
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
916 value = pat.sub('', value)
916 value = pat.sub('', value)
917
917
918 return tags, value
918 return tags, value
919
919
920
920
921 def style_metatag(tag_type, value):
921 def style_metatag(tag_type, value):
922 """
922 """
923 converts tags from value into html equivalent
923 converts tags from value into html equivalent
924 """
924 """
925 if not value:
925 if not value:
926 return ''
926 return ''
927
927
928 html_value = value
928 html_value = value
929 tag_data = tags_paterns.get(tag_type)
929 tag_data = tags_paterns.get(tag_type)
930 if tag_data:
930 if tag_data:
931 pat, replace_html = tag_data
931 pat, replace_html = tag_data
932 # convert to plain `unicode` instead of a markup tag to be used in
932 # convert to plain `unicode` instead of a markup tag to be used in
933 # regex expressions. safe_unicode doesn't work here
933 # regex expressions. safe_unicode doesn't work here
934 html_value = pat.sub(replace_html, unicode(value))
934 html_value = pat.sub(replace_html, unicode(value))
935
935
936 return html_value
936 return html_value
937
937
938
938
939 def bool2icon(value, show_at_false=True):
939 def bool2icon(value, show_at_false=True):
940 """
940 """
941 Returns boolean value of a given value, represented as html element with
941 Returns boolean value of a given value, represented as html element with
942 classes that will represent icons
942 classes that will represent icons
943
943
944 :param value: given value to convert to html node
944 :param value: given value to convert to html node
945 """
945 """
946
946
947 if value: # does bool conversion
947 if value: # does bool conversion
948 return HTML.tag('i', class_="icon-true")
948 return HTML.tag('i', class_="icon-true")
949 else: # not true as bool
949 else: # not true as bool
950 if show_at_false:
950 if show_at_false:
951 return HTML.tag('i', class_="icon-false")
951 return HTML.tag('i', class_="icon-false")
952 return HTML.tag('i')
952 return HTML.tag('i')
953
953
954 #==============================================================================
954 #==============================================================================
955 # PERMS
955 # PERMS
956 #==============================================================================
956 #==============================================================================
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
960 csrf_token_key
960 csrf_token_key
961
961
962
962
963 #==============================================================================
963 #==============================================================================
964 # GRAVATAR URL
964 # GRAVATAR URL
965 #==============================================================================
965 #==============================================================================
966 class InitialsGravatar(object):
966 class InitialsGravatar(object):
967 def __init__(self, email_address, first_name, last_name, size=30,
967 def __init__(self, email_address, first_name, last_name, size=30,
968 background=None, text_color='#fff'):
968 background=None, text_color='#fff'):
969 self.size = size
969 self.size = size
970 self.first_name = first_name
970 self.first_name = first_name
971 self.last_name = last_name
971 self.last_name = last_name
972 self.email_address = email_address
972 self.email_address = email_address
973 self.background = background or self.str2color(email_address)
973 self.background = background or self.str2color(email_address)
974 self.text_color = text_color
974 self.text_color = text_color
975
975
976 def get_color_bank(self):
976 def get_color_bank(self):
977 """
977 """
978 returns a predefined list of colors that gravatars can use.
978 returns a predefined list of colors that gravatars can use.
979 Those are randomized distinct colors that guarantee readability and
979 Those are randomized distinct colors that guarantee readability and
980 uniqueness.
980 uniqueness.
981
981
982 generated with: http://phrogz.net/css/distinct-colors.html
982 generated with: http://phrogz.net/css/distinct-colors.html
983 """
983 """
984 return [
984 return [
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1029 '#4f8c46', '#368dd9', '#5c0073'
1029 '#4f8c46', '#368dd9', '#5c0073'
1030 ]
1030 ]
1031
1031
1032 def rgb_to_hex_color(self, rgb_tuple):
1032 def rgb_to_hex_color(self, rgb_tuple):
1033 """
1033 """
1034 Converts an rgb_tuple passed to an hex color.
1034 Converts an rgb_tuple passed to an hex color.
1035
1035
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1037 """
1037 """
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1039
1039
1040 def email_to_int_list(self, email_str):
1040 def email_to_int_list(self, email_str):
1041 """
1041 """
1042 Get every byte of the hex digest value of email and turn it to integer.
1042 Get every byte of the hex digest value of email and turn it to integer.
1043 It's going to be always between 0-255
1043 It's going to be always between 0-255
1044 """
1044 """
1045 digest = md5_safe(email_str.lower())
1045 digest = md5_safe(email_str.lower())
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1047
1047
1048 def pick_color_bank_index(self, email_str, color_bank):
1048 def pick_color_bank_index(self, email_str, color_bank):
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1050
1050
1051 def str2color(self, email_str):
1051 def str2color(self, email_str):
1052 """
1052 """
1053 Tries to map in a stable algorithm an email to color
1053 Tries to map in a stable algorithm an email to color
1054
1054
1055 :param email_str:
1055 :param email_str:
1056 """
1056 """
1057 color_bank = self.get_color_bank()
1057 color_bank = self.get_color_bank()
1058 # pick position (module it's length so we always find it in the
1058 # pick position (module it's length so we always find it in the
1059 # bank even if it's smaller than 256 values
1059 # bank even if it's smaller than 256 values
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1061 return color_bank[pos]
1061 return color_bank[pos]
1062
1062
1063 def normalize_email(self, email_address):
1063 def normalize_email(self, email_address):
1064 import unicodedata
1064 import unicodedata
1065 # default host used to fill in the fake/missing email
1065 # default host used to fill in the fake/missing email
1066 default_host = u'localhost'
1066 default_host = u'localhost'
1067
1067
1068 if not email_address:
1068 if not email_address:
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1070
1070
1071 email_address = safe_unicode(email_address)
1071 email_address = safe_unicode(email_address)
1072
1072
1073 if u'@' not in email_address:
1073 if u'@' not in email_address:
1074 email_address = u'%s@%s' % (email_address, default_host)
1074 email_address = u'%s@%s' % (email_address, default_host)
1075
1075
1076 if email_address.endswith(u'@'):
1076 if email_address.endswith(u'@'):
1077 email_address = u'%s%s' % (email_address, default_host)
1077 email_address = u'%s%s' % (email_address, default_host)
1078
1078
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1080 .encode('ascii', 'ignore')
1080 .encode('ascii', 'ignore')
1081 return email_address
1081 return email_address
1082
1082
1083 def get_initials(self):
1083 def get_initials(self):
1084 """
1084 """
1085 Returns 2 letter initials calculated based on the input.
1085 Returns 2 letter initials calculated based on the input.
1086 The algorithm picks first given email address, and takes first letter
1086 The algorithm picks first given email address, and takes first letter
1087 of part before @, and then the first letter of server name. In case
1087 of part before @, and then the first letter of server name. In case
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1089 the server letter with first letter of somestring2
1089 the server letter with first letter of somestring2
1090
1090
1091 In case function was initialized with both first and lastname, this
1091 In case function was initialized with both first and lastname, this
1092 overrides the extraction from email by first letter of the first and
1092 overrides the extraction from email by first letter of the first and
1093 last name. We add special logic to that functionality, In case Full name
1093 last name. We add special logic to that functionality, In case Full name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1095 (Von Rossum) picking `R`.
1095 (Von Rossum) picking `R`.
1096
1096
1097 Function also normalizes the non-ascii characters to they ascii
1097 Function also normalizes the non-ascii characters to they ascii
1098 representation, eg Δ„ => A
1098 representation, eg Δ„ => A
1099 """
1099 """
1100 import unicodedata
1100 import unicodedata
1101 # replace non-ascii to ascii
1101 # replace non-ascii to ascii
1102 first_name = unicodedata.normalize(
1102 first_name = unicodedata.normalize(
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1104 last_name = unicodedata.normalize(
1104 last_name = unicodedata.normalize(
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1106
1106
1107 # do NFKD encoding, and also make sure email has proper format
1107 # do NFKD encoding, and also make sure email has proper format
1108 email_address = self.normalize_email(self.email_address)
1108 email_address = self.normalize_email(self.email_address)
1109
1109
1110 # first push the email initials
1110 # first push the email initials
1111 prefix, server = email_address.split('@', 1)
1111 prefix, server = email_address.split('@', 1)
1112
1112
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1114 _dot_split = prefix.rsplit('.', 1)
1114 _dot_split = prefix.rsplit('.', 1)
1115 if len(_dot_split) == 2 and _dot_split[1]:
1115 if len(_dot_split) == 2 and _dot_split[1]:
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1117 else:
1117 else:
1118 initials = [prefix[0], server[0]]
1118 initials = [prefix[0], server[0]]
1119
1119
1120 # then try to replace either first_name or last_name
1120 # then try to replace either first_name or last_name
1121 fn_letter = (first_name or " ")[0].strip()
1121 fn_letter = (first_name or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1123
1123
1124 if fn_letter:
1124 if fn_letter:
1125 initials[0] = fn_letter
1125 initials[0] = fn_letter
1126
1126
1127 if ln_letter:
1127 if ln_letter:
1128 initials[1] = ln_letter
1128 initials[1] = ln_letter
1129
1129
1130 return ''.join(initials).upper()
1130 return ''.join(initials).upper()
1131
1131
1132 def get_img_data_by_type(self, font_family, img_type):
1132 def get_img_data_by_type(self, font_family, img_type):
1133 default_user = """
1133 default_user = """
1134 <svg xmlns="http://www.w3.org/2000/svg"
1134 <svg xmlns="http://www.w3.org/2000/svg"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1136 viewBox="-15 -10 439.165 429.164"
1136 viewBox="-15 -10 439.165 429.164"
1137
1137
1138 xml:space="preserve"
1138 xml:space="preserve"
1139 style="background:{background};" >
1139 style="background:{background};" >
1140
1140
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1144 168.596,153.916,216.671,
1144 168.596,153.916,216.671,
1145 204.583,216.671z" fill="{text_color}"/>
1145 204.583,216.671z" fill="{text_color}"/>
1146 <path d="M407.164,374.717L360.88,
1146 <path d="M407.164,374.717L360.88,
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1150 0-48.762-8.122-69.078-23.488
1150 0-48.762-8.122-69.078-23.488
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1155 19.402-10.527 C409.699,390.129,
1155 19.402-10.527 C409.699,390.129,
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1157 </svg>""".format(
1157 </svg>""".format(
1158 size=self.size,
1158 size=self.size,
1159 background='#979797', # @grey4
1159 background='#979797', # @grey4
1160 text_color=self.text_color,
1160 text_color=self.text_color,
1161 font_family=font_family)
1161 font_family=font_family)
1162
1162
1163 return {
1163 return {
1164 "default_user": default_user
1164 "default_user": default_user
1165 }[img_type]
1165 }[img_type]
1166
1166
1167 def get_img_data(self, svg_type=None):
1167 def get_img_data(self, svg_type=None):
1168 """
1168 """
1169 generates the svg metadata for image
1169 generates the svg metadata for image
1170 """
1170 """
1171 fonts = [
1171 fonts = [
1172 '-apple-system',
1172 '-apple-system',
1173 'BlinkMacSystemFont',
1173 'BlinkMacSystemFont',
1174 'Segoe UI',
1174 'Segoe UI',
1175 'Roboto',
1175 'Roboto',
1176 'Oxygen-Sans',
1176 'Oxygen-Sans',
1177 'Ubuntu',
1177 'Ubuntu',
1178 'Cantarell',
1178 'Cantarell',
1179 'Helvetica Neue',
1179 'Helvetica Neue',
1180 'sans-serif'
1180 'sans-serif'
1181 ]
1181 ]
1182 font_family = ','.join(fonts)
1182 font_family = ','.join(fonts)
1183 if svg_type:
1183 if svg_type:
1184 return self.get_img_data_by_type(font_family, svg_type)
1184 return self.get_img_data_by_type(font_family, svg_type)
1185
1185
1186 initials = self.get_initials()
1186 initials = self.get_initials()
1187 img_data = """
1187 img_data = """
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1189 width="{size}" height="{size}"
1189 width="{size}" height="{size}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1191 viewBox="0 0 {size} {size}">
1191 viewBox="0 0 {size} {size}">
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1193 pointer-events="auto" fill="{text_color}"
1193 pointer-events="auto" fill="{text_color}"
1194 font-family="{font_family}"
1194 font-family="{font_family}"
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1196 </text>
1196 </text>
1197 </svg>""".format(
1197 </svg>""".format(
1198 size=self.size,
1198 size=self.size,
1199 f_size=self.size/1.85, # scale the text inside the box nicely
1199 f_size=self.size/1.85, # scale the text inside the box nicely
1200 background=self.background,
1200 background=self.background,
1201 text_color=self.text_color,
1201 text_color=self.text_color,
1202 text=initials.upper(),
1202 text=initials.upper(),
1203 font_family=font_family)
1203 font_family=font_family)
1204
1204
1205 return img_data
1205 return img_data
1206
1206
1207 def generate_svg(self, svg_type=None):
1207 def generate_svg(self, svg_type=None):
1208 img_data = self.get_img_data(svg_type)
1208 img_data = self.get_img_data(svg_type)
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1210
1210
1211
1211
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1213 svg_type = None
1213 svg_type = None
1214 if email_address == User.DEFAULT_USER_EMAIL:
1214 if email_address == User.DEFAULT_USER_EMAIL:
1215 svg_type = 'default_user'
1215 svg_type = 'default_user'
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1217 return klass.generate_svg(svg_type=svg_type)
1217 return klass.generate_svg(svg_type=svg_type)
1218
1218
1219
1219
1220 def gravatar_url(email_address, size=30, request=None):
1220 def gravatar_url(email_address, size=30, request=None):
1221 request = get_current_request()
1221 request = get_current_request()
1222 _use_gravatar = request.call_context.visual.use_gravatar
1222 _use_gravatar = request.call_context.visual.use_gravatar
1223 _gravatar_url = request.call_context.visual.gravatar_url
1223 _gravatar_url = request.call_context.visual.gravatar_url
1224
1224
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1226
1226
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1228 if isinstance(email_address, unicode):
1228 if isinstance(email_address, unicode):
1229 # hashlib crashes on unicode items
1229 # hashlib crashes on unicode items
1230 email_address = safe_str(email_address)
1230 email_address = safe_str(email_address)
1231
1231
1232 # empty email or default user
1232 # empty email or default user
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1235
1235
1236 if _use_gravatar:
1236 if _use_gravatar:
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1238 # get the host and schema here.
1238 # get the host and schema here.
1239 request = get_current_request()
1239 request = get_current_request()
1240 tmpl = safe_str(_gravatar_url)
1240 tmpl = safe_str(_gravatar_url)
1241 tmpl = tmpl.replace('{email}', email_address)\
1241 tmpl = tmpl.replace('{email}', email_address)\
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1243 .replace('{netloc}', request.host)\
1243 .replace('{netloc}', request.host)\
1244 .replace('{scheme}', request.scheme)\
1244 .replace('{scheme}', request.scheme)\
1245 .replace('{size}', safe_str(size))
1245 .replace('{size}', safe_str(size))
1246 return tmpl
1246 return tmpl
1247 else:
1247 else:
1248 return initials_gravatar(email_address, '', '', size=size)
1248 return initials_gravatar(email_address, '', '', size=size)
1249
1249
1250
1250
1251 class Page(_Page):
1251 class Page(_Page):
1252 """
1252 """
1253 Custom pager to match rendering style with paginator
1253 Custom pager to match rendering style with paginator
1254 """
1254 """
1255
1255
1256 def _get_pos(self, cur_page, max_page, items):
1256 def _get_pos(self, cur_page, max_page, items):
1257 edge = (items / 2) + 1
1257 edge = (items / 2) + 1
1258 if (cur_page <= edge):
1258 if (cur_page <= edge):
1259 radius = max(items / 2, items - cur_page)
1259 radius = max(items / 2, items - cur_page)
1260 elif (max_page - cur_page) < edge:
1260 elif (max_page - cur_page) < edge:
1261 radius = (items - 1) - (max_page - cur_page)
1261 radius = (items - 1) - (max_page - cur_page)
1262 else:
1262 else:
1263 radius = items / 2
1263 radius = items / 2
1264
1264
1265 left = max(1, (cur_page - (radius)))
1265 left = max(1, (cur_page - (radius)))
1266 right = min(max_page, cur_page + (radius))
1266 right = min(max_page, cur_page + (radius))
1267 return left, cur_page, right
1267 return left, cur_page, right
1268
1268
1269 def _range(self, regexp_match):
1269 def _range(self, regexp_match):
1270 """
1270 """
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1272
1272
1273 Arguments:
1273 Arguments:
1274
1274
1275 regexp_match
1275 regexp_match
1276 A "re" (regular expressions) match object containing the
1276 A "re" (regular expressions) match object containing the
1277 radius of linked pages around the current page in
1277 radius of linked pages around the current page in
1278 regexp_match.group(1) as a string
1278 regexp_match.group(1) as a string
1279
1279
1280 This function is supposed to be called as a callable in
1280 This function is supposed to be called as a callable in
1281 re.sub.
1281 re.sub.
1282
1282
1283 """
1283 """
1284 radius = int(regexp_match.group(1))
1284 radius = int(regexp_match.group(1))
1285
1285
1286 # Compute the first and last page number within the radius
1286 # Compute the first and last page number within the radius
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1288 # -> leftmost_page = 5
1288 # -> leftmost_page = 5
1289 # -> rightmost_page = 9
1289 # -> rightmost_page = 9
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1291 self.last_page,
1291 self.last_page,
1292 (radius * 2) + 1)
1292 (radius * 2) + 1)
1293 nav_items = []
1293 nav_items = []
1294
1294
1295 # Create a link to the first page (unless we are on the first page
1295 # Create a link to the first page (unless we are on the first page
1296 # or there would be no need to insert '..' spacers)
1296 # or there would be no need to insert '..' spacers)
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1299
1299
1300 # Insert dots if there are pages between the first page
1300 # Insert dots if there are pages between the first page
1301 # and the currently displayed page range
1301 # and the currently displayed page range
1302 if leftmost_page - self.first_page > 1:
1302 if leftmost_page - self.first_page > 1:
1303 # Wrap in a SPAN tag if nolink_attr is set
1303 # Wrap in a SPAN tag if nolink_attr is set
1304 text = '..'
1304 text = '..'
1305 if self.dotdot_attr:
1305 if self.dotdot_attr:
1306 text = HTML.span(c=text, **self.dotdot_attr)
1306 text = HTML.span(c=text, **self.dotdot_attr)
1307 nav_items.append(text)
1307 nav_items.append(text)
1308
1308
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1310 # Hilight the current page number and do not use a link
1310 # Hilight the current page number and do not use a link
1311 if thispage == self.page:
1311 if thispage == self.page:
1312 text = '%s' % (thispage,)
1312 text = '%s' % (thispage,)
1313 # Wrap in a SPAN tag if nolink_attr is set
1313 # Wrap in a SPAN tag if nolink_attr is set
1314 if self.curpage_attr:
1314 if self.curpage_attr:
1315 text = HTML.span(c=text, **self.curpage_attr)
1315 text = HTML.span(c=text, **self.curpage_attr)
1316 nav_items.append(text)
1316 nav_items.append(text)
1317 # Otherwise create just a link to that page
1317 # Otherwise create just a link to that page
1318 else:
1318 else:
1319 text = '%s' % (thispage,)
1319 text = '%s' % (thispage,)
1320 nav_items.append(self._pagerlink(thispage, text))
1320 nav_items.append(self._pagerlink(thispage, text))
1321
1321
1322 # Insert dots if there are pages between the displayed
1322 # Insert dots if there are pages between the displayed
1323 # page numbers and the end of the page range
1323 # page numbers and the end of the page range
1324 if self.last_page - rightmost_page > 1:
1324 if self.last_page - rightmost_page > 1:
1325 text = '..'
1325 text = '..'
1326 # Wrap in a SPAN tag if nolink_attr is set
1326 # Wrap in a SPAN tag if nolink_attr is set
1327 if self.dotdot_attr:
1327 if self.dotdot_attr:
1328 text = HTML.span(c=text, **self.dotdot_attr)
1328 text = HTML.span(c=text, **self.dotdot_attr)
1329 nav_items.append(text)
1329 nav_items.append(text)
1330
1330
1331 # Create a link to the very last page (unless we are on the last
1331 # Create a link to the very last page (unless we are on the last
1332 # page or there would be no need to insert '..' spacers)
1332 # page or there would be no need to insert '..' spacers)
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1335
1335
1336 ## prerender links
1336 ## prerender links
1337 #_page_link = url.current()
1337 #_page_link = url.current()
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1340 return self.separator.join(nav_items)
1340 return self.separator.join(nav_items)
1341
1341
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1343 show_if_single_page=False, separator=' ', onclick=None,
1343 show_if_single_page=False, separator=' ', onclick=None,
1344 symbol_first='<<', symbol_last='>>',
1344 symbol_first='<<', symbol_last='>>',
1345 symbol_previous='<', symbol_next='>',
1345 symbol_previous='<', symbol_next='>',
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1347 curpage_attr={'class': 'pager_curpage'},
1347 curpage_attr={'class': 'pager_curpage'},
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1349
1349
1350 self.curpage_attr = curpage_attr
1350 self.curpage_attr = curpage_attr
1351 self.separator = separator
1351 self.separator = separator
1352 self.pager_kwargs = kwargs
1352 self.pager_kwargs = kwargs
1353 self.page_param = page_param
1353 self.page_param = page_param
1354 self.partial_param = partial_param
1354 self.partial_param = partial_param
1355 self.onclick = onclick
1355 self.onclick = onclick
1356 self.link_attr = link_attr
1356 self.link_attr = link_attr
1357 self.dotdot_attr = dotdot_attr
1357 self.dotdot_attr = dotdot_attr
1358
1358
1359 # Don't show navigator if there is no more than one page
1359 # Don't show navigator if there is no more than one page
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1361 return ''
1361 return ''
1362
1362
1363 from string import Template
1363 from string import Template
1364 # Replace ~...~ in token format by range of pages
1364 # Replace ~...~ in token format by range of pages
1365 result = re.sub(r'~(\d+)~', self._range, format)
1365 result = re.sub(r'~(\d+)~', self._range, format)
1366
1366
1367 # Interpolate '%' variables
1367 # Interpolate '%' variables
1368 result = Template(result).safe_substitute({
1368 result = Template(result).safe_substitute({
1369 'first_page': self.first_page,
1369 'first_page': self.first_page,
1370 'last_page': self.last_page,
1370 'last_page': self.last_page,
1371 'page': self.page,
1371 'page': self.page,
1372 'page_count': self.page_count,
1372 'page_count': self.page_count,
1373 'items_per_page': self.items_per_page,
1373 'items_per_page': self.items_per_page,
1374 'first_item': self.first_item,
1374 'first_item': self.first_item,
1375 'last_item': self.last_item,
1375 'last_item': self.last_item,
1376 'item_count': self.item_count,
1376 'item_count': self.item_count,
1377 'link_first': self.page > self.first_page and \
1377 'link_first': self.page > self.first_page and \
1378 self._pagerlink(self.first_page, symbol_first) or '',
1378 self._pagerlink(self.first_page, symbol_first) or '',
1379 'link_last': self.page < self.last_page and \
1379 'link_last': self.page < self.last_page and \
1380 self._pagerlink(self.last_page, symbol_last) or '',
1380 self._pagerlink(self.last_page, symbol_last) or '',
1381 'link_previous': self.previous_page and \
1381 'link_previous': self.previous_page and \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1384 'link_next': self.next_page and \
1384 'link_next': self.next_page and \
1385 self._pagerlink(self.next_page, symbol_next) \
1385 self._pagerlink(self.next_page, symbol_next) \
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1387 })
1387 })
1388
1388
1389 return literal(result)
1389 return literal(result)
1390
1390
1391
1391
1392 #==============================================================================
1392 #==============================================================================
1393 # REPO PAGER, PAGER FOR REPOSITORY
1393 # REPO PAGER, PAGER FOR REPOSITORY
1394 #==============================================================================
1394 #==============================================================================
1395 class RepoPage(Page):
1395 class RepoPage(Page):
1396
1396
1397 def __init__(self, collection, page=1, items_per_page=20,
1397 def __init__(self, collection, page=1, items_per_page=20,
1398 item_count=None, url=None, **kwargs):
1398 item_count=None, url=None, **kwargs):
1399
1399
1400 """Create a "RepoPage" instance. special pager for paging
1400 """Create a "RepoPage" instance. special pager for paging
1401 repository
1401 repository
1402 """
1402 """
1403 self._url_generator = url
1403 self._url_generator = url
1404
1404
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1406 self.kwargs = kwargs
1406 self.kwargs = kwargs
1407
1407
1408 # Save a reference to the collection
1408 # Save a reference to the collection
1409 self.original_collection = collection
1409 self.original_collection = collection
1410
1410
1411 self.collection = collection
1411 self.collection = collection
1412
1412
1413 # The self.page is the number of the current page.
1413 # The self.page is the number of the current page.
1414 # The first page has the number 1!
1414 # The first page has the number 1!
1415 try:
1415 try:
1416 self.page = int(page) # make it int() if we get it as a string
1416 self.page = int(page) # make it int() if we get it as a string
1417 except (ValueError, TypeError):
1417 except (ValueError, TypeError):
1418 self.page = 1
1418 self.page = 1
1419
1419
1420 self.items_per_page = items_per_page
1420 self.items_per_page = items_per_page
1421
1421
1422 # Unless the user tells us how many items the collections has
1422 # Unless the user tells us how many items the collections has
1423 # we calculate that ourselves.
1423 # we calculate that ourselves.
1424 if item_count is not None:
1424 if item_count is not None:
1425 self.item_count = item_count
1425 self.item_count = item_count
1426 else:
1426 else:
1427 self.item_count = len(self.collection)
1427 self.item_count = len(self.collection)
1428
1428
1429 # Compute the number of the first and last available page
1429 # Compute the number of the first and last available page
1430 if self.item_count > 0:
1430 if self.item_count > 0:
1431 self.first_page = 1
1431 self.first_page = 1
1432 self.page_count = int(math.ceil(float(self.item_count) /
1432 self.page_count = int(math.ceil(float(self.item_count) /
1433 self.items_per_page))
1433 self.items_per_page))
1434 self.last_page = self.first_page + self.page_count - 1
1434 self.last_page = self.first_page + self.page_count - 1
1435
1435
1436 # Make sure that the requested page number is the range of
1436 # Make sure that the requested page number is the range of
1437 # valid pages
1437 # valid pages
1438 if self.page > self.last_page:
1438 if self.page > self.last_page:
1439 self.page = self.last_page
1439 self.page = self.last_page
1440 elif self.page < self.first_page:
1440 elif self.page < self.first_page:
1441 self.page = self.first_page
1441 self.page = self.first_page
1442
1442
1443 # Note: the number of items on this page can be less than
1443 # Note: the number of items on this page can be less than
1444 # items_per_page if the last page is not full
1444 # items_per_page if the last page is not full
1445 self.first_item = max(0, (self.item_count) - (self.page *
1445 self.first_item = max(0, (self.item_count) - (self.page *
1446 items_per_page))
1446 items_per_page))
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1448 (self.page - 1))
1448 (self.page - 1))
1449
1449
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1451
1451
1452 # Links to previous and next page
1452 # Links to previous and next page
1453 if self.page > self.first_page:
1453 if self.page > self.first_page:
1454 self.previous_page = self.page - 1
1454 self.previous_page = self.page - 1
1455 else:
1455 else:
1456 self.previous_page = None
1456 self.previous_page = None
1457
1457
1458 if self.page < self.last_page:
1458 if self.page < self.last_page:
1459 self.next_page = self.page + 1
1459 self.next_page = self.page + 1
1460 else:
1460 else:
1461 self.next_page = None
1461 self.next_page = None
1462
1462
1463 # No items available
1463 # No items available
1464 else:
1464 else:
1465 self.first_page = None
1465 self.first_page = None
1466 self.page_count = 0
1466 self.page_count = 0
1467 self.last_page = None
1467 self.last_page = None
1468 self.first_item = None
1468 self.first_item = None
1469 self.last_item = None
1469 self.last_item = None
1470 self.previous_page = None
1470 self.previous_page = None
1471 self.next_page = None
1471 self.next_page = None
1472 self.items = []
1472 self.items = []
1473
1473
1474 # This is a subclass of the 'list' type. Initialise the list now.
1474 # This is a subclass of the 'list' type. Initialise the list now.
1475 list.__init__(self, reversed(self.items))
1475 list.__init__(self, reversed(self.items))
1476
1476
1477
1477
1478 def breadcrumb_repo_link(repo):
1478 def breadcrumb_repo_link(repo):
1479 """
1479 """
1480 Makes a breadcrumbs path link to repo
1480 Makes a breadcrumbs path link to repo
1481
1481
1482 ex::
1482 ex::
1483 group >> subgroup >> repo
1483 group >> subgroup >> repo
1484
1484
1485 :param repo: a Repository instance
1485 :param repo: a Repository instance
1486 """
1486 """
1487
1487
1488 path = [
1488 path = [
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1490 for group in repo.groups_with_parents
1490 for group in repo.groups_with_parents
1491 ] + [
1491 ] + [
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1493 ]
1493 ]
1494
1494
1495 return literal(' &raquo; '.join(path))
1495 return literal(' &raquo; '.join(path))
1496
1496
1497
1497
1498 def format_byte_size_binary(file_size):
1498 def format_byte_size_binary(file_size):
1499 """
1499 """
1500 Formats file/folder sizes to standard.
1500 Formats file/folder sizes to standard.
1501 """
1501 """
1502 if file_size is None:
1502 if file_size is None:
1503 file_size = 0
1503 file_size = 0
1504
1504
1505 formatted_size = format_byte_size(file_size, binary=True)
1505 formatted_size = format_byte_size(file_size, binary=True)
1506 return formatted_size
1506 return formatted_size
1507
1507
1508
1508
1509 def urlify_text(text_, safe=True):
1509 def urlify_text(text_, safe=True):
1510 """
1510 """
1511 Extrac urls from text and make html links out of them
1511 Extrac urls from text and make html links out of them
1512
1512
1513 :param text_:
1513 :param text_:
1514 """
1514 """
1515
1515
1516 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1516 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1517 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1517 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1518
1518
1519 def url_func(match_obj):
1519 def url_func(match_obj):
1520 url_full = match_obj.groups()[0]
1520 url_full = match_obj.groups()[0]
1521 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1521 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1522 _newtext = url_pat.sub(url_func, text_)
1522 _newtext = url_pat.sub(url_func, text_)
1523 if safe:
1523 if safe:
1524 return literal(_newtext)
1524 return literal(_newtext)
1525 return _newtext
1525 return _newtext
1526
1526
1527
1527
1528 def urlify_commits(text_, repository):
1528 def urlify_commits(text_, repository):
1529 """
1529 """
1530 Extract commit ids from text and make link from them
1530 Extract commit ids from text and make link from them
1531
1531
1532 :param text_:
1532 :param text_:
1533 :param repository: repo name to build the URL with
1533 :param repository: repo name to build the URL with
1534 """
1534 """
1535
1535
1536 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1536 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1537
1537
1538 def url_func(match_obj):
1538 def url_func(match_obj):
1539 commit_id = match_obj.groups()[1]
1539 commit_id = match_obj.groups()[1]
1540 pref = match_obj.groups()[0]
1540 pref = match_obj.groups()[0]
1541 suf = match_obj.groups()[2]
1541 suf = match_obj.groups()[2]
1542
1542
1543 tmpl = (
1543 tmpl = (
1544 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1544 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1545 '%(commit_id)s</a>%(suf)s'
1545 '%(commit_id)s</a>%(suf)s'
1546 )
1546 )
1547 return tmpl % {
1547 return tmpl % {
1548 'pref': pref,
1548 'pref': pref,
1549 'cls': 'revision-link',
1549 'cls': 'revision-link',
1550 'url': route_url('repo_commit', repo_name=repository,
1550 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1551 commit_id=commit_id),
1552 'commit_id': commit_id,
1551 'commit_id': commit_id,
1553 'suf': suf
1552 'suf': suf
1554 }
1553 }
1555
1554
1556 newtext = URL_PAT.sub(url_func, text_)
1555 newtext = URL_PAT.sub(url_func, text_)
1557
1556
1558 return newtext
1557 return newtext
1559
1558
1560
1559
1561 def _process_url_func(match_obj, repo_name, uid, entry,
1560 def _process_url_func(match_obj, repo_name, uid, entry,
1562 return_raw_data=False, link_format='html'):
1561 return_raw_data=False, link_format='html'):
1563 pref = ''
1562 pref = ''
1564 if match_obj.group().startswith(' '):
1563 if match_obj.group().startswith(' '):
1565 pref = ' '
1564 pref = ' '
1566
1565
1567 issue_id = ''.join(match_obj.groups())
1566 issue_id = ''.join(match_obj.groups())
1568
1567
1569 if link_format == 'html':
1568 if link_format == 'html':
1570 tmpl = (
1569 tmpl = (
1571 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1570 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1572 '%(issue-prefix)s%(id-repr)s'
1571 '%(issue-prefix)s%(id-repr)s'
1573 '</a>')
1572 '</a>')
1574 elif link_format == 'rst':
1573 elif link_format == 'rst':
1575 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1574 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1576 elif link_format == 'markdown':
1575 elif link_format == 'markdown':
1577 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1576 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1578 else:
1577 else:
1579 raise ValueError('Bad link_format:{}'.format(link_format))
1578 raise ValueError('Bad link_format:{}'.format(link_format))
1580
1579
1581 (repo_name_cleaned,
1580 (repo_name_cleaned,
1582 parent_group_name) = RepoGroupModel().\
1581 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1583 _get_group_name_and_parent(repo_name)
1584
1582
1585 # variables replacement
1583 # variables replacement
1586 named_vars = {
1584 named_vars = {
1587 'id': issue_id,
1585 'id': issue_id,
1588 'repo': repo_name,
1586 'repo': repo_name,
1589 'repo_name': repo_name_cleaned,
1587 'repo_name': repo_name_cleaned,
1590 'group_name': parent_group_name
1588 'group_name': parent_group_name
1591 }
1589 }
1592 # named regex variables
1590 # named regex variables
1593 named_vars.update(match_obj.groupdict())
1591 named_vars.update(match_obj.groupdict())
1594 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1592 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1595
1593
1594 def quote_cleaner(input_str):
1595 """Remove quotes as it's HTML"""
1596 return input_str.replace('"', '')
1597
1596 data = {
1598 data = {
1597 'pref': pref,
1599 'pref': pref,
1598 'cls': 'issue-tracker-link',
1600 'cls': quote_cleaner('issue-tracker-link'),
1599 'url': _url,
1601 'url': quote_cleaner(_url),
1600 'id-repr': issue_id,
1602 'id-repr': issue_id,
1601 'issue-prefix': entry['pref'],
1603 'issue-prefix': entry['pref'],
1602 'serv': entry['url'],
1604 'serv': entry['url'],
1603 }
1605 }
1604 if return_raw_data:
1606 if return_raw_data:
1605 return {
1607 return {
1606 'id': issue_id,
1608 'id': issue_id,
1607 'url': _url
1609 'url': _url
1608 }
1610 }
1609 return tmpl % data
1611 return tmpl % data
1610
1612
1611
1613
1612 def get_active_pattern_entries(repo_name):
1614 def get_active_pattern_entries(repo_name):
1613 repo = None
1615 repo = None
1614 if repo_name:
1616 if repo_name:
1615 # Retrieving repo_name to avoid invalid repo_name to explode on
1617 # Retrieving repo_name to avoid invalid repo_name to explode on
1616 # IssueTrackerSettingsModel but still passing invalid name further down
1618 # IssueTrackerSettingsModel but still passing invalid name further down
1617 repo = Repository.get_by_repo_name(repo_name, cache=True)
1619 repo = Repository.get_by_repo_name(repo_name, cache=True)
1618
1620
1619 settings_model = IssueTrackerSettingsModel(repo=repo)
1621 settings_model = IssueTrackerSettingsModel(repo=repo)
1620 active_entries = settings_model.get_settings(cache=True)
1622 active_entries = settings_model.get_settings(cache=True)
1621 return active_entries
1623 return active_entries
1622
1624
1623
1625
1624 def process_patterns(text_string, repo_name, link_format='html',
1626 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1625 active_entries=None):
1626
1627
1627 allowed_formats = ['html', 'rst', 'markdown']
1628 allowed_formats = ['html', 'rst', 'markdown']
1628 if link_format not in allowed_formats:
1629 if link_format not in allowed_formats:
1629 raise ValueError('Link format can be only one of:{} got {}'.format(
1630 raise ValueError('Link format can be only one of:{} got {}'.format(
1630 allowed_formats, link_format))
1631 allowed_formats, link_format))
1631
1632
1632 active_entries = active_entries or get_active_pattern_entries(repo_name)
1633 active_entries = active_entries or get_active_pattern_entries(repo_name)
1633 issues_data = []
1634 issues_data = []
1634 newtext = text_string
1635 newtext = text_string
1635
1636
1636 for uid, entry in active_entries.items():
1637 for uid, entry in active_entries.items():
1637 log.debug('found issue tracker entry with uid %s', uid)
1638 log.debug('found issue tracker entry with uid %s', uid)
1638
1639
1639 if not (entry['pat'] and entry['url']):
1640 if not (entry['pat'] and entry['url']):
1640 log.debug('skipping due to missing data')
1641 log.debug('skipping due to missing data')
1641 continue
1642 continue
1642
1643
1643 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1644 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1644 uid, entry['pat'], entry['url'], entry['pref'])
1645 uid, entry['pat'], entry['url'], entry['pref'])
1645
1646
1646 try:
1647 try:
1647 pattern = re.compile(r'%s' % entry['pat'])
1648 pattern = re.compile(r'%s' % entry['pat'])
1648 except re.error:
1649 except re.error:
1649 log.exception(
1650 log.exception(
1650 'issue tracker pattern: `%s` failed to compile',
1651 'issue tracker pattern: `%s` failed to compile',
1651 entry['pat'])
1652 entry['pat'])
1652 continue
1653 continue
1653
1654
1654 data_func = partial(
1655 data_func = partial(
1655 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1656 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1656 return_raw_data=True)
1657 return_raw_data=True)
1657
1658
1658 for match_obj in pattern.finditer(text_string):
1659 for match_obj in pattern.finditer(text_string):
1659 issues_data.append(data_func(match_obj))
1660 issues_data.append(data_func(match_obj))
1660
1661
1661 url_func = partial(
1662 url_func = partial(
1662 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1663 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1663 link_format=link_format)
1664 link_format=link_format)
1664
1665
1665 newtext = pattern.sub(url_func, newtext)
1666 newtext = pattern.sub(url_func, newtext)
1666 log.debug('processed prefix:uid `%s`', uid)
1667 log.debug('processed prefix:uid `%s`', uid)
1667
1668
1668 return newtext, issues_data
1669 return newtext, issues_data
1669
1670
1670
1671
1671 def urlify_commit_message(commit_text, repository=None,
1672 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1672 active_pattern_entries=None):
1673 """
1673 """
1674 Parses given text message and makes proper links.
1674 Parses given text message and makes proper links.
1675 issues are linked to given issue-server, and rest is a commit link
1675 issues are linked to given issue-server, and rest is a commit link
1676
1676
1677 :param commit_text:
1677 :param commit_text:
1678 :param repository:
1678 :param repository:
1679 """
1679 """
1680 def escaper(string):
1680 def escaper(string):
1681 return string.replace('<', '&lt;').replace('>', '&gt;')
1681 return string.replace('<', '&lt;').replace('>', '&gt;')
1682
1682
1683 newtext = escaper(commit_text)
1683 newtext = escaper(commit_text)
1684
1684
1685 # extract http/https links and make them real urls
1685 # extract http/https links and make them real urls
1686 newtext = urlify_text(newtext, safe=False)
1686 newtext = urlify_text(newtext, safe=False)
1687
1687
1688 # urlify commits - extract commit ids and make link out of them, if we have
1688 # urlify commits - extract commit ids and make link out of them, if we have
1689 # the scope of repository present.
1689 # the scope of repository present.
1690 if repository:
1690 if repository:
1691 newtext = urlify_commits(newtext, repository)
1691 newtext = urlify_commits(newtext, repository)
1692
1692
1693 # process issue tracker patterns
1693 # process issue tracker patterns
1694 newtext, issues = process_patterns(newtext, repository or '',
1694 newtext, issues = process_patterns(newtext, repository or '',
1695 active_entries=active_pattern_entries)
1695 active_entries=active_pattern_entries)
1696
1696
1697 return literal(newtext)
1697 return literal(newtext)
1698
1698
1699
1699
1700 def render_binary(repo_name, file_obj):
1700 def render_binary(repo_name, file_obj):
1701 """
1701 """
1702 Choose how to render a binary file
1702 Choose how to render a binary file
1703 """
1703 """
1704
1704
1705 filename = file_obj.name
1705 filename = file_obj.name
1706
1706
1707 # images
1707 # images
1708 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1708 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1709 if fnmatch.fnmatch(filename, pat=ext):
1709 if fnmatch.fnmatch(filename, pat=ext):
1710 alt = escape(filename)
1710 alt = escape(filename)
1711 src = route_path(
1711 src = route_path(
1712 'repo_file_raw', repo_name=repo_name,
1712 'repo_file_raw', repo_name=repo_name,
1713 commit_id=file_obj.commit.raw_id,
1713 commit_id=file_obj.commit.raw_id,
1714 f_path=file_obj.path)
1714 f_path=file_obj.path)
1715 return literal(
1715 return literal(
1716 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1716 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1717
1717
1718
1718
1719 def renderer_from_filename(filename, exclude=None):
1719 def renderer_from_filename(filename, exclude=None):
1720 """
1720 """
1721 choose a renderer based on filename, this works only for text based files
1721 choose a renderer based on filename, this works only for text based files
1722 """
1722 """
1723
1723
1724 # ipython
1724 # ipython
1725 for ext in ['*.ipynb']:
1725 for ext in ['*.ipynb']:
1726 if fnmatch.fnmatch(filename, pat=ext):
1726 if fnmatch.fnmatch(filename, pat=ext):
1727 return 'jupyter'
1727 return 'jupyter'
1728
1728
1729 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1729 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1730 if is_markup:
1730 if is_markup:
1731 return is_markup
1731 return is_markup
1732 return None
1732 return None
1733
1733
1734
1734
1735 def render(source, renderer='rst', mentions=False, relative_urls=None,
1735 def render(source, renderer='rst', mentions=False, relative_urls=None,
1736 repo_name=None):
1736 repo_name=None):
1737
1737
1738 def maybe_convert_relative_links(html_source):
1738 def maybe_convert_relative_links(html_source):
1739 if relative_urls:
1739 if relative_urls:
1740 return relative_links(html_source, relative_urls)
1740 return relative_links(html_source, relative_urls)
1741 return html_source
1741 return html_source
1742
1742
1743 if renderer == 'plain':
1743 if renderer == 'plain':
1744 return literal(
1744 return literal(
1745 MarkupRenderer.plain(source, leading_newline=False))
1745 MarkupRenderer.plain(source, leading_newline=False))
1746
1746
1747 elif renderer == 'rst':
1747 elif renderer == 'rst':
1748 if repo_name:
1748 if repo_name:
1749 # process patterns on comments if we pass in repo name
1749 # process patterns on comments if we pass in repo name
1750 source, issues = process_patterns(
1750 source, issues = process_patterns(
1751 source, repo_name, link_format='rst')
1751 source, repo_name, link_format='rst')
1752
1752
1753 return literal(
1753 return literal(
1754 '<div class="rst-block">%s</div>' %
1754 '<div class="rst-block">%s</div>' %
1755 maybe_convert_relative_links(
1755 maybe_convert_relative_links(
1756 MarkupRenderer.rst(source, mentions=mentions)))
1756 MarkupRenderer.rst(source, mentions=mentions)))
1757
1757
1758 elif renderer == 'markdown':
1758 elif renderer == 'markdown':
1759 if repo_name:
1759 if repo_name:
1760 # process patterns on comments if we pass in repo name
1760 # process patterns on comments if we pass in repo name
1761 source, issues = process_patterns(
1761 source, issues = process_patterns(
1762 source, repo_name, link_format='markdown')
1762 source, repo_name, link_format='markdown')
1763
1763
1764 return literal(
1764 return literal(
1765 '<div class="markdown-block">%s</div>' %
1765 '<div class="markdown-block">%s</div>' %
1766 maybe_convert_relative_links(
1766 maybe_convert_relative_links(
1767 MarkupRenderer.markdown(source, flavored=True,
1767 MarkupRenderer.markdown(source, flavored=True,
1768 mentions=mentions)))
1768 mentions=mentions)))
1769
1769
1770 elif renderer == 'jupyter':
1770 elif renderer == 'jupyter':
1771 return literal(
1771 return literal(
1772 '<div class="ipynb">%s</div>' %
1772 '<div class="ipynb">%s</div>' %
1773 maybe_convert_relative_links(
1773 maybe_convert_relative_links(
1774 MarkupRenderer.jupyter(source)))
1774 MarkupRenderer.jupyter(source)))
1775
1775
1776 # None means just show the file-source
1776 # None means just show the file-source
1777 return None
1777 return None
1778
1778
1779
1779
1780 def commit_status(repo, commit_id):
1780 def commit_status(repo, commit_id):
1781 return ChangesetStatusModel().get_status(repo, commit_id)
1781 return ChangesetStatusModel().get_status(repo, commit_id)
1782
1782
1783
1783
1784 def commit_status_lbl(commit_status):
1784 def commit_status_lbl(commit_status):
1785 return dict(ChangesetStatus.STATUSES).get(commit_status)
1785 return dict(ChangesetStatus.STATUSES).get(commit_status)
1786
1786
1787
1787
1788 def commit_time(repo_name, commit_id):
1788 def commit_time(repo_name, commit_id):
1789 repo = Repository.get_by_repo_name(repo_name)
1789 repo = Repository.get_by_repo_name(repo_name)
1790 commit = repo.get_commit(commit_id=commit_id)
1790 commit = repo.get_commit(commit_id=commit_id)
1791 return commit.date
1791 return commit.date
1792
1792
1793
1793
1794 def get_permission_name(key):
1794 def get_permission_name(key):
1795 return dict(Permission.PERMS).get(key)
1795 return dict(Permission.PERMS).get(key)
1796
1796
1797
1797
1798 def journal_filter_help(request):
1798 def journal_filter_help(request):
1799 _ = request.translate
1799 _ = request.translate
1800 from rhodecode.lib.audit_logger import ACTIONS
1800 from rhodecode.lib.audit_logger import ACTIONS
1801 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1801 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1802
1802
1803 return _(
1803 return _(
1804 'Example filter terms:\n' +
1804 'Example filter terms:\n' +
1805 ' repository:vcs\n' +
1805 ' repository:vcs\n' +
1806 ' username:marcin\n' +
1806 ' username:marcin\n' +
1807 ' username:(NOT marcin)\n' +
1807 ' username:(NOT marcin)\n' +
1808 ' action:*push*\n' +
1808 ' action:*push*\n' +
1809 ' ip:127.0.0.1\n' +
1809 ' ip:127.0.0.1\n' +
1810 ' date:20120101\n' +
1810 ' date:20120101\n' +
1811 ' date:[20120101100000 TO 20120102]\n' +
1811 ' date:[20120101100000 TO 20120102]\n' +
1812 '\n' +
1812 '\n' +
1813 'Actions: {actions}\n' +
1813 'Actions: {actions}\n' +
1814 '\n' +
1814 '\n' +
1815 'Generate wildcards using \'*\' character:\n' +
1815 'Generate wildcards using \'*\' character:\n' +
1816 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1816 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1817 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1817 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1818 '\n' +
1818 '\n' +
1819 'Optional AND / OR operators in queries\n' +
1819 'Optional AND / OR operators in queries\n' +
1820 ' "repository:vcs OR repository:test"\n' +
1820 ' "repository:vcs OR repository:test"\n' +
1821 ' "username:test AND repository:test*"\n'
1821 ' "username:test AND repository:test*"\n'
1822 ).format(actions=actions)
1822 ).format(actions=actions)
1823
1823
1824
1824
1825 def not_mapped_error(repo_name):
1825 def not_mapped_error(repo_name):
1826 from rhodecode.translation import _
1826 from rhodecode.translation import _
1827 flash(_('%s repository is not mapped to db perhaps'
1827 flash(_('%s repository is not mapped to db perhaps'
1828 ' it was created or renamed from the filesystem'
1828 ' it was created or renamed from the filesystem'
1829 ' please run the application again'
1829 ' please run the application again'
1830 ' in order to rescan repositories') % repo_name, category='error')
1830 ' in order to rescan repositories') % repo_name, category='error')
1831
1831
1832
1832
1833 def ip_range(ip_addr):
1833 def ip_range(ip_addr):
1834 from rhodecode.model.db import UserIpMap
1834 from rhodecode.model.db import UserIpMap
1835 s, e = UserIpMap._get_ip_range(ip_addr)
1835 s, e = UserIpMap._get_ip_range(ip_addr)
1836 return '%s - %s' % (s, e)
1836 return '%s - %s' % (s, e)
1837
1837
1838
1838
1839 def form(url, method='post', needs_csrf_token=True, **attrs):
1839 def form(url, method='post', needs_csrf_token=True, **attrs):
1840 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1840 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1841 if method.lower() != 'get' and needs_csrf_token:
1841 if method.lower() != 'get' and needs_csrf_token:
1842 raise Exception(
1842 raise Exception(
1843 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1843 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1844 'CSRF token. If the endpoint does not require such token you can ' +
1844 'CSRF token. If the endpoint does not require such token you can ' +
1845 'explicitly set the parameter needs_csrf_token to false.')
1845 'explicitly set the parameter needs_csrf_token to false.')
1846
1846
1847 return wh_form(url, method=method, **attrs)
1847 return wh_form(url, method=method, **attrs)
1848
1848
1849
1849
1850 def secure_form(form_url, method="POST", multipart=False, **attrs):
1850 def secure_form(form_url, method="POST", multipart=False, **attrs):
1851 """Start a form tag that points the action to an url. This
1851 """Start a form tag that points the action to an url. This
1852 form tag will also include the hidden field containing
1852 form tag will also include the hidden field containing
1853 the auth token.
1853 the auth token.
1854
1854
1855 The url options should be given either as a string, or as a
1855 The url options should be given either as a string, or as a
1856 ``url()`` function. The method for the form defaults to POST.
1856 ``url()`` function. The method for the form defaults to POST.
1857
1857
1858 Options:
1858 Options:
1859
1859
1860 ``multipart``
1860 ``multipart``
1861 If set to True, the enctype is set to "multipart/form-data".
1861 If set to True, the enctype is set to "multipart/form-data".
1862 ``method``
1862 ``method``
1863 The method to use when submitting the form, usually either
1863 The method to use when submitting the form, usually either
1864 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1864 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1865 hidden input with name _method is added to simulate the verb
1865 hidden input with name _method is added to simulate the verb
1866 over POST.
1866 over POST.
1867
1867
1868 """
1868 """
1869 from webhelpers.pylonslib.secure_form import insecure_form
1869 from webhelpers.pylonslib.secure_form import insecure_form
1870
1870
1871 if 'request' in attrs:
1871 if 'request' in attrs:
1872 session = attrs['request'].session
1872 session = attrs['request'].session
1873 del attrs['request']
1873 del attrs['request']
1874 else:
1874 else:
1875 raise ValueError(
1875 raise ValueError(
1876 'Calling this form requires request= to be passed as argument')
1876 'Calling this form requires request= to be passed as argument')
1877
1877
1878 form = insecure_form(form_url, method, multipart, **attrs)
1878 form = insecure_form(form_url, method, multipart, **attrs)
1879 token = literal(
1879 token = literal(
1880 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1880 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1881 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1881 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1882
1882
1883 return literal("%s\n%s" % (form, token))
1883 return literal("%s\n%s" % (form, token))
1884
1884
1885
1885
1886 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1886 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1887 select_html = select(name, selected, options, **attrs)
1887 select_html = select(name, selected, options, **attrs)
1888 select2 = """
1888 select2 = """
1889 <script>
1889 <script>
1890 $(document).ready(function() {
1890 $(document).ready(function() {
1891 $('#%s').select2({
1891 $('#%s').select2({
1892 containerCssClass: 'drop-menu',
1892 containerCssClass: 'drop-menu',
1893 dropdownCssClass: 'drop-menu-dropdown',
1893 dropdownCssClass: 'drop-menu-dropdown',
1894 dropdownAutoWidth: true%s
1894 dropdownAutoWidth: true%s
1895 });
1895 });
1896 });
1896 });
1897 </script>
1897 </script>
1898 """
1898 """
1899 filter_option = """,
1899 filter_option = """,
1900 minimumResultsForSearch: -1
1900 minimumResultsForSearch: -1
1901 """
1901 """
1902 input_id = attrs.get('id') or name
1902 input_id = attrs.get('id') or name
1903 filter_enabled = "" if enable_filter else filter_option
1903 filter_enabled = "" if enable_filter else filter_option
1904 select_script = literal(select2 % (input_id, filter_enabled))
1904 select_script = literal(select2 % (input_id, filter_enabled))
1905
1905
1906 return literal(select_html+select_script)
1906 return literal(select_html+select_script)
1907
1907
1908
1908
1909 def get_visual_attr(tmpl_context_var, attr_name):
1909 def get_visual_attr(tmpl_context_var, attr_name):
1910 """
1910 """
1911 A safe way to get a variable from visual variable of template context
1911 A safe way to get a variable from visual variable of template context
1912
1912
1913 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1913 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1914 :param attr_name: name of the attribute we fetch from the c.visual
1914 :param attr_name: name of the attribute we fetch from the c.visual
1915 """
1915 """
1916 visual = getattr(tmpl_context_var, 'visual', None)
1916 visual = getattr(tmpl_context_var, 'visual', None)
1917 if not visual:
1917 if not visual:
1918 return
1918 return
1919 else:
1919 else:
1920 return getattr(visual, attr_name, None)
1920 return getattr(visual, attr_name, None)
1921
1921
1922
1922
1923 def get_last_path_part(file_node):
1923 def get_last_path_part(file_node):
1924 if not file_node.path:
1924 if not file_node.path:
1925 return u''
1925 return u''
1926
1926
1927 path = safe_unicode(file_node.path.split('/')[-1])
1927 path = safe_unicode(file_node.path.split('/')[-1])
1928 return u'../' + path
1928 return u'../' + path
1929
1929
1930
1930
1931 def route_url(*args, **kwargs):
1931 def route_url(*args, **kwargs):
1932 """
1932 """
1933 Wrapper around pyramids `route_url` (fully qualified url) function.
1933 Wrapper around pyramids `route_url` (fully qualified url) function.
1934 """
1934 """
1935 req = get_current_request()
1935 req = get_current_request()
1936 return req.route_url(*args, **kwargs)
1936 return req.route_url(*args, **kwargs)
1937
1937
1938
1938
1939 def route_path(*args, **kwargs):
1939 def route_path(*args, **kwargs):
1940 """
1940 """
1941 Wrapper around pyramids `route_path` function.
1941 Wrapper around pyramids `route_path` function.
1942 """
1942 """
1943 req = get_current_request()
1943 req = get_current_request()
1944 return req.route_path(*args, **kwargs)
1944 return req.route_path(*args, **kwargs)
1945
1945
1946
1946
1947 def route_path_or_none(*args, **kwargs):
1947 def route_path_or_none(*args, **kwargs):
1948 try:
1948 try:
1949 return route_path(*args, **kwargs)
1949 return route_path(*args, **kwargs)
1950 except KeyError:
1950 except KeyError:
1951 return None
1951 return None
1952
1952
1953
1953
1954 def current_route_path(request, **kw):
1954 def current_route_path(request, **kw):
1955 new_args = request.GET.mixed()
1955 new_args = request.GET.mixed()
1956 new_args.update(kw)
1956 new_args.update(kw)
1957 return request.current_route_path(_query=new_args)
1957 return request.current_route_path(_query=new_args)
1958
1958
1959
1959
1960 def api_call_example(method, args):
1960 def api_call_example(method, args):
1961 """
1961 """
1962 Generates an API call example via CURL
1962 Generates an API call example via CURL
1963 """
1963 """
1964 args_json = json.dumps(OrderedDict([
1964 args_json = json.dumps(OrderedDict([
1965 ('id', 1),
1965 ('id', 1),
1966 ('auth_token', 'SECRET'),
1966 ('auth_token', 'SECRET'),
1967 ('method', method),
1967 ('method', method),
1968 ('args', args)
1968 ('args', args)
1969 ]))
1969 ]))
1970 return literal(
1970 return literal(
1971 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1971 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1972 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1972 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1973 "and needs to be of `api calls` role."
1973 "and needs to be of `api calls` role."
1974 .format(
1974 .format(
1975 api_url=route_url('apiv2'),
1975 api_url=route_url('apiv2'),
1976 token_url=route_url('my_account_auth_tokens'),
1976 token_url=route_url('my_account_auth_tokens'),
1977 data=args_json))
1977 data=args_json))
1978
1978
1979
1979
1980 def notification_description(notification, request):
1980 def notification_description(notification, request):
1981 """
1981 """
1982 Generate notification human readable description based on notification type
1982 Generate notification human readable description based on notification type
1983 """
1983 """
1984 from rhodecode.model.notification import NotificationModel
1984 from rhodecode.model.notification import NotificationModel
1985 return NotificationModel().make_description(
1985 return NotificationModel().make_description(
1986 notification, translate=request.translate)
1986 notification, translate=request.translate)
1987
1987
1988
1988
1989 def go_import_header(request, db_repo=None):
1989 def go_import_header(request, db_repo=None):
1990 """
1990 """
1991 Creates a header for go-import functionality in Go Lang
1991 Creates a header for go-import functionality in Go Lang
1992 """
1992 """
1993
1993
1994 if not db_repo:
1994 if not db_repo:
1995 return
1995 return
1996 if 'go-get' not in request.GET:
1996 if 'go-get' not in request.GET:
1997 return
1997 return
1998
1998
1999 clone_url = db_repo.clone_url()
1999 clone_url = db_repo.clone_url()
2000 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2000 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2001 # we have a repo and go-get flag,
2001 # we have a repo and go-get flag,
2002 return literal('<meta name="go-import" content="{} {} {}">'.format(
2002 return literal('<meta name="go-import" content="{} {} {}">'.format(
2003 prefix, db_repo.repo_type, clone_url))
2003 prefix, db_repo.repo_type, clone_url))
2004
2004
2005
2005
2006 def reviewer_as_json(*args, **kwargs):
2006 def reviewer_as_json(*args, **kwargs):
2007 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2007 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2008 return _reviewer_as_json(*args, **kwargs)
2008 return _reviewer_as_json(*args, **kwargs)
2009
2009
2010
2010
2011 def get_repo_view_type(request):
2011 def get_repo_view_type(request):
2012 route_name = request.matched_route.name
2012 route_name = request.matched_route.name
2013 route_to_view_type = {
2013 route_to_view_type = {
2014 'repo_changelog': 'changelog',
2014 'repo_changelog': 'changelog',
2015 'repo_files': 'files',
2015 'repo_files': 'files',
2016 'repo_summary': 'summary',
2016 'repo_summary': 'summary',
2017 'repo_commit': 'commit'
2017 'repo_commit': 'commit'
2018
2018
2019 }
2019 }
2020 return route_to_view_type.get(route_name)
2020 return route_to_view_type.get(route_name)
@@ -1,837 +1,845 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import hashlib
22 import hashlib
23 import logging
23 import logging
24 from collections import namedtuple
24 from collections import namedtuple
25 from functools import wraps
25 from functools import wraps
26 import bleach
26 import bleach
27
27
28 from rhodecode.lib import rc_cache
28 from rhodecode.lib import rc_cache
29 from rhodecode.lib.utils2 import (
29 from rhodecode.lib.utils2 import (
30 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
30 Optional, AttributeDict, safe_str, remove_prefix, str2bool)
31 from rhodecode.lib.vcs.backends import base
31 from rhodecode.lib.vcs.backends import base
32 from rhodecode.model import BaseModel
32 from rhodecode.model import BaseModel
33 from rhodecode.model.db import (
33 from rhodecode.model.db import (
34 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting, CacheKey)
34 RepoRhodeCodeUi, RepoRhodeCodeSetting, RhodeCodeUi, RhodeCodeSetting, CacheKey)
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36
36
37
37
38 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
39
39
40
40
41 UiSetting = namedtuple(
41 UiSetting = namedtuple(
42 'UiSetting', ['section', 'key', 'value', 'active'])
42 'UiSetting', ['section', 'key', 'value', 'active'])
43
43
44 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
44 SOCIAL_PLUGINS_LIST = ['github', 'bitbucket', 'twitter', 'google']
45
45
46
46
47 class SettingNotFound(Exception):
47 class SettingNotFound(Exception):
48 def __init__(self, setting_id):
48 def __init__(self, setting_id):
49 msg = 'Setting `{}` is not found'.format(setting_id)
49 msg = 'Setting `{}` is not found'.format(setting_id)
50 super(SettingNotFound, self).__init__(msg)
50 super(SettingNotFound, self).__init__(msg)
51
51
52
52
53 class SettingsModel(BaseModel):
53 class SettingsModel(BaseModel):
54 BUILTIN_HOOKS = (
54 BUILTIN_HOOKS = (
55 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
55 RhodeCodeUi.HOOK_REPO_SIZE, RhodeCodeUi.HOOK_PUSH,
56 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
56 RhodeCodeUi.HOOK_PRE_PUSH, RhodeCodeUi.HOOK_PRETX_PUSH,
57 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
57 RhodeCodeUi.HOOK_PULL, RhodeCodeUi.HOOK_PRE_PULL,
58 RhodeCodeUi.HOOK_PUSH_KEY,)
58 RhodeCodeUi.HOOK_PUSH_KEY,)
59 HOOKS_SECTION = 'hooks'
59 HOOKS_SECTION = 'hooks'
60
60
61 def __init__(self, sa=None, repo=None):
61 def __init__(self, sa=None, repo=None):
62 self.repo = repo
62 self.repo = repo
63 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
63 self.UiDbModel = RepoRhodeCodeUi if repo else RhodeCodeUi
64 self.SettingsDbModel = (
64 self.SettingsDbModel = (
65 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
65 RepoRhodeCodeSetting if repo else RhodeCodeSetting)
66 super(SettingsModel, self).__init__(sa)
66 super(SettingsModel, self).__init__(sa)
67
67
68 def get_ui_by_key(self, key):
68 def get_ui_by_key(self, key):
69 q = self.UiDbModel.query()
69 q = self.UiDbModel.query()
70 q = q.filter(self.UiDbModel.ui_key == key)
70 q = q.filter(self.UiDbModel.ui_key == key)
71 q = self._filter_by_repo(RepoRhodeCodeUi, q)
71 q = self._filter_by_repo(RepoRhodeCodeUi, q)
72 return q.scalar()
72 return q.scalar()
73
73
74 def get_ui_by_section(self, section):
74 def get_ui_by_section(self, section):
75 q = self.UiDbModel.query()
75 q = self.UiDbModel.query()
76 q = q.filter(self.UiDbModel.ui_section == section)
76 q = q.filter(self.UiDbModel.ui_section == section)
77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
77 q = self._filter_by_repo(RepoRhodeCodeUi, q)
78 return q.all()
78 return q.all()
79
79
80 def get_ui_by_section_and_key(self, section, key):
80 def get_ui_by_section_and_key(self, section, key):
81 q = self.UiDbModel.query()
81 q = self.UiDbModel.query()
82 q = q.filter(self.UiDbModel.ui_section == section)
82 q = q.filter(self.UiDbModel.ui_section == section)
83 q = q.filter(self.UiDbModel.ui_key == key)
83 q = q.filter(self.UiDbModel.ui_key == key)
84 q = self._filter_by_repo(RepoRhodeCodeUi, q)
84 q = self._filter_by_repo(RepoRhodeCodeUi, q)
85 return q.scalar()
85 return q.scalar()
86
86
87 def get_ui(self, section=None, key=None):
87 def get_ui(self, section=None, key=None):
88 q = self.UiDbModel.query()
88 q = self.UiDbModel.query()
89 q = self._filter_by_repo(RepoRhodeCodeUi, q)
89 q = self._filter_by_repo(RepoRhodeCodeUi, q)
90
90
91 if section:
91 if section:
92 q = q.filter(self.UiDbModel.ui_section == section)
92 q = q.filter(self.UiDbModel.ui_section == section)
93 if key:
93 if key:
94 q = q.filter(self.UiDbModel.ui_key == key)
94 q = q.filter(self.UiDbModel.ui_key == key)
95
95
96 # TODO: mikhail: add caching
96 # TODO: mikhail: add caching
97 result = [
97 result = [
98 UiSetting(
98 UiSetting(
99 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
99 section=safe_str(r.ui_section), key=safe_str(r.ui_key),
100 value=safe_str(r.ui_value), active=r.ui_active
100 value=safe_str(r.ui_value), active=r.ui_active
101 )
101 )
102 for r in q.all()
102 for r in q.all()
103 ]
103 ]
104 return result
104 return result
105
105
106 def get_builtin_hooks(self):
106 def get_builtin_hooks(self):
107 q = self.UiDbModel.query()
107 q = self.UiDbModel.query()
108 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
108 q = q.filter(self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
109 return self._get_hooks(q)
109 return self._get_hooks(q)
110
110
111 def get_custom_hooks(self):
111 def get_custom_hooks(self):
112 q = self.UiDbModel.query()
112 q = self.UiDbModel.query()
113 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
113 q = q.filter(~self.UiDbModel.ui_key.in_(self.BUILTIN_HOOKS))
114 return self._get_hooks(q)
114 return self._get_hooks(q)
115
115
116 def create_ui_section_value(self, section, val, key=None, active=True):
116 def create_ui_section_value(self, section, val, key=None, active=True):
117 new_ui = self.UiDbModel()
117 new_ui = self.UiDbModel()
118 new_ui.ui_section = section
118 new_ui.ui_section = section
119 new_ui.ui_value = val
119 new_ui.ui_value = val
120 new_ui.ui_active = active
120 new_ui.ui_active = active
121
121
122 if self.repo:
122 if self.repo:
123 repo = self._get_repo(self.repo)
123 repo = self._get_repo(self.repo)
124 repository_id = repo.repo_id
124 repository_id = repo.repo_id
125 new_ui.repository_id = repository_id
125 new_ui.repository_id = repository_id
126
126
127 if not key:
127 if not key:
128 # keys are unique so they need appended info
128 # keys are unique so they need appended info
129 if self.repo:
129 if self.repo:
130 key = hashlib.sha1(
130 key = hashlib.sha1(
131 '{}{}{}'.format(section, val, repository_id)).hexdigest()
131 '{}{}{}'.format(section, val, repository_id)).hexdigest()
132 else:
132 else:
133 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
133 key = hashlib.sha1('{}{}'.format(section, val)).hexdigest()
134
134
135 new_ui.ui_key = key
135 new_ui.ui_key = key
136
136
137 Session().add(new_ui)
137 Session().add(new_ui)
138 return new_ui
138 return new_ui
139
139
140 def create_or_update_hook(self, key, value):
140 def create_or_update_hook(self, key, value):
141 ui = (
141 ui = (
142 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
142 self.get_ui_by_section_and_key(self.HOOKS_SECTION, key) or
143 self.UiDbModel())
143 self.UiDbModel())
144 ui.ui_section = self.HOOKS_SECTION
144 ui.ui_section = self.HOOKS_SECTION
145 ui.ui_active = True
145 ui.ui_active = True
146 ui.ui_key = key
146 ui.ui_key = key
147 ui.ui_value = value
147 ui.ui_value = value
148
148
149 if self.repo:
149 if self.repo:
150 repo = self._get_repo(self.repo)
150 repo = self._get_repo(self.repo)
151 repository_id = repo.repo_id
151 repository_id = repo.repo_id
152 ui.repository_id = repository_id
152 ui.repository_id = repository_id
153
153
154 Session().add(ui)
154 Session().add(ui)
155 return ui
155 return ui
156
156
157 def delete_ui(self, id_):
157 def delete_ui(self, id_):
158 ui = self.UiDbModel.get(id_)
158 ui = self.UiDbModel.get(id_)
159 if not ui:
159 if not ui:
160 raise SettingNotFound(id_)
160 raise SettingNotFound(id_)
161 Session().delete(ui)
161 Session().delete(ui)
162
162
163 def get_setting_by_name(self, name):
163 def get_setting_by_name(self, name):
164 q = self._get_settings_query()
164 q = self._get_settings_query()
165 q = q.filter(self.SettingsDbModel.app_settings_name == name)
165 q = q.filter(self.SettingsDbModel.app_settings_name == name)
166 return q.scalar()
166 return q.scalar()
167
167
168 def create_or_update_setting(
168 def create_or_update_setting(
169 self, name, val=Optional(''), type_=Optional('unicode')):
169 self, name, val=Optional(''), type_=Optional('unicode')):
170 """
170 """
171 Creates or updates RhodeCode setting. If updates is triggered it will
171 Creates or updates RhodeCode setting. If updates is triggered it will
172 only update parameters that are explicityl set Optional instance will
172 only update parameters that are explicityl set Optional instance will
173 be skipped
173 be skipped
174
174
175 :param name:
175 :param name:
176 :param val:
176 :param val:
177 :param type_:
177 :param type_:
178 :return:
178 :return:
179 """
179 """
180
180
181 res = self.get_setting_by_name(name)
181 res = self.get_setting_by_name(name)
182 repo = self._get_repo(self.repo) if self.repo else None
182 repo = self._get_repo(self.repo) if self.repo else None
183
183
184 if not res:
184 if not res:
185 val = Optional.extract(val)
185 val = Optional.extract(val)
186 type_ = Optional.extract(type_)
186 type_ = Optional.extract(type_)
187
187
188 args = (
188 args = (
189 (repo.repo_id, name, val, type_)
189 (repo.repo_id, name, val, type_)
190 if repo else (name, val, type_))
190 if repo else (name, val, type_))
191 res = self.SettingsDbModel(*args)
191 res = self.SettingsDbModel(*args)
192
192
193 else:
193 else:
194 if self.repo:
194 if self.repo:
195 res.repository_id = repo.repo_id
195 res.repository_id = repo.repo_id
196
196
197 res.app_settings_name = name
197 res.app_settings_name = name
198 if not isinstance(type_, Optional):
198 if not isinstance(type_, Optional):
199 # update if set
199 # update if set
200 res.app_settings_type = type_
200 res.app_settings_type = type_
201 if not isinstance(val, Optional):
201 if not isinstance(val, Optional):
202 # update if set
202 # update if set
203 res.app_settings_value = val
203 res.app_settings_value = val
204
204
205 Session().add(res)
205 Session().add(res)
206 return res
206 return res
207
207
208 def invalidate_settings_cache(self):
208 def invalidate_settings_cache(self):
209 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
209 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
210 CacheKey.set_invalidate(invalidation_namespace)
210 CacheKey.set_invalidate(invalidation_namespace)
211
211
212 def get_all_settings(self, cache=False):
212 def get_all_settings(self, cache=False):
213 region = rc_cache.get_or_create_region('sql_cache_short')
213 region = rc_cache.get_or_create_region('sql_cache_short')
214 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
214 invalidation_namespace = CacheKey.SETTINGS_INVALIDATION_NAMESPACE
215
215
216 @region.conditional_cache_on_arguments(condition=cache)
216 @region.conditional_cache_on_arguments(condition=cache)
217 def _get_all_settings(name, key):
217 def _get_all_settings(name, key):
218 q = self._get_settings_query()
218 q = self._get_settings_query()
219 if not q:
219 if not q:
220 raise Exception('Could not get application settings !')
220 raise Exception('Could not get application settings !')
221
221
222 settings = {
222 settings = {
223 'rhodecode_' + result.app_settings_name: result.app_settings_value
223 'rhodecode_' + result.app_settings_name: result.app_settings_value
224 for result in q
224 for result in q
225 }
225 }
226 return settings
226 return settings
227
227
228 repo = self._get_repo(self.repo) if self.repo else None
228 repo = self._get_repo(self.repo) if self.repo else None
229 key = "settings_repo.{}".format(repo.repo_id) if repo else "settings_app"
229 key = "settings_repo.{}".format(repo.repo_id) if repo else "settings_app"
230
230
231 inv_context_manager = rc_cache.InvalidationContext(
231 inv_context_manager = rc_cache.InvalidationContext(
232 uid='cache_settings', invalidation_namespace=invalidation_namespace)
232 uid='cache_settings', invalidation_namespace=invalidation_namespace)
233 with inv_context_manager as invalidation_context:
233 with inv_context_manager as invalidation_context:
234 # check for stored invalidation signal, and maybe purge the cache
234 # check for stored invalidation signal, and maybe purge the cache
235 # before computing it again
235 # before computing it again
236 if invalidation_context.should_invalidate():
236 if invalidation_context.should_invalidate():
237 # NOTE:(marcink) we flush the whole sql_cache_short region, because it
237 # NOTE:(marcink) we flush the whole sql_cache_short region, because it
238 # reads different settings etc. It's little too much but those caches
238 # reads different settings etc. It's little too much but those caches
239 # are anyway very short lived and it's a safest way.
239 # are anyway very short lived and it's a safest way.
240 region = rc_cache.get_or_create_region('sql_cache_short')
240 region = rc_cache.get_or_create_region('sql_cache_short')
241 region.invalidate()
241 region.invalidate()
242
242
243 result = _get_all_settings('rhodecode_settings', key)
243 result = _get_all_settings('rhodecode_settings', key)
244 log.debug('Fetching app settings for key: %s took: %.3fs', key,
244 log.debug('Fetching app settings for key: %s took: %.3fs', key,
245 inv_context_manager.compute_time)
245 inv_context_manager.compute_time)
246
246
247 return result
247 return result
248
248
249 def get_auth_settings(self):
249 def get_auth_settings(self):
250 q = self._get_settings_query()
250 q = self._get_settings_query()
251 q = q.filter(
251 q = q.filter(
252 self.SettingsDbModel.app_settings_name.startswith('auth_'))
252 self.SettingsDbModel.app_settings_name.startswith('auth_'))
253 rows = q.all()
253 rows = q.all()
254 auth_settings = {
254 auth_settings = {
255 row.app_settings_name: row.app_settings_value for row in rows}
255 row.app_settings_name: row.app_settings_value for row in rows}
256 return auth_settings
256 return auth_settings
257
257
258 def get_auth_plugins(self):
258 def get_auth_plugins(self):
259 auth_plugins = self.get_setting_by_name("auth_plugins")
259 auth_plugins = self.get_setting_by_name("auth_plugins")
260 return auth_plugins.app_settings_value
260 return auth_plugins.app_settings_value
261
261
262 def get_default_repo_settings(self, strip_prefix=False):
262 def get_default_repo_settings(self, strip_prefix=False):
263 q = self._get_settings_query()
263 q = self._get_settings_query()
264 q = q.filter(
264 q = q.filter(
265 self.SettingsDbModel.app_settings_name.startswith('default_'))
265 self.SettingsDbModel.app_settings_name.startswith('default_'))
266 rows = q.all()
266 rows = q.all()
267
267
268 result = {}
268 result = {}
269 for row in rows:
269 for row in rows:
270 key = row.app_settings_name
270 key = row.app_settings_name
271 if strip_prefix:
271 if strip_prefix:
272 key = remove_prefix(key, prefix='default_')
272 key = remove_prefix(key, prefix='default_')
273 result.update({key: row.app_settings_value})
273 result.update({key: row.app_settings_value})
274 return result
274 return result
275
275
276 def get_repo(self):
276 def get_repo(self):
277 repo = self._get_repo(self.repo)
277 repo = self._get_repo(self.repo)
278 if not repo:
278 if not repo:
279 raise Exception(
279 raise Exception(
280 'Repository `{}` cannot be found inside the database'.format(
280 'Repository `{}` cannot be found inside the database'.format(
281 self.repo))
281 self.repo))
282 return repo
282 return repo
283
283
284 def _filter_by_repo(self, model, query):
284 def _filter_by_repo(self, model, query):
285 if self.repo:
285 if self.repo:
286 repo = self.get_repo()
286 repo = self.get_repo()
287 query = query.filter(model.repository_id == repo.repo_id)
287 query = query.filter(model.repository_id == repo.repo_id)
288 return query
288 return query
289
289
290 def _get_hooks(self, query):
290 def _get_hooks(self, query):
291 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
291 query = query.filter(self.UiDbModel.ui_section == self.HOOKS_SECTION)
292 query = self._filter_by_repo(RepoRhodeCodeUi, query)
292 query = self._filter_by_repo(RepoRhodeCodeUi, query)
293 return query.all()
293 return query.all()
294
294
295 def _get_settings_query(self):
295 def _get_settings_query(self):
296 q = self.SettingsDbModel.query()
296 q = self.SettingsDbModel.query()
297 return self._filter_by_repo(RepoRhodeCodeSetting, q)
297 return self._filter_by_repo(RepoRhodeCodeSetting, q)
298
298
299 def list_enabled_social_plugins(self, settings):
299 def list_enabled_social_plugins(self, settings):
300 enabled = []
300 enabled = []
301 for plug in SOCIAL_PLUGINS_LIST:
301 for plug in SOCIAL_PLUGINS_LIST:
302 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
302 if str2bool(settings.get('rhodecode_auth_{}_enabled'.format(plug)
303 )):
303 )):
304 enabled.append(plug)
304 enabled.append(plug)
305 return enabled
305 return enabled
306
306
307
307
308 def assert_repo_settings(func):
308 def assert_repo_settings(func):
309 @wraps(func)
309 @wraps(func)
310 def _wrapper(self, *args, **kwargs):
310 def _wrapper(self, *args, **kwargs):
311 if not self.repo_settings:
311 if not self.repo_settings:
312 raise Exception('Repository is not specified')
312 raise Exception('Repository is not specified')
313 return func(self, *args, **kwargs)
313 return func(self, *args, **kwargs)
314 return _wrapper
314 return _wrapper
315
315
316
316
317 class IssueTrackerSettingsModel(object):
317 class IssueTrackerSettingsModel(object):
318 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
318 INHERIT_SETTINGS = 'inherit_issue_tracker_settings'
319 SETTINGS_PREFIX = 'issuetracker_'
319 SETTINGS_PREFIX = 'issuetracker_'
320
320
321 def __init__(self, sa=None, repo=None):
321 def __init__(self, sa=None, repo=None):
322 self.global_settings = SettingsModel(sa=sa)
322 self.global_settings = SettingsModel(sa=sa)
323 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
323 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
324
324
325 @property
325 @property
326 def inherit_global_settings(self):
326 def inherit_global_settings(self):
327 if not self.repo_settings:
327 if not self.repo_settings:
328 return True
328 return True
329 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
329 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
330 return setting.app_settings_value if setting else True
330 return setting.app_settings_value if setting else True
331
331
332 @inherit_global_settings.setter
332 @inherit_global_settings.setter
333 def inherit_global_settings(self, value):
333 def inherit_global_settings(self, value):
334 if self.repo_settings:
334 if self.repo_settings:
335 settings = self.repo_settings.create_or_update_setting(
335 settings = self.repo_settings.create_or_update_setting(
336 self.INHERIT_SETTINGS, value, type_='bool')
336 self.INHERIT_SETTINGS, value, type_='bool')
337 Session().add(settings)
337 Session().add(settings)
338
338
339 def _get_keyname(self, key, uid, prefix=''):
339 def _get_keyname(self, key, uid, prefix=''):
340 return '{0}{1}{2}_{3}'.format(
340 return '{0}{1}{2}_{3}'.format(
341 prefix, self.SETTINGS_PREFIX, key, uid)
341 prefix, self.SETTINGS_PREFIX, key, uid)
342
342
343 def _make_dict_for_settings(self, qs):
343 def _make_dict_for_settings(self, qs):
344 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
344 prefix_match = self._get_keyname('pat', '', 'rhodecode_')
345
345
346 issuetracker_entries = {}
346 issuetracker_entries = {}
347 # create keys
347 # create keys
348 for k, v in qs.items():
348 for k, v in qs.items():
349 if k.startswith(prefix_match):
349 if k.startswith(prefix_match):
350 uid = k[len(prefix_match):]
350 uid = k[len(prefix_match):]
351 issuetracker_entries[uid] = None
351 issuetracker_entries[uid] = None
352
352
353 def url_cleaner(input_str):
354 input_str = input_str.replace('"', '').replace("'", '')
355 input_str = bleach.clean(input_str, strip=True)
356 return input_str
357
353 # populate
358 # populate
354 for uid in issuetracker_entries:
359 for uid in issuetracker_entries:
360 url_data = qs.get(self._get_keyname('url', uid, 'rhodecode_'))
361
355 issuetracker_entries[uid] = AttributeDict({
362 issuetracker_entries[uid] = AttributeDict({
356 'pat': qs.get(
363 'pat': qs.get(
357 self._get_keyname('pat', uid, 'rhodecode_')),
364 self._get_keyname('pat', uid, 'rhodecode_')),
358 'url': bleach.clean(
365 'url': url_cleaner(
359 qs.get(self._get_keyname('url', uid, 'rhodecode_')) or ''),
366 qs.get(self._get_keyname('url', uid, 'rhodecode_')) or ''),
360 'pref': bleach.clean(
367 'pref': bleach.clean(
361 qs.get(self._get_keyname('pref', uid, 'rhodecode_')) or ''),
368 qs.get(self._get_keyname('pref', uid, 'rhodecode_')) or ''),
362 'desc': qs.get(
369 'desc': qs.get(
363 self._get_keyname('desc', uid, 'rhodecode_')),
370 self._get_keyname('desc', uid, 'rhodecode_')),
364 })
371 })
372
365 return issuetracker_entries
373 return issuetracker_entries
366
374
367 def get_global_settings(self, cache=False):
375 def get_global_settings(self, cache=False):
368 """
376 """
369 Returns list of global issue tracker settings
377 Returns list of global issue tracker settings
370 """
378 """
371 defaults = self.global_settings.get_all_settings(cache=cache)
379 defaults = self.global_settings.get_all_settings(cache=cache)
372 settings = self._make_dict_for_settings(defaults)
380 settings = self._make_dict_for_settings(defaults)
373 return settings
381 return settings
374
382
375 def get_repo_settings(self, cache=False):
383 def get_repo_settings(self, cache=False):
376 """
384 """
377 Returns list of issue tracker settings per repository
385 Returns list of issue tracker settings per repository
378 """
386 """
379 if not self.repo_settings:
387 if not self.repo_settings:
380 raise Exception('Repository is not specified')
388 raise Exception('Repository is not specified')
381 all_settings = self.repo_settings.get_all_settings(cache=cache)
389 all_settings = self.repo_settings.get_all_settings(cache=cache)
382 settings = self._make_dict_for_settings(all_settings)
390 settings = self._make_dict_for_settings(all_settings)
383 return settings
391 return settings
384
392
385 def get_settings(self, cache=False):
393 def get_settings(self, cache=False):
386 if self.inherit_global_settings:
394 if self.inherit_global_settings:
387 return self.get_global_settings(cache=cache)
395 return self.get_global_settings(cache=cache)
388 else:
396 else:
389 return self.get_repo_settings(cache=cache)
397 return self.get_repo_settings(cache=cache)
390
398
391 def delete_entries(self, uid):
399 def delete_entries(self, uid):
392 if self.repo_settings:
400 if self.repo_settings:
393 all_patterns = self.get_repo_settings()
401 all_patterns = self.get_repo_settings()
394 settings_model = self.repo_settings
402 settings_model = self.repo_settings
395 else:
403 else:
396 all_patterns = self.get_global_settings()
404 all_patterns = self.get_global_settings()
397 settings_model = self.global_settings
405 settings_model = self.global_settings
398 entries = all_patterns.get(uid, [])
406 entries = all_patterns.get(uid, [])
399
407
400 for del_key in entries:
408 for del_key in entries:
401 setting_name = self._get_keyname(del_key, uid)
409 setting_name = self._get_keyname(del_key, uid)
402 entry = settings_model.get_setting_by_name(setting_name)
410 entry = settings_model.get_setting_by_name(setting_name)
403 if entry:
411 if entry:
404 Session().delete(entry)
412 Session().delete(entry)
405
413
406 Session().commit()
414 Session().commit()
407
415
408 def create_or_update_setting(
416 def create_or_update_setting(
409 self, name, val=Optional(''), type_=Optional('unicode')):
417 self, name, val=Optional(''), type_=Optional('unicode')):
410 if self.repo_settings:
418 if self.repo_settings:
411 setting = self.repo_settings.create_or_update_setting(
419 setting = self.repo_settings.create_or_update_setting(
412 name, val, type_)
420 name, val, type_)
413 else:
421 else:
414 setting = self.global_settings.create_or_update_setting(
422 setting = self.global_settings.create_or_update_setting(
415 name, val, type_)
423 name, val, type_)
416 return setting
424 return setting
417
425
418
426
419 class VcsSettingsModel(object):
427 class VcsSettingsModel(object):
420
428
421 INHERIT_SETTINGS = 'inherit_vcs_settings'
429 INHERIT_SETTINGS = 'inherit_vcs_settings'
422 GENERAL_SETTINGS = (
430 GENERAL_SETTINGS = (
423 'use_outdated_comments',
431 'use_outdated_comments',
424 'pr_merge_enabled',
432 'pr_merge_enabled',
425 'hg_use_rebase_for_merging',
433 'hg_use_rebase_for_merging',
426 'hg_close_branch_before_merging',
434 'hg_close_branch_before_merging',
427 'git_use_rebase_for_merging',
435 'git_use_rebase_for_merging',
428 'git_close_branch_before_merging',
436 'git_close_branch_before_merging',
429 'diff_cache',
437 'diff_cache',
430 )
438 )
431
439
432 HOOKS_SETTINGS = (
440 HOOKS_SETTINGS = (
433 ('hooks', 'changegroup.repo_size'),
441 ('hooks', 'changegroup.repo_size'),
434 ('hooks', 'changegroup.push_logger'),
442 ('hooks', 'changegroup.push_logger'),
435 ('hooks', 'outgoing.pull_logger'),)
443 ('hooks', 'outgoing.pull_logger'),)
436 HG_SETTINGS = (
444 HG_SETTINGS = (
437 ('extensions', 'largefiles'),
445 ('extensions', 'largefiles'),
438 ('phases', 'publish'),
446 ('phases', 'publish'),
439 ('extensions', 'evolve'),)
447 ('extensions', 'evolve'),)
440 GIT_SETTINGS = (
448 GIT_SETTINGS = (
441 ('vcs_git_lfs', 'enabled'),)
449 ('vcs_git_lfs', 'enabled'),)
442 GLOBAL_HG_SETTINGS = (
450 GLOBAL_HG_SETTINGS = (
443 ('extensions', 'largefiles'),
451 ('extensions', 'largefiles'),
444 ('largefiles', 'usercache'),
452 ('largefiles', 'usercache'),
445 ('phases', 'publish'),
453 ('phases', 'publish'),
446 ('extensions', 'hgsubversion'),
454 ('extensions', 'hgsubversion'),
447 ('extensions', 'evolve'),)
455 ('extensions', 'evolve'),)
448 GLOBAL_GIT_SETTINGS = (
456 GLOBAL_GIT_SETTINGS = (
449 ('vcs_git_lfs', 'enabled'),
457 ('vcs_git_lfs', 'enabled'),
450 ('vcs_git_lfs', 'store_location'))
458 ('vcs_git_lfs', 'store_location'))
451
459
452 GLOBAL_SVN_SETTINGS = (
460 GLOBAL_SVN_SETTINGS = (
453 ('vcs_svn_proxy', 'http_requests_enabled'),
461 ('vcs_svn_proxy', 'http_requests_enabled'),
454 ('vcs_svn_proxy', 'http_server_url'))
462 ('vcs_svn_proxy', 'http_server_url'))
455
463
456 SVN_BRANCH_SECTION = 'vcs_svn_branch'
464 SVN_BRANCH_SECTION = 'vcs_svn_branch'
457 SVN_TAG_SECTION = 'vcs_svn_tag'
465 SVN_TAG_SECTION = 'vcs_svn_tag'
458 SSL_SETTING = ('web', 'push_ssl')
466 SSL_SETTING = ('web', 'push_ssl')
459 PATH_SETTING = ('paths', '/')
467 PATH_SETTING = ('paths', '/')
460
468
461 def __init__(self, sa=None, repo=None):
469 def __init__(self, sa=None, repo=None):
462 self.global_settings = SettingsModel(sa=sa)
470 self.global_settings = SettingsModel(sa=sa)
463 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
471 self.repo_settings = SettingsModel(sa=sa, repo=repo) if repo else None
464 self._ui_settings = (
472 self._ui_settings = (
465 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
473 self.HG_SETTINGS + self.GIT_SETTINGS + self.HOOKS_SETTINGS)
466 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
474 self._svn_sections = (self.SVN_BRANCH_SECTION, self.SVN_TAG_SECTION)
467
475
468 @property
476 @property
469 @assert_repo_settings
477 @assert_repo_settings
470 def inherit_global_settings(self):
478 def inherit_global_settings(self):
471 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
479 setting = self.repo_settings.get_setting_by_name(self.INHERIT_SETTINGS)
472 return setting.app_settings_value if setting else True
480 return setting.app_settings_value if setting else True
473
481
474 @inherit_global_settings.setter
482 @inherit_global_settings.setter
475 @assert_repo_settings
483 @assert_repo_settings
476 def inherit_global_settings(self, value):
484 def inherit_global_settings(self, value):
477 self.repo_settings.create_or_update_setting(
485 self.repo_settings.create_or_update_setting(
478 self.INHERIT_SETTINGS, value, type_='bool')
486 self.INHERIT_SETTINGS, value, type_='bool')
479
487
480 def get_global_svn_branch_patterns(self):
488 def get_global_svn_branch_patterns(self):
481 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
489 return self.global_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
482
490
483 @assert_repo_settings
491 @assert_repo_settings
484 def get_repo_svn_branch_patterns(self):
492 def get_repo_svn_branch_patterns(self):
485 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
493 return self.repo_settings.get_ui_by_section(self.SVN_BRANCH_SECTION)
486
494
487 def get_global_svn_tag_patterns(self):
495 def get_global_svn_tag_patterns(self):
488 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
496 return self.global_settings.get_ui_by_section(self.SVN_TAG_SECTION)
489
497
490 @assert_repo_settings
498 @assert_repo_settings
491 def get_repo_svn_tag_patterns(self):
499 def get_repo_svn_tag_patterns(self):
492 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
500 return self.repo_settings.get_ui_by_section(self.SVN_TAG_SECTION)
493
501
494 def get_global_settings(self):
502 def get_global_settings(self):
495 return self._collect_all_settings(global_=True)
503 return self._collect_all_settings(global_=True)
496
504
497 @assert_repo_settings
505 @assert_repo_settings
498 def get_repo_settings(self):
506 def get_repo_settings(self):
499 return self._collect_all_settings(global_=False)
507 return self._collect_all_settings(global_=False)
500
508
501 @assert_repo_settings
509 @assert_repo_settings
502 def create_or_update_repo_settings(
510 def create_or_update_repo_settings(
503 self, data, inherit_global_settings=False):
511 self, data, inherit_global_settings=False):
504 from rhodecode.model.scm import ScmModel
512 from rhodecode.model.scm import ScmModel
505
513
506 self.inherit_global_settings = inherit_global_settings
514 self.inherit_global_settings = inherit_global_settings
507
515
508 repo = self.repo_settings.get_repo()
516 repo = self.repo_settings.get_repo()
509 if not inherit_global_settings:
517 if not inherit_global_settings:
510 if repo.repo_type == 'svn':
518 if repo.repo_type == 'svn':
511 self.create_repo_svn_settings(data)
519 self.create_repo_svn_settings(data)
512 else:
520 else:
513 self.create_or_update_repo_hook_settings(data)
521 self.create_or_update_repo_hook_settings(data)
514 self.create_or_update_repo_pr_settings(data)
522 self.create_or_update_repo_pr_settings(data)
515
523
516 if repo.repo_type == 'hg':
524 if repo.repo_type == 'hg':
517 self.create_or_update_repo_hg_settings(data)
525 self.create_or_update_repo_hg_settings(data)
518
526
519 if repo.repo_type == 'git':
527 if repo.repo_type == 'git':
520 self.create_or_update_repo_git_settings(data)
528 self.create_or_update_repo_git_settings(data)
521
529
522 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
530 ScmModel().mark_for_invalidation(repo.repo_name, delete=True)
523
531
524 @assert_repo_settings
532 @assert_repo_settings
525 def create_or_update_repo_hook_settings(self, data):
533 def create_or_update_repo_hook_settings(self, data):
526 for section, key in self.HOOKS_SETTINGS:
534 for section, key in self.HOOKS_SETTINGS:
527 data_key = self._get_form_ui_key(section, key)
535 data_key = self._get_form_ui_key(section, key)
528 if data_key not in data:
536 if data_key not in data:
529 raise ValueError(
537 raise ValueError(
530 'The given data does not contain {} key'.format(data_key))
538 'The given data does not contain {} key'.format(data_key))
531
539
532 active = data.get(data_key)
540 active = data.get(data_key)
533 repo_setting = self.repo_settings.get_ui_by_section_and_key(
541 repo_setting = self.repo_settings.get_ui_by_section_and_key(
534 section, key)
542 section, key)
535 if not repo_setting:
543 if not repo_setting:
536 global_setting = self.global_settings.\
544 global_setting = self.global_settings.\
537 get_ui_by_section_and_key(section, key)
545 get_ui_by_section_and_key(section, key)
538 self.repo_settings.create_ui_section_value(
546 self.repo_settings.create_ui_section_value(
539 section, global_setting.ui_value, key=key, active=active)
547 section, global_setting.ui_value, key=key, active=active)
540 else:
548 else:
541 repo_setting.ui_active = active
549 repo_setting.ui_active = active
542 Session().add(repo_setting)
550 Session().add(repo_setting)
543
551
544 def update_global_hook_settings(self, data):
552 def update_global_hook_settings(self, data):
545 for section, key in self.HOOKS_SETTINGS:
553 for section, key in self.HOOKS_SETTINGS:
546 data_key = self._get_form_ui_key(section, key)
554 data_key = self._get_form_ui_key(section, key)
547 if data_key not in data:
555 if data_key not in data:
548 raise ValueError(
556 raise ValueError(
549 'The given data does not contain {} key'.format(data_key))
557 'The given data does not contain {} key'.format(data_key))
550 active = data.get(data_key)
558 active = data.get(data_key)
551 repo_setting = self.global_settings.get_ui_by_section_and_key(
559 repo_setting = self.global_settings.get_ui_by_section_and_key(
552 section, key)
560 section, key)
553 repo_setting.ui_active = active
561 repo_setting.ui_active = active
554 Session().add(repo_setting)
562 Session().add(repo_setting)
555
563
556 @assert_repo_settings
564 @assert_repo_settings
557 def create_or_update_repo_pr_settings(self, data):
565 def create_or_update_repo_pr_settings(self, data):
558 return self._create_or_update_general_settings(
566 return self._create_or_update_general_settings(
559 self.repo_settings, data)
567 self.repo_settings, data)
560
568
561 def create_or_update_global_pr_settings(self, data):
569 def create_or_update_global_pr_settings(self, data):
562 return self._create_or_update_general_settings(
570 return self._create_or_update_general_settings(
563 self.global_settings, data)
571 self.global_settings, data)
564
572
565 @assert_repo_settings
573 @assert_repo_settings
566 def create_repo_svn_settings(self, data):
574 def create_repo_svn_settings(self, data):
567 return self._create_svn_settings(self.repo_settings, data)
575 return self._create_svn_settings(self.repo_settings, data)
568
576
569 @assert_repo_settings
577 @assert_repo_settings
570 def create_or_update_repo_hg_settings(self, data):
578 def create_or_update_repo_hg_settings(self, data):
571 largefiles, phases, evolve = \
579 largefiles, phases, evolve = \
572 self.HG_SETTINGS
580 self.HG_SETTINGS
573 largefiles_key, phases_key, evolve_key = \
581 largefiles_key, phases_key, evolve_key = \
574 self._get_settings_keys(self.HG_SETTINGS, data)
582 self._get_settings_keys(self.HG_SETTINGS, data)
575
583
576 self._create_or_update_ui(
584 self._create_or_update_ui(
577 self.repo_settings, *largefiles, value='',
585 self.repo_settings, *largefiles, value='',
578 active=data[largefiles_key])
586 active=data[largefiles_key])
579 self._create_or_update_ui(
587 self._create_or_update_ui(
580 self.repo_settings, *evolve, value='',
588 self.repo_settings, *evolve, value='',
581 active=data[evolve_key])
589 active=data[evolve_key])
582 self._create_or_update_ui(
590 self._create_or_update_ui(
583 self.repo_settings, *phases, value=safe_str(data[phases_key]))
591 self.repo_settings, *phases, value=safe_str(data[phases_key]))
584
592
585 def create_or_update_global_hg_settings(self, data):
593 def create_or_update_global_hg_settings(self, data):
586 largefiles, largefiles_store, phases, hgsubversion, evolve \
594 largefiles, largefiles_store, phases, hgsubversion, evolve \
587 = self.GLOBAL_HG_SETTINGS
595 = self.GLOBAL_HG_SETTINGS
588 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
596 largefiles_key, largefiles_store_key, phases_key, subversion_key, evolve_key \
589 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
597 = self._get_settings_keys(self.GLOBAL_HG_SETTINGS, data)
590
598
591 self._create_or_update_ui(
599 self._create_or_update_ui(
592 self.global_settings, *largefiles, value='',
600 self.global_settings, *largefiles, value='',
593 active=data[largefiles_key])
601 active=data[largefiles_key])
594 self._create_or_update_ui(
602 self._create_or_update_ui(
595 self.global_settings, *largefiles_store,
603 self.global_settings, *largefiles_store,
596 value=data[largefiles_store_key])
604 value=data[largefiles_store_key])
597 self._create_or_update_ui(
605 self._create_or_update_ui(
598 self.global_settings, *phases, value=safe_str(data[phases_key]))
606 self.global_settings, *phases, value=safe_str(data[phases_key]))
599 self._create_or_update_ui(
607 self._create_or_update_ui(
600 self.global_settings, *hgsubversion, active=data[subversion_key])
608 self.global_settings, *hgsubversion, active=data[subversion_key])
601 self._create_or_update_ui(
609 self._create_or_update_ui(
602 self.global_settings, *evolve, value='',
610 self.global_settings, *evolve, value='',
603 active=data[evolve_key])
611 active=data[evolve_key])
604
612
605 def create_or_update_repo_git_settings(self, data):
613 def create_or_update_repo_git_settings(self, data):
606 # NOTE(marcink): # comma make unpack work properly
614 # NOTE(marcink): # comma make unpack work properly
607 lfs_enabled, \
615 lfs_enabled, \
608 = self.GIT_SETTINGS
616 = self.GIT_SETTINGS
609
617
610 lfs_enabled_key, \
618 lfs_enabled_key, \
611 = self._get_settings_keys(self.GIT_SETTINGS, data)
619 = self._get_settings_keys(self.GIT_SETTINGS, data)
612
620
613 self._create_or_update_ui(
621 self._create_or_update_ui(
614 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
622 self.repo_settings, *lfs_enabled, value=data[lfs_enabled_key],
615 active=data[lfs_enabled_key])
623 active=data[lfs_enabled_key])
616
624
617 def create_or_update_global_git_settings(self, data):
625 def create_or_update_global_git_settings(self, data):
618 lfs_enabled, lfs_store_location \
626 lfs_enabled, lfs_store_location \
619 = self.GLOBAL_GIT_SETTINGS
627 = self.GLOBAL_GIT_SETTINGS
620 lfs_enabled_key, lfs_store_location_key \
628 lfs_enabled_key, lfs_store_location_key \
621 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
629 = self._get_settings_keys(self.GLOBAL_GIT_SETTINGS, data)
622
630
623 self._create_or_update_ui(
631 self._create_or_update_ui(
624 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
632 self.global_settings, *lfs_enabled, value=data[lfs_enabled_key],
625 active=data[lfs_enabled_key])
633 active=data[lfs_enabled_key])
626 self._create_or_update_ui(
634 self._create_or_update_ui(
627 self.global_settings, *lfs_store_location,
635 self.global_settings, *lfs_store_location,
628 value=data[lfs_store_location_key])
636 value=data[lfs_store_location_key])
629
637
630 def create_or_update_global_svn_settings(self, data):
638 def create_or_update_global_svn_settings(self, data):
631 # branch/tags patterns
639 # branch/tags patterns
632 self._create_svn_settings(self.global_settings, data)
640 self._create_svn_settings(self.global_settings, data)
633
641
634 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
642 http_requests_enabled, http_server_url = self.GLOBAL_SVN_SETTINGS
635 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
643 http_requests_enabled_key, http_server_url_key = self._get_settings_keys(
636 self.GLOBAL_SVN_SETTINGS, data)
644 self.GLOBAL_SVN_SETTINGS, data)
637
645
638 self._create_or_update_ui(
646 self._create_or_update_ui(
639 self.global_settings, *http_requests_enabled,
647 self.global_settings, *http_requests_enabled,
640 value=safe_str(data[http_requests_enabled_key]))
648 value=safe_str(data[http_requests_enabled_key]))
641 self._create_or_update_ui(
649 self._create_or_update_ui(
642 self.global_settings, *http_server_url,
650 self.global_settings, *http_server_url,
643 value=data[http_server_url_key])
651 value=data[http_server_url_key])
644
652
645 def update_global_ssl_setting(self, value):
653 def update_global_ssl_setting(self, value):
646 self._create_or_update_ui(
654 self._create_or_update_ui(
647 self.global_settings, *self.SSL_SETTING, value=value)
655 self.global_settings, *self.SSL_SETTING, value=value)
648
656
649 def update_global_path_setting(self, value):
657 def update_global_path_setting(self, value):
650 self._create_or_update_ui(
658 self._create_or_update_ui(
651 self.global_settings, *self.PATH_SETTING, value=value)
659 self.global_settings, *self.PATH_SETTING, value=value)
652
660
653 @assert_repo_settings
661 @assert_repo_settings
654 def delete_repo_svn_pattern(self, id_):
662 def delete_repo_svn_pattern(self, id_):
655 ui = self.repo_settings.UiDbModel.get(id_)
663 ui = self.repo_settings.UiDbModel.get(id_)
656 if ui and ui.repository.repo_name == self.repo_settings.repo:
664 if ui and ui.repository.repo_name == self.repo_settings.repo:
657 # only delete if it's the same repo as initialized settings
665 # only delete if it's the same repo as initialized settings
658 self.repo_settings.delete_ui(id_)
666 self.repo_settings.delete_ui(id_)
659 else:
667 else:
660 # raise error as if we wouldn't find this option
668 # raise error as if we wouldn't find this option
661 self.repo_settings.delete_ui(-1)
669 self.repo_settings.delete_ui(-1)
662
670
663 def delete_global_svn_pattern(self, id_):
671 def delete_global_svn_pattern(self, id_):
664 self.global_settings.delete_ui(id_)
672 self.global_settings.delete_ui(id_)
665
673
666 @assert_repo_settings
674 @assert_repo_settings
667 def get_repo_ui_settings(self, section=None, key=None):
675 def get_repo_ui_settings(self, section=None, key=None):
668 global_uis = self.global_settings.get_ui(section, key)
676 global_uis = self.global_settings.get_ui(section, key)
669 repo_uis = self.repo_settings.get_ui(section, key)
677 repo_uis = self.repo_settings.get_ui(section, key)
670 filtered_repo_uis = self._filter_ui_settings(repo_uis)
678 filtered_repo_uis = self._filter_ui_settings(repo_uis)
671 filtered_repo_uis_keys = [
679 filtered_repo_uis_keys = [
672 (s.section, s.key) for s in filtered_repo_uis]
680 (s.section, s.key) for s in filtered_repo_uis]
673
681
674 def _is_global_ui_filtered(ui):
682 def _is_global_ui_filtered(ui):
675 return (
683 return (
676 (ui.section, ui.key) in filtered_repo_uis_keys
684 (ui.section, ui.key) in filtered_repo_uis_keys
677 or ui.section in self._svn_sections)
685 or ui.section in self._svn_sections)
678
686
679 filtered_global_uis = [
687 filtered_global_uis = [
680 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
688 ui for ui in global_uis if not _is_global_ui_filtered(ui)]
681
689
682 return filtered_global_uis + filtered_repo_uis
690 return filtered_global_uis + filtered_repo_uis
683
691
684 def get_global_ui_settings(self, section=None, key=None):
692 def get_global_ui_settings(self, section=None, key=None):
685 return self.global_settings.get_ui(section, key)
693 return self.global_settings.get_ui(section, key)
686
694
687 def get_ui_settings_as_config_obj(self, section=None, key=None):
695 def get_ui_settings_as_config_obj(self, section=None, key=None):
688 config = base.Config()
696 config = base.Config()
689
697
690 ui_settings = self.get_ui_settings(section=section, key=key)
698 ui_settings = self.get_ui_settings(section=section, key=key)
691
699
692 for entry in ui_settings:
700 for entry in ui_settings:
693 config.set(entry.section, entry.key, entry.value)
701 config.set(entry.section, entry.key, entry.value)
694
702
695 return config
703 return config
696
704
697 def get_ui_settings(self, section=None, key=None):
705 def get_ui_settings(self, section=None, key=None):
698 if not self.repo_settings or self.inherit_global_settings:
706 if not self.repo_settings or self.inherit_global_settings:
699 return self.get_global_ui_settings(section, key)
707 return self.get_global_ui_settings(section, key)
700 else:
708 else:
701 return self.get_repo_ui_settings(section, key)
709 return self.get_repo_ui_settings(section, key)
702
710
703 def get_svn_patterns(self, section=None):
711 def get_svn_patterns(self, section=None):
704 if not self.repo_settings:
712 if not self.repo_settings:
705 return self.get_global_ui_settings(section)
713 return self.get_global_ui_settings(section)
706 else:
714 else:
707 return self.get_repo_ui_settings(section)
715 return self.get_repo_ui_settings(section)
708
716
709 @assert_repo_settings
717 @assert_repo_settings
710 def get_repo_general_settings(self):
718 def get_repo_general_settings(self):
711 global_settings = self.global_settings.get_all_settings()
719 global_settings = self.global_settings.get_all_settings()
712 repo_settings = self.repo_settings.get_all_settings()
720 repo_settings = self.repo_settings.get_all_settings()
713 filtered_repo_settings = self._filter_general_settings(repo_settings)
721 filtered_repo_settings = self._filter_general_settings(repo_settings)
714 global_settings.update(filtered_repo_settings)
722 global_settings.update(filtered_repo_settings)
715 return global_settings
723 return global_settings
716
724
717 def get_global_general_settings(self):
725 def get_global_general_settings(self):
718 return self.global_settings.get_all_settings()
726 return self.global_settings.get_all_settings()
719
727
720 def get_general_settings(self):
728 def get_general_settings(self):
721 if not self.repo_settings or self.inherit_global_settings:
729 if not self.repo_settings or self.inherit_global_settings:
722 return self.get_global_general_settings()
730 return self.get_global_general_settings()
723 else:
731 else:
724 return self.get_repo_general_settings()
732 return self.get_repo_general_settings()
725
733
726 def get_repos_location(self):
734 def get_repos_location(self):
727 return self.global_settings.get_ui_by_key('/').ui_value
735 return self.global_settings.get_ui_by_key('/').ui_value
728
736
729 def _filter_ui_settings(self, settings):
737 def _filter_ui_settings(self, settings):
730 filtered_settings = [
738 filtered_settings = [
731 s for s in settings if self._should_keep_setting(s)]
739 s for s in settings if self._should_keep_setting(s)]
732 return filtered_settings
740 return filtered_settings
733
741
734 def _should_keep_setting(self, setting):
742 def _should_keep_setting(self, setting):
735 keep = (
743 keep = (
736 (setting.section, setting.key) in self._ui_settings or
744 (setting.section, setting.key) in self._ui_settings or
737 setting.section in self._svn_sections)
745 setting.section in self._svn_sections)
738 return keep
746 return keep
739
747
740 def _filter_general_settings(self, settings):
748 def _filter_general_settings(self, settings):
741 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
749 keys = ['rhodecode_{}'.format(key) for key in self.GENERAL_SETTINGS]
742 return {
750 return {
743 k: settings[k]
751 k: settings[k]
744 for k in settings if k in keys}
752 for k in settings if k in keys}
745
753
746 def _collect_all_settings(self, global_=False):
754 def _collect_all_settings(self, global_=False):
747 settings = self.global_settings if global_ else self.repo_settings
755 settings = self.global_settings if global_ else self.repo_settings
748 result = {}
756 result = {}
749
757
750 for section, key in self._ui_settings:
758 for section, key in self._ui_settings:
751 ui = settings.get_ui_by_section_and_key(section, key)
759 ui = settings.get_ui_by_section_and_key(section, key)
752 result_key = self._get_form_ui_key(section, key)
760 result_key = self._get_form_ui_key(section, key)
753
761
754 if ui:
762 if ui:
755 if section in ('hooks', 'extensions'):
763 if section in ('hooks', 'extensions'):
756 result[result_key] = ui.ui_active
764 result[result_key] = ui.ui_active
757 elif result_key in ['vcs_git_lfs_enabled']:
765 elif result_key in ['vcs_git_lfs_enabled']:
758 result[result_key] = ui.ui_active
766 result[result_key] = ui.ui_active
759 else:
767 else:
760 result[result_key] = ui.ui_value
768 result[result_key] = ui.ui_value
761
769
762 for name in self.GENERAL_SETTINGS:
770 for name in self.GENERAL_SETTINGS:
763 setting = settings.get_setting_by_name(name)
771 setting = settings.get_setting_by_name(name)
764 if setting:
772 if setting:
765 result_key = 'rhodecode_{}'.format(name)
773 result_key = 'rhodecode_{}'.format(name)
766 result[result_key] = setting.app_settings_value
774 result[result_key] = setting.app_settings_value
767
775
768 return result
776 return result
769
777
770 def _get_form_ui_key(self, section, key):
778 def _get_form_ui_key(self, section, key):
771 return '{section}_{key}'.format(
779 return '{section}_{key}'.format(
772 section=section, key=key.replace('.', '_'))
780 section=section, key=key.replace('.', '_'))
773
781
774 def _create_or_update_ui(
782 def _create_or_update_ui(
775 self, settings, section, key, value=None, active=None):
783 self, settings, section, key, value=None, active=None):
776 ui = settings.get_ui_by_section_and_key(section, key)
784 ui = settings.get_ui_by_section_and_key(section, key)
777 if not ui:
785 if not ui:
778 active = True if active is None else active
786 active = True if active is None else active
779 settings.create_ui_section_value(
787 settings.create_ui_section_value(
780 section, value, key=key, active=active)
788 section, value, key=key, active=active)
781 else:
789 else:
782 if active is not None:
790 if active is not None:
783 ui.ui_active = active
791 ui.ui_active = active
784 if value is not None:
792 if value is not None:
785 ui.ui_value = value
793 ui.ui_value = value
786 Session().add(ui)
794 Session().add(ui)
787
795
788 def _create_svn_settings(self, settings, data):
796 def _create_svn_settings(self, settings, data):
789 svn_settings = {
797 svn_settings = {
790 'new_svn_branch': self.SVN_BRANCH_SECTION,
798 'new_svn_branch': self.SVN_BRANCH_SECTION,
791 'new_svn_tag': self.SVN_TAG_SECTION
799 'new_svn_tag': self.SVN_TAG_SECTION
792 }
800 }
793 for key in svn_settings:
801 for key in svn_settings:
794 if data.get(key):
802 if data.get(key):
795 settings.create_ui_section_value(svn_settings[key], data[key])
803 settings.create_ui_section_value(svn_settings[key], data[key])
796
804
797 def _create_or_update_general_settings(self, settings, data):
805 def _create_or_update_general_settings(self, settings, data):
798 for name in self.GENERAL_SETTINGS:
806 for name in self.GENERAL_SETTINGS:
799 data_key = 'rhodecode_{}'.format(name)
807 data_key = 'rhodecode_{}'.format(name)
800 if data_key not in data:
808 if data_key not in data:
801 raise ValueError(
809 raise ValueError(
802 'The given data does not contain {} key'.format(data_key))
810 'The given data does not contain {} key'.format(data_key))
803 setting = settings.create_or_update_setting(
811 setting = settings.create_or_update_setting(
804 name, data[data_key], 'bool')
812 name, data[data_key], 'bool')
805 Session().add(setting)
813 Session().add(setting)
806
814
807 def _get_settings_keys(self, settings, data):
815 def _get_settings_keys(self, settings, data):
808 data_keys = [self._get_form_ui_key(*s) for s in settings]
816 data_keys = [self._get_form_ui_key(*s) for s in settings]
809 for data_key in data_keys:
817 for data_key in data_keys:
810 if data_key not in data:
818 if data_key not in data:
811 raise ValueError(
819 raise ValueError(
812 'The given data does not contain {} key'.format(data_key))
820 'The given data does not contain {} key'.format(data_key))
813 return data_keys
821 return data_keys
814
822
815 def create_largeobjects_dirs_if_needed(self, repo_store_path):
823 def create_largeobjects_dirs_if_needed(self, repo_store_path):
816 """
824 """
817 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
825 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
818 does a repository scan if enabled in the settings.
826 does a repository scan if enabled in the settings.
819 """
827 """
820
828
821 from rhodecode.lib.vcs.backends.hg import largefiles_store
829 from rhodecode.lib.vcs.backends.hg import largefiles_store
822 from rhodecode.lib.vcs.backends.git import lfs_store
830 from rhodecode.lib.vcs.backends.git import lfs_store
823
831
824 paths = [
832 paths = [
825 largefiles_store(repo_store_path),
833 largefiles_store(repo_store_path),
826 lfs_store(repo_store_path)]
834 lfs_store(repo_store_path)]
827
835
828 for path in paths:
836 for path in paths:
829 if os.path.isdir(path):
837 if os.path.isdir(path):
830 continue
838 continue
831 if os.path.isfile(path):
839 if os.path.isfile(path):
832 continue
840 continue
833 # not a file nor dir, we try to create it
841 # not a file nor dir, we try to create it
834 try:
842 try:
835 os.makedirs(path)
843 os.makedirs(path)
836 except Exception:
844 except Exception:
837 log.warning('Failed to create largefiles dir:%s', path)
845 log.warning('Failed to create largefiles dir:%s', path)
General Comments 0
You need to be logged in to leave comments. Login now