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