##// END OF EJS Templates
Make rhodecode use author/username filter from vcs instead of mercurial
marcink -
r1356:eec4defd beta
parent child Browse files
Show More
@@ -1,699 +1,695
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10
10
11 from datetime import datetime
11 from datetime import datetime
12 from pygments.formatters import HtmlFormatter
12 from pygments.formatters import HtmlFormatter
13 from pygments import highlight as code_highlight
13 from pygments import highlight as code_highlight
14 from pylons import url, request, config
14 from pylons import url, request, config
15 from pylons.i18n.translation import _, ungettext
15 from pylons.i18n.translation import _, ungettext
16
16
17 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html.tools import *
18 from webhelpers.html.tools import *
19 from webhelpers.html.builder import make_tag
19 from webhelpers.html.builder import make_tag
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 password, textarea, title, ul, xml_declaration, radio
23 password, textarea, title, ul, xml_declaration, radio
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 mail_to, strip_links, strip_tags, tag_re
25 mail_to, strip_links, strip_tags, tag_re
26 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 replace_whitespace, urlify, truncate, wrap_paragraphs
31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 from webhelpers.date import time_ago_in_words
32 from webhelpers.date import time_ago_in_words
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 convert_boolean_attrs, NotGiven
35 convert_boolean_attrs, NotGiven
36
36
37 from vcs.utils.annotate import annotate_highlight
37 from vcs.utils.annotate import annotate_highlight
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib import str2bool, safe_unicode
39 from rhodecode.lib import str2bool, safe_unicode
40
40
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 """
42 """
43 Reset button
43 Reset button
44 """
44 """
45 _set_input_attrs(attrs, type, name, value)
45 _set_input_attrs(attrs, type, name, value)
46 _set_id_attr(attrs, id, name)
46 _set_id_attr(attrs, id, name)
47 convert_boolean_attrs(attrs, ["disabled"])
47 convert_boolean_attrs(attrs, ["disabled"])
48 return HTML.input(**attrs)
48 return HTML.input(**attrs)
49
49
50 reset = _reset
50 reset = _reset
51
51
52
52
53 def get_token():
53 def get_token():
54 """Return the current authentication token, creating one if one doesn't
54 """Return the current authentication token, creating one if one doesn't
55 already exist.
55 already exist.
56 """
56 """
57 token_key = "_authentication_token"
57 token_key = "_authentication_token"
58 from pylons import session
58 from pylons import session
59 if not token_key in session:
59 if not token_key in session:
60 try:
60 try:
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 except AttributeError: # Python < 2.4
62 except AttributeError: # Python < 2.4
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 session[token_key] = token
64 session[token_key] = token
65 if hasattr(session, 'save'):
65 if hasattr(session, 'save'):
66 session.save()
66 session.save()
67 return session[token_key]
67 return session[token_key]
68
68
69 class _GetError(object):
69 class _GetError(object):
70 """Get error from form_errors, and represent it as span wrapped error
70 """Get error from form_errors, and represent it as span wrapped error
71 message
71 message
72
72
73 :param field_name: field to fetch errors for
73 :param field_name: field to fetch errors for
74 :param form_errors: form errors dict
74 :param form_errors: form errors dict
75 """
75 """
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 class _ToolTip(object):
84 class _ToolTip(object):
85
85
86 def __call__(self, tooltip_title, trim_at=50):
86 def __call__(self, tooltip_title, trim_at=50):
87 """Special function just to wrap our text into nice formatted
87 """Special function just to wrap our text into nice formatted
88 autowrapped text
88 autowrapped text
89
89
90 :param tooltip_title:
90 :param tooltip_title:
91 """
91 """
92
92
93 return escape(tooltip_title)
93 return escape(tooltip_title)
94
94
95 def activate(self):
95 def activate(self):
96 """Adds tooltip mechanism to the given Html all tooltips have to have
96 """Adds tooltip mechanism to the given Html all tooltips have to have
97 set class `tooltip` and set attribute `tooltip_title`.
97 set class `tooltip` and set attribute `tooltip_title`.
98 Then a tooltip will be generated based on that. All with yui js tooltip
98 Then a tooltip will be generated based on that. All with yui js tooltip
99 """
99 """
100
100
101 js = '''
101 js = '''
102 YAHOO.util.Event.onDOMReady(function(){
102 YAHOO.util.Event.onDOMReady(function(){
103 function toolTipsId(){
103 function toolTipsId(){
104 var ids = [];
104 var ids = [];
105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
105 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106
106
107 for (var i = 0; i < tts.length; i++) {
107 for (var i = 0; i < tts.length; i++) {
108 //if element doesn't not have and id autogenerate one for tooltip
108 //if element doesn't not have and id autogenerate one for tooltip
109
109
110 if (!tts[i].id){
110 if (!tts[i].id){
111 tts[i].id='tt'+i*100;
111 tts[i].id='tt'+i*100;
112 }
112 }
113 ids.push(tts[i].id);
113 ids.push(tts[i].id);
114 }
114 }
115 return ids
115 return ids
116 };
116 };
117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
117 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
118 context: [[toolTipsId()],"tl","bl",null,[0,5]],
119 monitorresize:false,
119 monitorresize:false,
120 xyoffset :[0,0],
120 xyoffset :[0,0],
121 autodismissdelay:300000,
121 autodismissdelay:300000,
122 hidedelay:5,
122 hidedelay:5,
123 showdelay:20,
123 showdelay:20,
124 });
124 });
125
125
126 });
126 });
127 '''
127 '''
128 return literal(js)
128 return literal(js)
129
129
130 tooltip = _ToolTip()
130 tooltip = _ToolTip()
131
131
132 class _FilesBreadCrumbs(object):
132 class _FilesBreadCrumbs(object):
133
133
134 def __call__(self, repo_name, rev, paths):
134 def __call__(self, repo_name, rev, paths):
135 if isinstance(paths, str):
135 if isinstance(paths, str):
136 paths = safe_unicode(paths)
136 paths = safe_unicode(paths)
137 url_l = [link_to(repo_name, url('files_home',
137 url_l = [link_to(repo_name, url('files_home',
138 repo_name=repo_name,
138 repo_name=repo_name,
139 revision=rev, f_path=''))]
139 revision=rev, f_path=''))]
140 paths_l = paths.split('/')
140 paths_l = paths.split('/')
141 for cnt, p in enumerate(paths_l):
141 for cnt, p in enumerate(paths_l):
142 if p != '':
142 if p != '':
143 url_l.append(link_to(p, url('files_home',
143 url_l.append(link_to(p, url('files_home',
144 repo_name=repo_name,
144 repo_name=repo_name,
145 revision=rev,
145 revision=rev,
146 f_path='/'.join(paths_l[:cnt + 1]))))
146 f_path='/'.join(paths_l[:cnt + 1]))))
147
147
148 return literal('/'.join(url_l))
148 return literal('/'.join(url_l))
149
149
150 files_breadcrumbs = _FilesBreadCrumbs()
150 files_breadcrumbs = _FilesBreadCrumbs()
151
151
152 class CodeHtmlFormatter(HtmlFormatter):
152 class CodeHtmlFormatter(HtmlFormatter):
153 """My code Html Formatter for source codes
153 """My code Html Formatter for source codes
154 """
154 """
155
155
156 def wrap(self, source, outfile):
156 def wrap(self, source, outfile):
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
157 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158
158
159 def _wrap_code(self, source):
159 def _wrap_code(self, source):
160 for cnt, it in enumerate(source):
160 for cnt, it in enumerate(source):
161 i, t = it
161 i, t = it
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
162 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 yield i, t
163 yield i, t
164
164
165 def _wrap_tablelinenos(self, inner):
165 def _wrap_tablelinenos(self, inner):
166 dummyoutfile = StringIO.StringIO()
166 dummyoutfile = StringIO.StringIO()
167 lncount = 0
167 lncount = 0
168 for t, line in inner:
168 for t, line in inner:
169 if t:
169 if t:
170 lncount += 1
170 lncount += 1
171 dummyoutfile.write(line)
171 dummyoutfile.write(line)
172
172
173 fl = self.linenostart
173 fl = self.linenostart
174 mw = len(str(lncount + fl - 1))
174 mw = len(str(lncount + fl - 1))
175 sp = self.linenospecial
175 sp = self.linenospecial
176 st = self.linenostep
176 st = self.linenostep
177 la = self.lineanchors
177 la = self.lineanchors
178 aln = self.anchorlinenos
178 aln = self.anchorlinenos
179 nocls = self.noclasses
179 nocls = self.noclasses
180 if sp:
180 if sp:
181 lines = []
181 lines = []
182
182
183 for i in range(fl, fl + lncount):
183 for i in range(fl, fl + lncount):
184 if i % st == 0:
184 if i % st == 0:
185 if i % sp == 0:
185 if i % sp == 0:
186 if aln:
186 if aln:
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
187 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 (la, i, mw, i))
188 (la, i, mw, i))
189 else:
189 else:
190 lines.append('<span class="special">%*d</span>' % (mw, i))
190 lines.append('<span class="special">%*d</span>' % (mw, i))
191 else:
191 else:
192 if aln:
192 if aln:
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
193 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 else:
194 else:
195 lines.append('%*d' % (mw, i))
195 lines.append('%*d' % (mw, i))
196 else:
196 else:
197 lines.append('')
197 lines.append('')
198 ls = '\n'.join(lines)
198 ls = '\n'.join(lines)
199 else:
199 else:
200 lines = []
200 lines = []
201 for i in range(fl, fl + lncount):
201 for i in range(fl, fl + lncount):
202 if i % st == 0:
202 if i % st == 0:
203 if aln:
203 if aln:
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
204 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 else:
205 else:
206 lines.append('%*d' % (mw, i))
206 lines.append('%*d' % (mw, i))
207 else:
207 else:
208 lines.append('')
208 lines.append('')
209 ls = '\n'.join(lines)
209 ls = '\n'.join(lines)
210
210
211 # in case you wonder about the seemingly redundant <div> here: since the
211 # in case you wonder about the seemingly redundant <div> here: since the
212 # content in the other cell also is wrapped in a div, some browsers in
212 # content in the other cell also is wrapped in a div, some browsers in
213 # some configurations seem to mess up the formatting...
213 # some configurations seem to mess up the formatting...
214 if nocls:
214 if nocls:
215 yield 0, ('<table class="%stable">' % self.cssclass +
215 yield 0, ('<table class="%stable">' % self.cssclass +
216 '<tr><td><div class="linenodiv" '
216 '<tr><td><div class="linenodiv" '
217 'style="background-color: #f0f0f0; padding-right: 10px">'
217 'style="background-color: #f0f0f0; padding-right: 10px">'
218 '<pre style="line-height: 125%">' +
218 '<pre style="line-height: 125%">' +
219 ls + '</pre></div></td><td id="hlcode" class="code">')
219 ls + '</pre></div></td><td id="hlcode" class="code">')
220 else:
220 else:
221 yield 0, ('<table class="%stable">' % self.cssclass +
221 yield 0, ('<table class="%stable">' % self.cssclass +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
222 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 ls + '</pre></div></td><td id="hlcode" class="code">')
223 ls + '</pre></div></td><td id="hlcode" class="code">')
224 yield 0, dummyoutfile.getvalue()
224 yield 0, dummyoutfile.getvalue()
225 yield 0, '</td></tr></table>'
225 yield 0, '</td></tr></table>'
226
226
227
227
228 def pygmentize(filenode, **kwargs):
228 def pygmentize(filenode, **kwargs):
229 """pygmentize function using pygments
229 """pygmentize function using pygments
230
230
231 :param filenode:
231 :param filenode:
232 """
232 """
233
233
234 return literal(code_highlight(filenode.content,
234 return literal(code_highlight(filenode.content,
235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
235 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236
236
237 def pygmentize_annotation(repo_name, filenode, **kwargs):
237 def pygmentize_annotation(repo_name, filenode, **kwargs):
238 """pygmentize function for annotation
238 """pygmentize function for annotation
239
239
240 :param filenode:
240 :param filenode:
241 """
241 """
242
242
243 color_dict = {}
243 color_dict = {}
244 def gen_color(n=10000):
244 def gen_color(n=10000):
245 """generator for getting n of evenly distributed colors using
245 """generator for getting n of evenly distributed colors using
246 hsv color and golden ratio. It always return same order of colors
246 hsv color and golden ratio. It always return same order of colors
247
247
248 :returns: RGB tuple
248 :returns: RGB tuple
249 """
249 """
250 import colorsys
250 import colorsys
251 golden_ratio = 0.618033988749895
251 golden_ratio = 0.618033988749895
252 h = 0.22717784590367374
252 h = 0.22717784590367374
253
253
254 for _ in xrange(n):
254 for _ in xrange(n):
255 h += golden_ratio
255 h += golden_ratio
256 h %= 1
256 h %= 1
257 HSV_tuple = [h, 0.95, 0.95]
257 HSV_tuple = [h, 0.95, 0.95]
258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
258 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
259 yield map(lambda x:str(int(x * 256)), RGB_tuple)
260
260
261 cgenerator = gen_color()
261 cgenerator = gen_color()
262
262
263 def get_color_string(cs):
263 def get_color_string(cs):
264 if color_dict.has_key(cs):
264 if color_dict.has_key(cs):
265 col = color_dict[cs]
265 col = color_dict[cs]
266 else:
266 else:
267 col = color_dict[cs] = cgenerator.next()
267 col = color_dict[cs] = cgenerator.next()
268 return "color: rgb(%s)! important;" % (', '.join(col))
268 return "color: rgb(%s)! important;" % (', '.join(col))
269
269
270 def url_func(repo_name):
270 def url_func(repo_name):
271
271
272 def _url_func(changeset):
272 def _url_func(changeset):
273 author = changeset.author
273 author = changeset.author
274 date = changeset.date
274 date = changeset.date
275 message = tooltip(changeset.message)
275 message = tooltip(changeset.message)
276
276
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
277 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
278 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
279 "</b> %s<br/></div>")
279 "</b> %s<br/></div>")
280
280
281 tooltip_html = tooltip_html % (author, date, message)
281 tooltip_html = tooltip_html % (author, date, message)
282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
282 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
283 short_id(changeset.raw_id))
283 short_id(changeset.raw_id))
284 uri = link_to(
284 uri = link_to(
285 lnk_format,
285 lnk_format,
286 url('changeset_home', repo_name=repo_name,
286 url('changeset_home', repo_name=repo_name,
287 revision=changeset.raw_id),
287 revision=changeset.raw_id),
288 style=get_color_string(changeset.raw_id),
288 style=get_color_string(changeset.raw_id),
289 class_='tooltip',
289 class_='tooltip',
290 title=tooltip_html
290 title=tooltip_html
291 )
291 )
292
292
293 uri += '\n'
293 uri += '\n'
294 return uri
294 return uri
295 return _url_func
295 return _url_func
296
296
297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
297 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
298
298
299 def get_changeset_safe(repo, rev):
299 def get_changeset_safe(repo, rev):
300 from vcs.backends.base import BaseRepository
300 from vcs.backends.base import BaseRepository
301 from vcs.exceptions import RepositoryError
301 from vcs.exceptions import RepositoryError
302 if not isinstance(repo, BaseRepository):
302 if not isinstance(repo, BaseRepository):
303 raise Exception('You must pass an Repository '
303 raise Exception('You must pass an Repository '
304 'object as first argument got %s', type(repo))
304 'object as first argument got %s', type(repo))
305
305
306 try:
306 try:
307 cs = repo.get_changeset(rev)
307 cs = repo.get_changeset(rev)
308 except RepositoryError:
308 except RepositoryError:
309 from rhodecode.lib.utils import EmptyChangeset
309 from rhodecode.lib.utils import EmptyChangeset
310 cs = EmptyChangeset()
310 cs = EmptyChangeset()
311 return cs
311 return cs
312
312
313
313
314 def is_following_repo(repo_name, user_id):
314 def is_following_repo(repo_name, user_id):
315 from rhodecode.model.scm import ScmModel
315 from rhodecode.model.scm import ScmModel
316 return ScmModel().is_following_repo(repo_name, user_id)
316 return ScmModel().is_following_repo(repo_name, user_id)
317
317
318 flash = _Flash()
318 flash = _Flash()
319
319
320
321 #==============================================================================
320 #==============================================================================
322 # MERCURIAL FILTERS available via h.
321 # SCM FILTERS available via h.
323 #==============================================================================
322 #==============================================================================
324 from mercurial import util
323 from vcs.utils import author_name, author_email
325 from mercurial.templatefilters import person as _person
326 from rhodecode.lib import credentials_hidder, age as _age
324 from rhodecode.lib import credentials_hidder, age as _age
327
325
328
329
330 age = lambda x:_age(x)
326 age = lambda x:_age(x)
331 capitalize = lambda x: x.capitalize()
327 capitalize = lambda x: x.capitalize()
332 email = util.email
328 email = author_email
333 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
329 email_or_none = lambda x: email(x) if email(x) != x else None
334 person = lambda x: _person(x)
330 person = lambda x: author_name(x)
335 short_id = lambda x: x[:12]
331 short_id = lambda x: x[:12]
336 hide_credentials = lambda x: ''.join(credentials_hidder(x))
332 hide_credentials = lambda x: ''.join(credentials_hidder(x))
337
333
338 def bool2icon(value):
334 def bool2icon(value):
339 """Returns True/False values represented as small html image of true/false
335 """Returns True/False values represented as small html image of true/false
340 icons
336 icons
341
337
342 :param value: bool value
338 :param value: bool value
343 """
339 """
344
340
345 if value is True:
341 if value is True:
346 return HTML.tag('img', src=url("/images/icons/accept.png"),
342 return HTML.tag('img', src=url("/images/icons/accept.png"),
347 alt=_('True'))
343 alt=_('True'))
348
344
349 if value is False:
345 if value is False:
350 return HTML.tag('img', src=url("/images/icons/cancel.png"),
346 return HTML.tag('img', src=url("/images/icons/cancel.png"),
351 alt=_('False'))
347 alt=_('False'))
352
348
353 return value
349 return value
354
350
355
351
356 def action_parser(user_log, feed=False):
352 def action_parser(user_log, feed=False):
357 """This helper will action_map the specified string action into translated
353 """This helper will action_map the specified string action into translated
358 fancy names with icons and links
354 fancy names with icons and links
359
355
360 :param user_log: user log instance
356 :param user_log: user log instance
361 :param feed: use output for feeds (no html and fancy icons)
357 :param feed: use output for feeds (no html and fancy icons)
362 """
358 """
363
359
364 action = user_log.action
360 action = user_log.action
365 action_params = ' '
361 action_params = ' '
366
362
367 x = action.split(':')
363 x = action.split(':')
368
364
369 if len(x) > 1:
365 if len(x) > 1:
370 action, action_params = x
366 action, action_params = x
371
367
372 def get_cs_links():
368 def get_cs_links():
373 revs_limit = 5 #display this amount always
369 revs_limit = 5 #display this amount always
374 revs_top_limit = 50 #show upto this amount of changesets hidden
370 revs_top_limit = 50 #show upto this amount of changesets hidden
375 revs = action_params.split(',')
371 revs = action_params.split(',')
376 repo_name = user_log.repository.repo_name
372 repo_name = user_log.repository.repo_name
377
373
378 from rhodecode.model.scm import ScmModel
374 from rhodecode.model.scm import ScmModel
379 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
375 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
380 invalidation_list=[])
376 invalidation_list=[])
381
377
382 message = lambda rev: get_changeset_safe(repo, rev).message
378 message = lambda rev: get_changeset_safe(repo, rev).message
383
379
384 cs_links = " " + ', '.join ([link_to(rev,
380 cs_links = " " + ', '.join ([link_to(rev,
385 url('changeset_home',
381 url('changeset_home',
386 repo_name=repo_name,
382 repo_name=repo_name,
387 revision=rev), title=tooltip(message(rev)),
383 revision=rev), title=tooltip(message(rev)),
388 class_='tooltip') for rev in revs[:revs_limit] ])
384 class_='tooltip') for rev in revs[:revs_limit] ])
389
385
390 compare_view = (' <div class="compare_view tooltip" title="%s">'
386 compare_view = (' <div class="compare_view tooltip" title="%s">'
391 '<a href="%s">%s</a> '
387 '<a href="%s">%s</a> '
392 '</div>' % (_('Show all combined changesets %s->%s' \
388 '</div>' % (_('Show all combined changesets %s->%s' \
393 % (revs[0], revs[-1])),
389 % (revs[0], revs[-1])),
394 url('changeset_home', repo_name=repo_name,
390 url('changeset_home', repo_name=repo_name,
395 revision='%s...%s' % (revs[0], revs[-1])
391 revision='%s...%s' % (revs[0], revs[-1])
396 ),
392 ),
397 _('compare view'))
393 _('compare view'))
398 )
394 )
399
395
400 if len(revs) > revs_limit:
396 if len(revs) > revs_limit:
401 uniq_id = revs[0]
397 uniq_id = revs[0]
402 html_tmpl = ('<span> %s '
398 html_tmpl = ('<span> %s '
403 '<a class="show_more" id="_%s" href="#more">%s</a> '
399 '<a class="show_more" id="_%s" href="#more">%s</a> '
404 '%s</span>')
400 '%s</span>')
405 if not feed:
401 if not feed:
406 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
402 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
407 % (len(revs) - revs_limit),
403 % (len(revs) - revs_limit),
408 _('revisions'))
404 _('revisions'))
409
405
410 if not feed:
406 if not feed:
411 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
407 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
412 else:
408 else:
413 html_tmpl = '<span id="%s"> %s </span>'
409 html_tmpl = '<span id="%s"> %s </span>'
414
410
415 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
411 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
416 url('changeset_home',
412 url('changeset_home',
417 repo_name=repo_name, revision=rev),
413 repo_name=repo_name, revision=rev),
418 title=message(rev), class_='tooltip')
414 title=message(rev), class_='tooltip')
419 for rev in revs[revs_limit:revs_top_limit]]))
415 for rev in revs[revs_limit:revs_top_limit]]))
420 if len(revs) > 1:
416 if len(revs) > 1:
421 cs_links += compare_view
417 cs_links += compare_view
422 return cs_links
418 return cs_links
423
419
424 def get_fork_name():
420 def get_fork_name():
425 repo_name = action_params
421 repo_name = action_params
426 return _('fork name ') + str(link_to(action_params, url('summary_home',
422 return _('fork name ') + str(link_to(action_params, url('summary_home',
427 repo_name=repo_name,)))
423 repo_name=repo_name,)))
428
424
429 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
425 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
430 'user_created_repo':(_('[created] repository'), None),
426 'user_created_repo':(_('[created] repository'), None),
431 'user_forked_repo':(_('[forked] repository'), get_fork_name),
427 'user_forked_repo':(_('[forked] repository'), get_fork_name),
432 'user_updated_repo':(_('[updated] repository'), None),
428 'user_updated_repo':(_('[updated] repository'), None),
433 'admin_deleted_repo':(_('[delete] repository'), None),
429 'admin_deleted_repo':(_('[delete] repository'), None),
434 'admin_created_repo':(_('[created] repository'), None),
430 'admin_created_repo':(_('[created] repository'), None),
435 'admin_forked_repo':(_('[forked] repository'), None),
431 'admin_forked_repo':(_('[forked] repository'), None),
436 'admin_updated_repo':(_('[updated] repository'), None),
432 'admin_updated_repo':(_('[updated] repository'), None),
437 'push':(_('[pushed] into'), get_cs_links),
433 'push':(_('[pushed] into'), get_cs_links),
438 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
434 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
439 'push_remote':(_('[pulled from remote] into'), get_cs_links),
435 'push_remote':(_('[pulled from remote] into'), get_cs_links),
440 'pull':(_('[pulled] from'), None),
436 'pull':(_('[pulled] from'), None),
441 'started_following_repo':(_('[started following] repository'), None),
437 'started_following_repo':(_('[started following] repository'), None),
442 'stopped_following_repo':(_('[stopped following] repository'), None),
438 'stopped_following_repo':(_('[stopped following] repository'), None),
443 }
439 }
444
440
445 action_str = action_map.get(action, action)
441 action_str = action_map.get(action, action)
446 if feed:
442 if feed:
447 action = action_str[0].replace('[', '').replace(']', '')
443 action = action_str[0].replace('[', '').replace(']', '')
448 else:
444 else:
449 action = action_str[0].replace('[', '<span class="journal_highlight">')\
445 action = action_str[0].replace('[', '<span class="journal_highlight">')\
450 .replace(']', '</span>')
446 .replace(']', '</span>')
451
447
452 action_params_func = lambda :""
448 action_params_func = lambda :""
453
449
454 if callable(action_str[1]):
450 if callable(action_str[1]):
455 action_params_func = action_str[1]
451 action_params_func = action_str[1]
456
452
457 return [literal(action), action_params_func]
453 return [literal(action), action_params_func]
458
454
459 def action_parser_icon(user_log):
455 def action_parser_icon(user_log):
460 action = user_log.action
456 action = user_log.action
461 action_params = None
457 action_params = None
462 x = action.split(':')
458 x = action.split(':')
463
459
464 if len(x) > 1:
460 if len(x) > 1:
465 action, action_params = x
461 action, action_params = x
466
462
467 tmpl = """<img src="%s%s" alt="%s"/>"""
463 tmpl = """<img src="%s%s" alt="%s"/>"""
468 map = {'user_deleted_repo':'database_delete.png',
464 map = {'user_deleted_repo':'database_delete.png',
469 'user_created_repo':'database_add.png',
465 'user_created_repo':'database_add.png',
470 'user_forked_repo':'arrow_divide.png',
466 'user_forked_repo':'arrow_divide.png',
471 'user_updated_repo':'database_edit.png',
467 'user_updated_repo':'database_edit.png',
472 'admin_deleted_repo':'database_delete.png',
468 'admin_deleted_repo':'database_delete.png',
473 'admin_created_repo':'database_add.png',
469 'admin_created_repo':'database_add.png',
474 'admin_forked_repo':'arrow_divide.png',
470 'admin_forked_repo':'arrow_divide.png',
475 'admin_updated_repo':'database_edit.png',
471 'admin_updated_repo':'database_edit.png',
476 'push':'script_add.png',
472 'push':'script_add.png',
477 'push_local':'script_edit.png',
473 'push_local':'script_edit.png',
478 'push_remote':'connect.png',
474 'push_remote':'connect.png',
479 'pull':'down_16.png',
475 'pull':'down_16.png',
480 'started_following_repo':'heart_add.png',
476 'started_following_repo':'heart_add.png',
481 'stopped_following_repo':'heart_delete.png',
477 'stopped_following_repo':'heart_delete.png',
482 }
478 }
483 return literal(tmpl % ((url('/images/icons/')),
479 return literal(tmpl % ((url('/images/icons/')),
484 map.get(action, action), action))
480 map.get(action, action), action))
485
481
486
482
487 #==============================================================================
483 #==============================================================================
488 # PERMS
484 # PERMS
489 #==============================================================================
485 #==============================================================================
490 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
486 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
491 HasRepoPermissionAny, HasRepoPermissionAll
487 HasRepoPermissionAny, HasRepoPermissionAll
492
488
493 #==============================================================================
489 #==============================================================================
494 # GRAVATAR URL
490 # GRAVATAR URL
495 #==============================================================================
491 #==============================================================================
496
492
497 def gravatar_url(email_address, size=30):
493 def gravatar_url(email_address, size=30):
498 if not str2bool(config['app_conf'].get('use_gravatar')) or \
494 if not str2bool(config['app_conf'].get('use_gravatar')) or \
499 email_address == 'anonymous@rhodecode.org':
495 email_address == 'anonymous@rhodecode.org':
500 return "/images/user%s.png" % size
496 return "/images/user%s.png" % size
501
497
502 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
498 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
503 default = 'identicon'
499 default = 'identicon'
504 baseurl_nossl = "http://www.gravatar.com/avatar/"
500 baseurl_nossl = "http://www.gravatar.com/avatar/"
505 baseurl_ssl = "https://secure.gravatar.com/avatar/"
501 baseurl_ssl = "https://secure.gravatar.com/avatar/"
506 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
502 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
507
503
508 if isinstance(email_address, unicode):
504 if isinstance(email_address, unicode):
509 #hashlib crashes on unicode items
505 #hashlib crashes on unicode items
510 email_address = email_address.encode('utf8', 'replace')
506 email_address = email_address.encode('utf8', 'replace')
511 # construct the url
507 # construct the url
512 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
508 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
513 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
509 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
514
510
515 return gravatar_url
511 return gravatar_url
516
512
517
513
518 #==============================================================================
514 #==============================================================================
519 # REPO PAGER, PAGER FOR REPOSITORY
515 # REPO PAGER, PAGER FOR REPOSITORY
520 #==============================================================================
516 #==============================================================================
521 class RepoPage(Page):
517 class RepoPage(Page):
522
518
523 def __init__(self, collection, page=1, items_per_page=20,
519 def __init__(self, collection, page=1, items_per_page=20,
524 item_count=None, url=None, branch_name=None, **kwargs):
520 item_count=None, url=None, branch_name=None, **kwargs):
525
521
526 """Create a "RepoPage" instance. special pager for paging
522 """Create a "RepoPage" instance. special pager for paging
527 repository
523 repository
528 """
524 """
529 self._url_generator = url
525 self._url_generator = url
530
526
531 # Safe the kwargs class-wide so they can be used in the pager() method
527 # Safe the kwargs class-wide so they can be used in the pager() method
532 self.kwargs = kwargs
528 self.kwargs = kwargs
533
529
534 # Save a reference to the collection
530 # Save a reference to the collection
535 self.original_collection = collection
531 self.original_collection = collection
536
532
537 self.collection = collection
533 self.collection = collection
538
534
539 # The self.page is the number of the current page.
535 # The self.page is the number of the current page.
540 # The first page has the number 1!
536 # The first page has the number 1!
541 try:
537 try:
542 self.page = int(page) # make it int() if we get it as a string
538 self.page = int(page) # make it int() if we get it as a string
543 except (ValueError, TypeError):
539 except (ValueError, TypeError):
544 self.page = 1
540 self.page = 1
545
541
546 self.items_per_page = items_per_page
542 self.items_per_page = items_per_page
547
543
548 # Unless the user tells us how many items the collections has
544 # Unless the user tells us how many items the collections has
549 # we calculate that ourselves.
545 # we calculate that ourselves.
550 if item_count is not None:
546 if item_count is not None:
551 self.item_count = item_count
547 self.item_count = item_count
552 else:
548 else:
553 self.item_count = len(self.collection)
549 self.item_count = len(self.collection)
554
550
555 # Compute the number of the first and last available page
551 # Compute the number of the first and last available page
556 if self.item_count > 0:
552 if self.item_count > 0:
557 self.first_page = 1
553 self.first_page = 1
558 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
554 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
559 self.last_page = self.first_page + self.page_count - 1
555 self.last_page = self.first_page + self.page_count - 1
560
556
561 # Make sure that the requested page number is the range of valid pages
557 # Make sure that the requested page number is the range of valid pages
562 if self.page > self.last_page:
558 if self.page > self.last_page:
563 self.page = self.last_page
559 self.page = self.last_page
564 elif self.page < self.first_page:
560 elif self.page < self.first_page:
565 self.page = self.first_page
561 self.page = self.first_page
566
562
567 # Note: the number of items on this page can be less than
563 # Note: the number of items on this page can be less than
568 # items_per_page if the last page is not full
564 # items_per_page if the last page is not full
569 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
565 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
570 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
566 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
571
567
572 iterator = self.collection.get_changesets(start=self.first_item,
568 iterator = self.collection.get_changesets(start=self.first_item,
573 end=self.last_item,
569 end=self.last_item,
574 reverse=True,
570 reverse=True,
575 branch_name=branch_name)
571 branch_name=branch_name)
576 self.items = list(iterator)
572 self.items = list(iterator)
577
573
578 # Links to previous and next page
574 # Links to previous and next page
579 if self.page > self.first_page:
575 if self.page > self.first_page:
580 self.previous_page = self.page - 1
576 self.previous_page = self.page - 1
581 else:
577 else:
582 self.previous_page = None
578 self.previous_page = None
583
579
584 if self.page < self.last_page:
580 if self.page < self.last_page:
585 self.next_page = self.page + 1
581 self.next_page = self.page + 1
586 else:
582 else:
587 self.next_page = None
583 self.next_page = None
588
584
589 # No items available
585 # No items available
590 else:
586 else:
591 self.first_page = None
587 self.first_page = None
592 self.page_count = 0
588 self.page_count = 0
593 self.last_page = None
589 self.last_page = None
594 self.first_item = None
590 self.first_item = None
595 self.last_item = None
591 self.last_item = None
596 self.previous_page = None
592 self.previous_page = None
597 self.next_page = None
593 self.next_page = None
598 self.items = []
594 self.items = []
599
595
600 # This is a subclass of the 'list' type. Initialise the list now.
596 # This is a subclass of the 'list' type. Initialise the list now.
601 list.__init__(self, self.items)
597 list.__init__(self, self.items)
602
598
603
599
604 def changed_tooltip(nodes):
600 def changed_tooltip(nodes):
605 """
601 """
606 Generates a html string for changed nodes in changeset page.
602 Generates a html string for changed nodes in changeset page.
607 It limits the output to 30 entries
603 It limits the output to 30 entries
608
604
609 :param nodes: LazyNodesGenerator
605 :param nodes: LazyNodesGenerator
610 """
606 """
611 if nodes:
607 if nodes:
612 pref = ': <br/> '
608 pref = ': <br/> '
613 suf = ''
609 suf = ''
614 if len(nodes) > 30:
610 if len(nodes) > 30:
615 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
611 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
616 return literal(pref + '<br/> '.join([safe_unicode(x.path)
612 return literal(pref + '<br/> '.join([safe_unicode(x.path)
617 for x in nodes[:30]]) + suf)
613 for x in nodes[:30]]) + suf)
618 else:
614 else:
619 return ': ' + _('No Files')
615 return ': ' + _('No Files')
620
616
621
617
622
618
623 def repo_link(groups_and_repos):
619 def repo_link(groups_and_repos):
624 """
620 """
625 Makes a breadcrumbs link to repo within a group
621 Makes a breadcrumbs link to repo within a group
626 joins &raquo; on each group to create a fancy link
622 joins &raquo; on each group to create a fancy link
627
623
628 ex::
624 ex::
629 group >> subgroup >> repo
625 group >> subgroup >> repo
630
626
631 :param groups_and_repos:
627 :param groups_and_repos:
632 """
628 """
633 groups, repo_name = groups_and_repos
629 groups, repo_name = groups_and_repos
634
630
635 if not groups:
631 if not groups:
636 return repo_name
632 return repo_name
637 else:
633 else:
638 def make_link(group):
634 def make_link(group):
639 return link_to(group.group_name, url('repos_group',
635 return link_to(group.group_name, url('repos_group',
640 id=group.group_id))
636 id=group.group_id))
641 return literal(' &raquo; '.join(map(make_link, groups)) + \
637 return literal(' &raquo; '.join(map(make_link, groups)) + \
642 " &raquo; " + repo_name)
638 " &raquo; " + repo_name)
643
639
644
640
645 def fancy_file_stats(stats):
641 def fancy_file_stats(stats):
646 """
642 """
647 Displays a fancy two colored bar for number of added/deleted
643 Displays a fancy two colored bar for number of added/deleted
648 lines of code on file
644 lines of code on file
649
645
650 :param stats: two element list of added/deleted lines of code
646 :param stats: two element list of added/deleted lines of code
651 """
647 """
652
648
653 a, d, t = stats[0], stats[1], stats[0] + stats[1]
649 a, d, t = stats[0], stats[1], stats[0] + stats[1]
654 width = 100
650 width = 100
655 unit = float(width) / (t or 1)
651 unit = float(width) / (t or 1)
656
652
657 # needs > 9% of width to be visible or 0 to be hidden
653 # needs > 9% of width to be visible or 0 to be hidden
658 a_p = max(9, unit * a) if a > 0 else 0
654 a_p = max(9, unit * a) if a > 0 else 0
659 d_p = max(9, unit * d) if d > 0 else 0
655 d_p = max(9, unit * d) if d > 0 else 0
660 p_sum = a_p + d_p
656 p_sum = a_p + d_p
661
657
662 if p_sum > width:
658 if p_sum > width:
663 #adjust the percentage to be == 100% since we adjusted to 9
659 #adjust the percentage to be == 100% since we adjusted to 9
664 if a_p > d_p:
660 if a_p > d_p:
665 a_p = a_p - (p_sum - width)
661 a_p = a_p - (p_sum - width)
666 else:
662 else:
667 d_p = d_p - (p_sum - width)
663 d_p = d_p - (p_sum - width)
668
664
669 a_v = a if a > 0 else ''
665 a_v = a if a > 0 else ''
670 d_v = d if d > 0 else ''
666 d_v = d if d > 0 else ''
671
667
672
668
673 def cgen(l_type):
669 def cgen(l_type):
674 mapping = {'tr':'top-right-rounded-corner',
670 mapping = {'tr':'top-right-rounded-corner',
675 'tl':'top-left-rounded-corner',
671 'tl':'top-left-rounded-corner',
676 'br':'bottom-right-rounded-corner',
672 'br':'bottom-right-rounded-corner',
677 'bl':'bottom-left-rounded-corner'}
673 'bl':'bottom-left-rounded-corner'}
678 map_getter = lambda x:mapping[x]
674 map_getter = lambda x:mapping[x]
679
675
680 if l_type == 'a' and d_v:
676 if l_type == 'a' and d_v:
681 #case when added and deleted are present
677 #case when added and deleted are present
682 return ' '.join(map(map_getter, ['tl', 'bl']))
678 return ' '.join(map(map_getter, ['tl', 'bl']))
683
679
684 if l_type == 'a' and not d_v:
680 if l_type == 'a' and not d_v:
685 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
681 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
686
682
687 if l_type == 'd' and a_v:
683 if l_type == 'd' and a_v:
688 return ' '.join(map(map_getter, ['tr', 'br']))
684 return ' '.join(map(map_getter, ['tr', 'br']))
689
685
690 if l_type == 'd' and not a_v:
686 if l_type == 'd' and not a_v:
691 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
687 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
692
688
693
689
694
690
695 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
691 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
696 a_p, a_v)
692 a_p, a_v)
697 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
693 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
698 d_p, d_v)
694 d_p, d_v)
699 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
695 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
General Comments 0
You need to be logged in to leave comments. Login now