@@ -135,38 +135,39 @@ class ReposController(BaseController):
c.repos_list = Repository.query()\
.order_by(Repository.repo_name)\
.all()
repos_data = []
total_records = len(c.repos_list)
_tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
quick_menu = lambda repo_name: (template.get_def("quick_menu")
.render(repo_name, _=_, h=h))
.render(repo_name, _=_, h=h, c=c))
repo_lnk = lambda name, rtype, private, fork_of: (
template.get_def("repo_name")
.render(name, rtype, private, fork_of, short_name=False,
admin=True, _=_, h=h))
admin=True, _=_, h=h, c=c))
repo_actions = lambda repo_name: (template.get_def("repo_actions")
for repo in c.repos_list:
repos_data.append({
"menu": quick_menu(repo.repo_name),
"raw_name": repo.repo_name,
"name": repo_lnk(repo.repo_name, repo.repo_type, repo.private, repo.fork),
"name": repo_lnk(repo.repo_name, repo.repo_type,
repo.private, repo.fork),
"desc": repo.description,
"owner": repo.user.username,
"action": repo_actions(repo.repo_name),
})
c.data = json.dumps({
"totalRecords": total_records,
"startIndex": 0,
"sort": "name",
"dir": "asc",
"records": repos_data
@@ -36,25 +36,25 @@ from pylons.controllers.util import abor
from pylons.i18n.translation import _
from rhodecode.lib import helpers as h
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
HasPermissionAnyDecorator, NotAnonymous
from rhodecode.lib.base import BaseController, render
from rhodecode.lib.celerylib import tasks, run_task
from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
set_rhodecode_config, repo_name_slug
from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
RhodeCodeSetting, PullRequest, PullRequestReviewers
from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
ApplicationUiSettingsForm
ApplicationUiSettingsForm, ApplicationVisualisationForm
from rhodecode.model.scm import ScmModel
from rhodecode.model.user import UserModel
from rhodecode.model.db import User
from rhodecode.model.notification import EmailNotificationModel
from rhodecode.model.meta import Session
log = logging.getLogger(__name__)
class SettingsController(BaseController):
"""REST Controller styled on the Atom Publishing Protocol"""
# To properly map this controller, ensure your config/routing.py
@@ -134,46 +134,87 @@ class SettingsController(BaseController)
try:
form_result = application_form.to_python(dict(request.POST))
except formencode.Invalid, errors:
return htmlfill.render(
render('admin/settings/settings.html'),
defaults=errors.value,
errors=errors.error_dict or {},
prefix_error=False,
encoding="UTF-8"
)
sett1 = RhodeCodeSetting.get_by_name('title')
sett1 = RhodeCodeSetting.get_by_name_or_create('title')
sett1.app_settings_value = form_result['rhodecode_title']
Session().add(sett1)
sett2 = RhodeCodeSetting.get_by_name('realm')
sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
sett2.app_settings_value = form_result['rhodecode_realm']
Session().add(sett2)
sett3 = RhodeCodeSetting.get_by_name('ga_code')
sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
sett3.app_settings_value = form_result['rhodecode_ga_code']
Session().add(sett3)
Session().commit()
set_rhodecode_config(config)
h.flash(_('Updated application settings'), category='success')
except Exception:
log.error(traceback.format_exc())
h.flash(_('error occurred during updating '
'application settings'),
category='error')
if setting_id == 'visual':
application_form = ApplicationVisualisationForm()()
sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
sett1.app_settings_value = \
form_result['rhodecode_show_public_icon']
sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
sett2.app_settings_value = \
form_result['rhodecode_show_private_icon']
sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
sett3.app_settings_value = \
form_result['rhodecode_stylify_metatags']
h.flash(_('Updated visualisation settings'),
category='success')
'visualisation settings'),
if setting_id == 'vcs':
application_form = ApplicationUiSettingsForm()()
@@ -69,33 +69,33 @@ class UsersController(BaseController):
"""GET /users: All items in the collection"""
# url('users')
c.users_list = User.query().order_by(User.username).all()
users_data = []
total_records = len(c.users_list)
grav_tmpl = lambda user_email, size: (
template.get_def("user_gravatar")
.render(user_email, size, _=_, h=h))
.render(user_email, size, _=_, h=h, c=c))
user_lnk = lambda user_id, username: (
template.get_def("user_name")
.render(user_id, username, _=_, h=h))
.render(user_id, username, _=_, h=h, c=c))
user_actions = lambda user_id, username: (
template.get_def("user_actions")
for user in c.users_list:
users_data.append({
"gravatar": grav_tmpl(user. email, 24),
"raw_username": user.username,
"username": user_lnk(user.user_id, user.username),
"firstname": user.name,
"lastname": user.lastname,
"last_login": h.fmt_date(user.last_login),
"active": h.bool2icon(user.active),
"admin": h.bool2icon(user.admin),
"ldap": h.bool2icon(bool(user.ldap_dn)),
@@ -8,25 +8,25 @@ import traceback
from paste.auth.basic import AuthBasicAuthenticator
from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
from paste.httpheaders import WWW_AUTHENTICATE
from pylons import config, tmpl_context as c, request, session, url
from pylons.controllers import WSGIController
from pylons.controllers.util import redirect
from pylons.templating import render_mako as render
from rhodecode import __version__, BACKENDS
from rhodecode.lib.utils2 import str2bool, safe_unicode
from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict
from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
HasPermissionAnyMiddleware, CookieStoreWrapper
from rhodecode.lib.utils import get_repo_slug, invalidate_cache
from rhodecode.model import meta
from rhodecode.model.db import Repository, RhodeCodeUi
from rhodecode.model.notification import NotificationModel
@@ -149,44 +149,50 @@ class BaseVCSController(object):
"""
Checks the SSL check flag and returns False if SSL is not present
and required True otherwise
org_proto = environ['wsgi._org_proto']
#check if we have SSL required ! if not it's a bad request !
require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl')\
.scalar().ui_value)
if require_ssl and org_proto == 'http':
log.debug('proto is %s and SSL is required BAD REQUEST !'
% org_proto)
return False
return True
def __call__(self, environ, start_response):
start = time.time()
return self._handle_request(environ, start_response)
finally:
log = logging.getLogger('rhodecode.' + self.__class__.__name__)
log.debug('Request time: %.3fs' % (time.time() - start))
meta.Session.remove()
class BaseController(WSGIController):
def __before__(self):
c.rhodecode_version = __version__
c.rhodecode_instanceid = config.get('instance_id')
c.rhodecode_name = config.get('rhodecode_title')
c.use_gravatar = str2bool(config.get('use_gravatar'))
c.ga_code = config.get('rhodecode_ga_code')
# Visual options
c.visual = AttributeDict({})
c.visual.show_public_icon = str2bool(config.get('rhodecode_show_public_icon'))
c.visual.show_private_icon = str2bool(config.get('rhodecode_show_private_icon'))
c.visual.stylify_metatags = str2bool(config.get('rhodecode_stylify_metatags'))
c.repo_name = get_repo_slug(request)
c.backends = BACKENDS.keys()
c.unread_notifications = NotificationModel()\
.get_unread_cnt_for_user(c.rhodecode_user.user_id)
self.cut_off_limit = int(config.get('cut_off_limit'))
self.sa = meta.Session
self.scm_model = ScmModel(self.sa)
self.ip_addr = ''
"""Invoke the Controller"""
"""Helper functions
Consists of functions to typically be used within templates, but also
available to Controllers. This module is available to both as 'h'.
import random
import hashlib
import StringIO
import urllib
import math
import logging
import re
from datetime import datetime
from pygments.formatters.html import HtmlFormatter
from pygments import highlight as code_highlight
from pylons import url, request, config
from pylons.i18n.translation import _, ungettext
from hashlib import md5
from webhelpers.html import literal, HTML, escape
from webhelpers.html.tools import *
from webhelpers.html.builder import make_tag
from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
@@ -421,24 +422,44 @@ def person(author):
# Maybe it's a username?
_author = author_name(author)
user = User.get_by_username(_author, case_insensitive=True,
cache=True)
if user is not None:
return person_getter(user)
# Still nothing? Just pass back the author name then
return _author
def desc_stylize(value):
converts tags from value into html equivalent
:param value:
value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
'<div class="metatag" tag="see">see => \\1 </div>', value)
value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
'<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
'<div class="metatag" tag="\\1">\\1 => <a href="/\\2">\\2</a></div>', value)
value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
'<div class="metatag" tag="lang">\\2</div>', value)
value = re.sub(r'\[([a-z]+)\]',
'<div class="metatag" tag="\\1">\\1</div>', value)
return value
def bool2icon(value):
"""Returns True/False values represented as small html image of true/false
icons
:param value: bool value
if value is True:
return HTML.tag('img', src=url("/images/icons/accept.png"),
alt=_('True'))
if value is False:
@@ -434,12 +434,18 @@ MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9
def extract_mentioned_users(s):
Returns unique usernames from given string s that have @mention
:param s: string to get mentions
usrs = set()
for username in re.findall(MENTIONS_REGEX, s):
usrs.add(username)
return sorted(list(usrs), key=lambda k: k.lower())
class AttributeDict(dict):
def __getattr__(self, attr):
return self.get(attr, None)
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
@@ -173,27 +173,34 @@ class RhodeCodeSetting(Base, BaseModel):
:param val:
self._app_settings_value = safe_unicode(val)
def __unicode__(self):
return u"<%s('%s:%s')>" % (
self.__class__.__name__,
self.app_settings_name, self.app_settings_value
@classmethod
def get_by_name(cls, ldap_key):
def get_by_name(cls, key):
return cls.query()\
.filter(cls.app_settings_name == ldap_key).scalar()
.filter(cls.app_settings_name == key).scalar()
def get_by_name_or_create(cls, key):
res = cls.get_by_name(key)
if not res:
res = cls(key)
return res
def get_app_settings(cls, cache=False):
ret = cls.query()
if cache:
ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
if not ret:
raise Exception('Could not get application settings !')
settings = {}
@@ -580,26 +587,26 @@ class Repository(Base, BaseModel):
landing_rev = Column("landing_revision", String(255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default=None)
fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
user = relationship('User')
fork = relationship('Repository', remote_side=repo_id)
group = relationship('RepoGroup')
repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
stats = relationship('Statistics', cascade='all', uselist=False)
followers = relationship('UserFollowing',
primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id',
cascade='all')
logs = relationship('UserLog')
comments = relationship('ChangesetComment', cascade="all, delete, delete-orphan")
pull_requests_org = relationship('PullRequest',
primaryjoin='PullRequest.org_repo_id==Repository.repo_id',
cascade="all, delete, delete-orphan")
pull_requests_other = relationship('PullRequest',
primaryjoin='PullRequest.other_repo_id==Repository.repo_id',
@@ -1538,25 +1545,25 @@ class PullRequest(Base, BaseModel):
other_repo_id = Column('other_repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
other_ref = Column('other_ref', Unicode(256), nullable=False)
@hybrid_property
def revisions(self):
return self._revisions.split(':')
@revisions.setter
def revisions(self, val):
self._revisions = ':'.join(val)
author = relationship('User', lazy='joined')
reviewers = relationship('PullRequestReviewers',
org_repo = relationship('Repository', primaryjoin='PullRequest.org_repo_id==Repository.repo_id')
other_repo = relationship('Repository', primaryjoin='PullRequest.other_repo_id==Repository.repo_id')
statuses = relationship('ChangesetStatus')
comments = relationship('ChangesetComment',
def is_closed(self):
return self.status == self.STATUS_CLOSED
def __json__(self):
return dict(
@@ -233,51 +233,58 @@ def RepoSettingsForm(edit=False, old_dat
def ApplicationSettingsForm():
class _ApplicationSettingsForm(formencode.Schema):
allow_extra_fields = True
filter_extra_fields = False
rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True)
rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True)
rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False)
return _ApplicationSettingsForm
def ApplicationVisualisationForm():
class _ApplicationVisualisationForm(formencode.Schema):
rhodecode_show_public_icon = v.StringBoolean(if_missing=False)
rhodecode_show_private_icon = v.StringBoolean(if_missing=False)
rhodecode_stylify_metatags = v.StringBoolean(if_missing=False)
return _ApplicationVisualisationForm
def ApplicationUiSettingsForm():
class _ApplicationUiSettingsForm(formencode.Schema):
web_push_ssl = v.OneOf(['true', 'false'], if_missing='false')
web_push_ssl = v.StringBoolean(if_missing=False)
paths_root_path = All(
v.ValidPath(),
v.UnicodeString(strip=True, min=1, not_empty=True)
hooks_changegroup_update = v.OneOf(['True', 'False'],
if_missing=False)
hooks_changegroup_repo_size = v.OneOf(['True', 'False'],
hooks_changegroup_push_logger = v.OneOf(['True', 'False'],
hooks_preoutgoing_pull_logger = v.OneOf(['True', 'False'],
hooks_changegroup_update = v.StringBoolean(if_missing=False)
hooks_changegroup_repo_size = v.StringBoolean(if_missing=False)
hooks_changegroup_push_logger = v.StringBoolean(if_missing=False)
hooks_preoutgoing_pull_logger = v.StringBoolean(if_missing=False)
return _ApplicationUiSettingsForm
def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
class _DefaultPermissionsForm(formencode.Schema):
filter_extra_fields = True
overwrite_default = v.StringBoolean(if_missing=False)
anonymous = v.OneOf(['True', 'False'], if_missing=False)
anonymous = v.StringBoolean(if_missing=False)
default_perm = v.OneOf(perms_choices)
default_register = v.OneOf(register_choices)
default_create = v.OneOf(create_choices)
return _DefaultPermissionsForm
def LdapSettingsForm(tls_reqcert_choices, search_scope_choices,
tls_kind_choices):
class _LdapSettingsForm(formencode.Schema):
@@ -1818,24 +1818,99 @@ div.form div.fields div.field div.button
border: none;
padding: 2px 3px 3px;
}
#content div.box div.traffic table td.legendLabel {
padding: 0 3px 2px;
#summary {
#summary .metatag {
display: inline-block;
padding: 3px 5px;
margin-bottom: 3px;
margin-right: 1px;
border-radius: 5px;
#content div.box #summary p {
margin-bottom: -5px;
width: 600px;
white-space: pre-wrap;
#content div.box #summary p:last-child {
margin-bottom: 9px;
#content div.box #summary p:first-of-type {
margin-top: 9px;
.metatag {
-webkit-border-radius: 4px 4px 4px 4px;
-khtml-border-radius: 4px 4px 4px 4px;
-moz-border-radius: 4px 4px 4px 4px;
border-radius: 4px 4px 4px 4px;
border: solid 1px #9CF;
padding: 2px 3px 2px 3px !important;
background-color: #DEF;
.metatag[tag="dead"] {
background-color: #E44;
.metatag[tag="stale"] {
background-color: #EA4;
.metatag[tag="featured"] {
background-color: #AEA;
.metatag[tag="requires"] {
background-color: #9CF;
.metatag[tag="recommends"] {
background-color: #BDF;
.metatag[tag="lang"] {
background-color: #FAF474;
.metatag[tag="license"] {
target-new: tab !important;
.metatag[tag="see"] {
border: solid 1px #CBD;
background-color: #EDF;
a.metatag[tag="license"]:hover {
background-color: #003367;
color: #FFF;
text-decoration: none;
#summary .desc {
white-space: pre;
width: 100%;
#summary .repo_name {
font-size: 1.6em;
font-weight: bold;
vertical-align: baseline;
clear: right
@@ -107,24 +107,81 @@
${h.text('rhodecode_ga_code',size=30)}
</div>
<div class="buttons">
${h.submit('save',_('Save settings'),class_="ui-btn large")}
${h.reset('reset',_('Reset'),class_="ui-btn large")}
${h.end_form()}
<h3>${_('Visualisation settings')}</h3>
${h.form(url('admin_setting', setting_id='visual'),method='put')}
<div class="form">
<!-- fields -->
<div class="fields">
<div class="field">
<div class="label label-checkbox">
<label>${_('Icons')}:</label>
<div class="checkboxes">
<div class="checkbox">
${h.checkbox('rhodecode_show_public_icon','True')}
<label for="rhodecode_show_public_icon">${_('Show public repo icon on repositories')}</label>
${h.checkbox('rhodecode_show_private_icon','True')}
<label for="rhodecode_show_private_icon">${_('Show private repo icon on repositories')}</label>
<label>${_('Meta-Tagging')}:</label>
${h.checkbox('rhodecode_stylify_metatags','True')}
<label for="rhodecode_stylify_metatags">${_('Stylify recognised metatags:')}</label>
<div style="padding-left: 20px;">
<ul> <!-- Fix style here -->
<li>[featured] <span class="metatag" tag="featured">featured</span></li>
<li>[stale] <span class="metatag" tag="stale">stale</span></li>
<li>[dead] <span class="metatag" tag="dead">dead</span></li>
<li>[lang => lang] <span class="metatag" tag="lang" >lang</span></li>
<li>[license => License] <span class="metatag" tag="license"><a href="http://www.opensource.org/licenses/License" >License</a></span></li>
<li>[requires => Repo] <span class="metatag" tag="requires" >requires => <a href="#" >Repo</a></span></li>
<li>[recommends => Repo] <span class="metatag" tag="recommends" >recommends => <a href="#" >Repo</a></span></li>
<li>[see => URI] <span class="metatag" tag="see">see => <a href="#">URI</a> </span></li>
</ul>
<h3>${_('VCS settings')}</h3>
${h.form(url('admin_setting', setting_id='vcs'),method='put')}
<label>${_('Web')}:</label>
@@ -54,28 +54,28 @@
else:
return name
%>
<div style="white-space: nowrap">
##TYPE OF REPO
%if h.is_hg(rtype):
<img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
%elif h.is_git(rtype):
<img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
%endif
##PRIVATE/PUBLIC
%if private:
<img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
%else:
<img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
%if private and c.visual.show_private_icon:
%elif not private and c.visual.show_public_icon:
##NAME
%if admin:
${h.link_to(get_name(name),h.url('edit_repo',repo_name=name),class_="repo_name")}
${h.link_to(get_name(name),h.url('summary_home',repo_name=name),class_="repo_name")}
%if fork_of:
<a href="${h.url('summary_home',repo_name=fork_of)}">
<img class="icon" alt="${_('fork')}" title="${_('Fork of')} ${fork_of}" src="${h.url('/images/icons/arrow_divide.png')}"/></a>
@@ -99,13 +99,12 @@
</%def>
<%def name="user_actions(user_id, username)">
${h.form(h.url('delete_user', id=user_id),method='delete')}
${h.submit('remove_',_('delete'),id="remove_user_%s" % user_id,
class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
<%def name="user_name(user_id, username)">
${h.link_to(username,h.url('edit_user', id=user_id))}
@@ -32,25 +32,29 @@
</tr>
</thead>
## REPO GROUPS
% for gr in c.groups:
<tr>
<td>
<img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
</td>
<td>${gr.group_description}</td>
%if c.visual.stylify_metatags:
<td>${h.desc_stylize(gr.group_description)}</td>
## this is commented out since for multi nested repos can be HEAVY!
## in number of executed queries during traversing uncomment at will
##<td><b>${gr.repositories_recursive_count}</b></td>
% endfor
</table>
<div style="height: 20px"></div>
% endif
<div id="welcome" style="display:none;text-align:center">
<h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
@@ -76,25 +80,29 @@
%for cnt,repo in enumerate(c.repos_list):
<tr class="parity${(cnt+1)%2}">
##QUICK MENU
<td class="quick_repo_menu">
${dt.quick_menu(repo['name'])}
##REPO NAME AND ICONS
<td class="reponame">
${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'),pageargs.get('short_repo_names'))}
##DESCRIPTION
<td><span class="tooltip" title="${h.tooltip(repo['description'])}">
${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))}</span>
${h.truncate(repo['description'],60)}</span>
##LAST CHANGE DATE
<span class="tooltip" date="${repo['last_change']}" title="${h.tooltip(h.fmt_date(repo['last_change']))}">${h.age(repo['last_change'])}</span>
##LAST REVISION
${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
##
<td title="${repo['contact']}">${h.person(repo['contact'])}</td>
@@ -119,27 +119,27 @@
<div style="float:right;padding-right:5px">
<span id="follow_toggle_${entry.follows_repository.repo_id}" class="following" title="${_('Stop following this repository')}"
onclick="javascript:toggleFollowingRepo(this,${entry.follows_repository.repo_id},'${str(h.get_token())}')">
</span>
%if h.is_hg(entry.follows_repository):
%elif h.is_git(entry.follows_repository):
%if entry.follows_repository.private:
%if entry.follows_repository.private and c.visual.show_private_icon:
%elif not entry.follows_repository.private and c.visual.show_public_icon:
<span class="watched_repo">
${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
%endfor
</tbody>
## -*- coding: utf-8 -*-
<li class="qfilter_rs">
<input type="text" style="border:0;width:100%" value="${_('quick filter...')}" name="filter" id="q_filter_rs" />
</li>
%for repo in c.repos_list:
%if repo['dbrepo']['private']:
%if repo['dbrepo']['private'] and c.visual.show_private_icon:
<li>
<img src="${h.url('/images/icons/lock.png')}" alt="${_('Private repository')}" class="repo_switcher_type"/>
${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])}
%elif not repo['dbrepo']['private'] and c.visual.show_public_icon:
<img src="${h.url('/images/icons/lock_open.png')}" alt="${_('Public repository')}" class="repo_switcher_type" />
@@ -95,25 +95,29 @@
<a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}"><img class="icon" alt="${_('remote clone')}" title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}" src="${h.url('/images/icons/connect.png')}"/>
${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
</a>
<div class="label-summary">
<label>${_('Description')}:</label>
<div class="input ${summary(c.show_stats)} desc">${h.urlify_text(c.dbrepo.description)}</div>
<div class="input ${summary(c.show_stats)} desc">${h.urlify_text(h.desc_stylize(c.dbrepo.description))}</div>
<label>${_('Contact')}:</label>
<div class="input ${summary(c.show_stats)}">
<div class="gravatar">
<img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
${_('Username')}: ${c.dbrepo.user.username}<br/>
${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
@@ -122,12 +122,28 @@ class TestLibs(unittest.TestCase):
from rhodecode.lib.utils2 import age
n = datetime.datetime.now()
delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs)
self.assertEqual(age(n), u'just now')
self.assertEqual(age(n - delt(seconds=1)), u'1 second ago')
self.assertEqual(age(n - delt(seconds=60 * 2)), u'2 minutes ago')
self.assertEqual(age(n - delt(hours=1)), u'1 hour ago')
self.assertEqual(age(n - delt(hours=24)), u'1 day ago')
self.assertEqual(age(n - delt(hours=24 * 5)), u'5 days ago')
self.assertEqual(age(n - delt(hours=24 * (calendar.mdays[n.month-1] + 2))),
u'1 month and 2 days ago')
self.assertEqual(age(n - delt(hours=24 * 400)), u'1 year and 1 month ago')
def test_tag_exctrator(self):
sample = (
"hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]"
"[requires] [stale] [see<>=>] [see => http://url.com]"
"[requires => url] [lang => python] [just a tag]"
"[,d] [ => ULR ] [obsolete] [desc]]"
from rhodecode.lib.helpers import desc_stylize
res = desc_stylize(sample)
self.assertTrue('<div class="metatag" tag="tag">tag</div>' in res)
self.assertTrue('<div class="metatag" tag="obsolete">obsolete</div>' in res)
self.assertTrue('<div class="metatag" tag="stale">stale</div>' in res)
self.assertTrue('<div class="metatag" tag="lang">python</div>' in res)
self.assertTrue('<div class="metatag" tag="requires">requires => <a href="/url">url</a></div>' in res)
Status change: