# -*- coding: utf-8 -*-
"""
rhodecode.controllers.admin.admin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Controller for Admin panel of Rhodecode
:created_on: Apr 7, 2010
:author: marcink
:copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
:license: GPLv3, see COPYING for more details.
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
from pylons import request, tmpl_context as c, url
from sqlalchemy.orm import joinedload
from webhelpers.paginate import Page
from whoosh.qparser.default import QueryParser
from whoosh import query
from sqlalchemy.sql.expression import or_
from sqlalchemy.sql.expression import or_, and_
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
from rhodecode.lib.base import BaseController, render
from rhodecode.model.db import UserLog, User
from rhodecode.lib.utils2 import safe_int, remove_prefix, remove_suffix
from rhodecode.lib.indexers import JOURNAL_SCHEMA
from whoosh.qparser.dateparse import DateParserPlugin
log = logging.getLogger(__name__)
def _filter(user_log, search_term):
def _journal_filter(user_log, search_term):
Filters sqlalchemy user_log based on search_term with whoosh Query language
http://packages.python.org/Whoosh/querylang.html
:param user_log:
:param search_term:
log.debug('Initial search term: %r' % search_term)
qry = None
if search_term:
qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
qp.add_plugin(DateParserPlugin())
qry = qp.parse(unicode(search_term))
log.debug('Filtering using parsed query %r' % qry)
def wildcard_handler(col, wc_term):
if wc_term.startswith('*') and not wc_term.endswith('*'):
#postfix == endswith
wc_term = remove_prefix(wc_term, prefix='*')
return getattr(col, 'endswith')(wc_term)
elif wc_term.startswith('*') and wc_term.endswith('*'):
#wildcard == ilike
wc_term = remove_suffix(wc_term, suffix='*')
return getattr(col, 'contains')(wc_term)
def get_filterion(field, val, term):
if field == 'repository':
field = getattr(UserLog, 'repository_name')
elif field == 'ip':
field = getattr(UserLog, 'user_ip')
elif field == 'date':
field = getattr(UserLog, 'action_date')
elif field == 'username':
field = getattr(UserLog, 'username')
else:
field = getattr(UserLog, field)
log.debug('filter field: %s val=>%s' % (field, val))
#sql filtering
if isinstance(term, query.Wildcard):
return wildcard_handler(field, val)
elif isinstance(term, query.Prefix):
return field.startswith(val)
elif isinstance(term, query.DateRange):
return and_(field >= val[0], field <= val[1])
return field == val
if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard)):
if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard,
query.DateRange)):
if not isinstance(qry, query.And):
qry = [qry]
for term in qry:
field = term.fieldname
val = term.text
val = (term.text if not isinstance(term, query.DateRange)
else [term.startdate, term.enddate])
user_log = user_log.filter(get_filterion(field, val, term))
elif isinstance(qry, query.Or):
filters = []
filters.append(get_filterion(field, val, term))
user_log = user_log.filter(or_(*filters))
return user_log
class AdminController(BaseController):
@LoginRequired()
def __before__(self):
super(AdminController, self).__before__()
@HasPermissionAllDecorator('hg.admin')
def index(self):
users_log = UserLog.query()\
.options(joinedload(UserLog.user))\
.options(joinedload(UserLog.repository))
#FILTERING
c.search_term = request.GET.get('filter')
try:
users_log = _filter(users_log, c.search_term)
users_log = _journal_filter(users_log, c.search_term)
except:
# we want this to crash for now
raise
users_log = users_log.order_by(UserLog.action_date.desc())
p = safe_int(request.params.get('page', 1), 1)
def url_generator(**kw):
return url.current(filter=c.search_term, **kw)
c.users_log = Page(users_log, page=p, items_per_page=10, url=url_generator)
c.log_data = render('admin/admin_log.html')
if request.environ.get('HTTP_X_PARTIAL_XHR'):
return c.log_data
return render('admin/admin.html')
rhodecode.controllers.journal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Journal controller for pylons
:created_on: Nov 21, 2010
from itertools import groupby
from sqlalchemy import or_
from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
from webob.exc import HTTPBadRequest
from pylons import request, tmpl_context as c, response, url
from pylons.i18n.translation import _
import rhodecode.lib.helpers as h
from rhodecode.lib.auth import LoginRequired, NotAnonymous
from rhodecode.model.db import UserLog, UserFollowing, Repository, User
from rhodecode.model.meta import Session
from sqlalchemy.sql.expression import func
from rhodecode.model.scm import ScmModel
from rhodecode.lib.utils2 import safe_int
from rhodecode.controllers.admin.admin import _journal_filter
class JournalController(BaseController):
super(JournalController, self).__before__()
self.language = 'en-us'
self.ttl = "5"
self.feed_nr = 20
@NotAnonymous()
# Return a rendered template
c.user = User.get(self.rhodecode_user.user_id)
c.following = self.sa.query(UserFollowing)\
.filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
.options(joinedload(UserFollowing.follows_repository))\
.all()
journal = self._get_journal_data(c.following)
c.journal_pager = Page(journal, page=p, items_per_page=20)
c.journal_pager = Page(journal, page=p, items_per_page=20, url=url_generator)
c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
c.journal_data = render('journal/journal_data.html')
return c.journal_data
return render('journal/journal.html')
def index_my_repos(self):
all_repos = self.sa.query(Repository)\
.filter(Repository.user_id == c.user.user_id)\
.order_by(func.lower(Repository.repo_name)).all()
c.user_repos = ScmModel().get_repos(all_repos)
return render('journal/journal_page_repos.html')
@LoginRequired(api_access=True)
def journal_atom(self):
Produce an atom-1.0 feed via feedgenerator module
following = self.sa.query(UserFollowing)\
return self._atom_feed(following, public=False)
def journal_rss(self):
Produce an rss feed via feedgenerator module
return self._rss_feed(following, public=False)
def _get_daily_aggregate(self, journal):
groups = []
for k, g in groupby(journal, lambda x: x.action_as_day):
user_group = []
#groupby username if it's a present value, else fallback to journal username
for _, g2 in groupby(list(g), lambda x: x.user.username if x.user else x.username):
l = list(g2)
user_group.append((l[0].user, l))
groups.append((k, user_group,))
return groups
def _get_journal_data(self, following_repos):
repo_ids = [x.follows_repository.repo_id for x in following_repos
if x.follows_repository is not None]
user_ids = [x.follows_user.user_id for x in following_repos
if x.follows_user is not None]
filtering_criterion = None
if repo_ids and user_ids:
filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
UserLog.user_id.in_(user_ids))
if repo_ids and not user_ids:
filtering_criterion = UserLog.repository_id.in_(repo_ids)
if not repo_ids and user_ids:
filtering_criterion = UserLog.user_id.in_(user_ids)
if filtering_criterion is not None:
journal = self.sa.query(UserLog)\
.options(joinedload(UserLog.repository))\
.filter(filtering_criterion)\
.order_by(UserLog.action_date.desc())
#filter
journal = _journal_filter(journal, c.search_term)
journal = journal.filter(filtering_criterion)\
journal = []
return journal
def toggle_following(self):
cur_token = request.POST.get('auth_token')
token = h.get_token()
if cur_token == token:
user_id = request.POST.get('follows_user_id')
if user_id:
self.scm_model.toggle_following_user(user_id,
self.rhodecode_user.user_id)
Session.commit()
return 'ok'
raise HTTPBadRequest()
repo_id = request.POST.get('follows_repo_id')
if repo_id:
self.scm_model.toggle_following_repo(repo_id,
log.debug('token mismatch %s vs %s' % (cur_token, token))
def public_journal(self):
return render('journal/public_journal.html')
def _atom_feed(self, repos, public=True):
journal = self._get_journal_data(repos)
if public:
_link = url('public_journal_atom', qualified=True)
_desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
'atom feed')
_link = url('journal_atom', qualified=True)
_desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
feed = Atom1Feed(title=_desc,
link=_link,
description=_desc,
language=self.language,
ttl=self.ttl)
for entry in journal[:self.feed_nr]:
action, action_extra, ico = h.action_parser(entry, feed=True)
title = "%s - %s %s" % (entry.user.short_contact, action(),
entry.repository.repo_name)
desc = action_extra()
_url = None
if entry.repository is not None:
_url = url('changelog_home',
repo_name=entry.repository.repo_name,
qualified=True)
feed.add_item(title=title,
pubdate=entry.action_date,
link=_url or url('', qualified=True),
author_email=entry.user.email,
author_name=entry.user.full_contact,
description=desc)
response.content_type = feed.mime_type
return feed.writeString('utf-8')
def _rss_feed(self, repos, public=True):
"""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 re
import urlparse
import textwrap
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, \
end_form, file, form, hidden, image, javascript_link, link_to, \
link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
submit, text, password, textarea, title, ul, xml_declaration, radio
from webhelpers.html.tools import auto_link, button_to, highlight, \
js_obfuscate, mail_to, strip_links, strip_tags, tag_re
from webhelpers.number import format_byte_size, format_bit_size
from webhelpers.pylonslib import Flash as _Flash
from webhelpers.pylonslib.secure_form import secure_form
from webhelpers.text import chop_at, collapse, convert_accented_entities, \
convert_misc_entities, lchop, plural, rchop, remove_formatting, \
replace_whitespace, urlify, truncate, wrap_paragraphs
from webhelpers.date import time_ago_in_words
from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
convert_boolean_attrs, NotGiven, _make_safe_id_component
from rhodecode.lib.annotate import annotate_highlight
from rhodecode.lib.utils import repo_name_slug
from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
from rhodecode.lib.markup_renderer import MarkupRenderer
from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
from rhodecode.model.changeset_status import ChangesetStatusModel
from rhodecode.model.db import URL_SEP, Permission
html_escape_table = {
"&": "&",
'"': """,
"'": "'",
">": ">",
"<": "<",
}
def html_escape(text):
"""Produce entities within text."""
return "".join(html_escape_table.get(c, c) for c in text)
def shorter(text, size=20):
postfix = '...'
if len(text) > size:
return text[:size - len(postfix)] + postfix
return text
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
Reset button
_set_input_attrs(attrs, type, name, value)
_set_id_attr(attrs, id, name)
convert_boolean_attrs(attrs, ["disabled"])
return HTML.input(**attrs)
reset = _reset
safeid = _make_safe_id_component
def FID(raw_id, path):
Creates a uniqe ID for filenode based on it's hash of path and revision
it's safe to use in urls
:param raw_id:
:param path:
return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
def get_token():
"""Return the current authentication token, creating one if one doesn't
already exist.
token_key = "_authentication_token"
from pylons import session
if not token_key in session:
@@ -1042,96 +1043,116 @@ def urlify_commit(text_, repository=None
links.append(e)
return ''.join(links)
# urlify changesets - extrac revisions and make link out of them
newtext = urlify_changesets(escaper(text_), repository)
conf = config['app_conf']
# allow multiple issue servers to be used
valid_indices = [
x.group(1)
for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
if x and 'issue_server_link%s' % x.group(1) in conf
and 'issue_prefix%s' % x.group(1) in conf
]
log.debug('found issue server suffixes `%s` during valuation of: %s'
% (','.join(valid_indices), newtext))
for pattern_index in valid_indices:
ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
% (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
ISSUE_PREFIX))
URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
def url_func(match_obj):
pref = ''
if match_obj.group().startswith(' '):
pref = ' '
issue_id = ''.join(match_obj.groups())
tmpl = (
'%(pref)s<a class="%(cls)s" href="%(url)s">'
'%(issue-prefix)s%(id-repr)s'
'</a>'
)
url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
if repository:
url = url.replace('{repo}', repository)
repo_name = repository.split(URL_SEP)[-1]
url = url.replace('{repo_name}', repo_name)
return tmpl % {
'pref': pref,
'cls': 'issue-tracker-link',
'url': url,
'id-repr': issue_id,
'issue-prefix': ISSUE_PREFIX,
'serv': ISSUE_SERVER_LNK,
newtext = URL_PAT.sub(url_func, newtext)
log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
# if we actually did something above
if link_:
# wrap not links into final link => link_
newtext = linkify_others(newtext, link_)
log.error(traceback.format_exc())
pass
return literal(newtext)
def rst(source):
return literal('<div class="rst-block">%s</div>' %
MarkupRenderer.rst(source))
def rst_w_mentions(source):
Wrapped rst renderer with @mention highlighting
:param source:
MarkupRenderer.rst_with_mentions(source))
def changeset_status(repo, revision):
return ChangesetStatusModel().get_status(repo, revision)
def changeset_status_lbl(changeset_status):
return dict(ChangesetStatus.STATUSES).get(changeset_status)
def get_permission_name(key):
return dict(Permission.PERMS).get(key)
def journal_filter_help():
return _(textwrap.dedent('''
Example filter terms:
repository:vcs
username:marcin
action:*push*
ip:127.0.0.1
date:20120101
date:[20120101100000 TO 20120102]
Generate wildcards using '*' character:
"repositroy:vcs*" - search everything starting with 'vcs'
"repository:*vcs*" - search for repository containing 'vcs'
Optional AND / OR operators in queries
"repository:vcs OR repository:test"
"username:test AND repository:test*"
'''))
## -*- coding: utf-8 -*-
<%inherit file="/base/base.html"/>
<%def name="title()">
${_('Admin journal')} - ${c.rhodecode_name}
</%def>
<%def name="breadcrumbs_links()">
<form id="filter_form">
<input class="q_filter_box ${'' if c.search_term else 'initial'}" id="q_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
<span class="tooltip" title="${h.tooltip(_('''
Example search query:
"repository:vcs"
"username:marcin"
You can use wildcards using '*'
Use AND / OR operators in queries
List of valid search filters:
repository:
username:
action:
ip:
date:
'''))}">?</span>
<input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('journal filter...')}"/>
<span class="tooltip" title="${h.tooltip(h.journal_filter_help())}">?</span>
<input type='submit' value="${_('filter')}" class="ui-btn" style="padding:0px 2px 0px 2px;margin:0px"/>
${_('Admin journal')} - ${ungettext('%s entry', '%s entries', c.users_log.item_count) % (c.users_log.item_count)}
</form>
${h.end_form()}
<%def name="page_nav()">
${self.menu('admin')}
<%def name="main()">
<div class="box">
<!-- box / title -->
<div class="title">
${self.breadcrumbs()}
</div>
<!-- end box / title -->
<div class="table">
<div id="user_log">
${c.log_data}
<script>
YUE.on('q_filter','click',function(){
var qfilter = YUD.get('q_filter');
if(YUD.hasClass(qfilter, 'initial')){
qfilter.value = '';
YUE.on('j_filter','click',function(){
var jfilter = YUD.get('j_filter');
if(YUD.hasClass(jfilter, 'initial')){
jfilter.value = '';
});
var fix_q_filter_width = function(len){
YUD.setStyle(YUD.get('q_filter'),'width',Math.max(80, len*6.50)+'px');
var fix_j_filter_width = function(len){
YUD.setStyle(YUD.get('j_filter'),'width',Math.max(80, len*6.50)+'px');
YUE.on('q_filter','keyup',function(){
fix_q_filter_width(YUD.get('q_filter').value.length);
YUE.on('j_filter','keyup',function(){
fix_j_filter_width(YUD.get('j_filter').value.length);
YUE.on('filter_form','submit',function(e){
YUE.preventDefault(e)
var val = YUD.get('q_filter').value;
var val = YUD.get('j_filter').value;
window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
</script>
${_('Journal')} - ${c.rhodecode_name}
<%def name="breadcrumbs()">
${c.rhodecode_name}
<h5>
<input class="q_filter_box ${'' if c.search_term else 'initial'}" id="j_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
${_('journal')} - ${ungettext('%s entry', '%s entries', c.journal_pager.item_count) % (c.journal_pager.item_count)}
</h5>
${self.menu('home')}
<%def name="head_extra()">
<link href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('ATOM journal feed')}" type="application/atom+xml" />
<link href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}" rel="alternate" title="${_('RSS journal feed')}" type="application/rss+xml" />
<div class="box box-left">
<h5>${_('Journal')}</h5>
<ul class="links">
<li>
<span><a id="refresh" href="${h.url('journal')}"><img class="icon" title="${_('Refresh')}" alt="${_('Refresh')}" src="${h.url('/images/icons/arrow_refresh.png')}"/></a></span>
</li>
<span><a href="${h.url('journal_rss', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('RSS feed')}" alt="${_('RSS feed')}" src="${h.url('/images/icons/rss_16.png')}"/></a></span>
<span><a href="${h.url('journal_atom', api_key=c.rhodecode_user.api_key)}"><img class="icon" title="${_('ATOM feed')}" alt="${_('ATOM feed')}" src="${h.url('/images/icons/atom.png')}"/></a></span>
</ul>
<div id="journal">${c.journal_data}</div>
<div class="box box-right">
<input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
<a id="show_watched" class="link-white" href="#watched">${_('Watched')}</a> / <a id="show_my" class="link-white" href="#my">${_('My repos')}</a>
%if h.HasPermissionAny('hg.admin','hg.create.repository')():
<span>${h.link_to(_('ADD'),h.url('admin_settings_create_repository'))}</span>
%endif
<div id="my" class="table" style="display:none">
## loaded via AJAX
${_('Loading...')}
<div id="watched" class="table">
%if c.following:
<table>
<thead>
<tr>
<th class="left">${_('Name')}</th>
</thead>
<tbody>
%for entry in c.following:
<td>
%if entry.follows_user_id:
<img title="${_('following user')}" alt="${_('user')}" src="${h.url('/images/icons/user.png')}"/>
${entry.follows_user.full_contact}
%if entry.follows_repo_id:
<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):
<img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
%elif h.is_git(entry.follows_repository):
<img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
%if entry.follows_repository.private and c.visual.show_private_icon:
<img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
%elif not entry.follows_repository.private and c.visual.show_public_icon:
<img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
<span class="watched_repo">
${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',repo_name=entry.follows_repository.repo_name))}
</td>
</tr>
%endfor
</tbody>
</table>
%else:
<div style="padding:5px 0px 10px 0px;">
${_('You are not following any users or repositories')}
<script type="text/javascript">
var show_my = function(e){
YUD.setStyle('watched','display','none');
YUD.setStyle('my','display','');
var url = "${h.url('admin_settings_my_repos')}";
ypjax(url, 'my', function(){
tooltip_activate();
quick_repo_menu();
var nodes = YUQ('#my tr td a.repo_name');
var func = function(node){
return node.parentNode.parentNode.parentNode;
q_filter('q_filter',nodes,func);
YUE.on('show_my','click',function(e){
show_my(e);
})
var show_watched = function(e){
YUD.setStyle('my','display','none');
YUD.setStyle('watched','display','');
var nodes = YUQ('#watched .watched_repo a');
var target = 'q_filter';
return node.parentNode.parentNode;
q_filter(target,nodes,func);
YUE.on('show_watched','click',function(e){
show_watched(e);
//init watched
show_watched();
var tabs = {
'watched': show_watched,
'my': show_my,
var url = location.href.split('#');
if (url[1]) {
//We have a hash
var tabHash = url[1];
tabs[tabHash]();
YUE.on('refresh','click',function(e){
ypjax(e.currentTarget.href,"journal",function(){
ypjax("${h.url.current(filter=c.search_term)}","journal",function(){
show_more_event();
show_changeset_tooltip();
YUE.preventDefault(e);
// main table sorting
var myColumnDefs = [
{key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
{key:"name",label:"${_('Name')}",sortable:true,
sortOptions: { sortFunction: nameSort }},
{key:"tip",label:"${_('Tip')}",sortable:true,
sortOptions: { sortFunction: revisionSort }},
{key:"action1",label:"",sortable:false},
{key:"action2",label:"",sortable:false},
];
var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
myDataSource.responseSchema = {
fields: [
{key:"menu"},
{key:"name"},
{key:"tip"},
{key:"action1"},
{key:"action2"}
};
var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
{
sortedBy:{key:"name",dir:"asc"},
MSG_SORTASC:"${_('Click to sort ascending')}",
MSG_SORTDESC:"${_('Click to sort descending')}",
MSG_EMPTY:"${_('No records found.')}",
MSG_ERROR:"${_('Data error.')}",
MSG_LOADING:"${_('Loading...')}",
);
myDataTable.subscribe('postRenderEvent',function(oArgs) {
return node.parentNode.parentNode.parentNode.parentNode;
q_filter('q_filter',YUQ('#my tr td a.repo_name'),func);
@@ -7,97 +7,109 @@ from rhodecode.model.meta import Session
from rhodecode.lib.utils2 import safe_unicode
dn = os.path.dirname
FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'fixtures')
class TestAdminController(TestController):
@classmethod
def setup_class(cls):
UserLog.query().delete()
Session().commit()
with open(os.path.join(FIXTURES, 'journal_dump.csv')) as f:
for row in csv.DictReader(f):
ul = UserLog()
for k, v in row.iteritems():
v = safe_unicode(v)
if k == 'action_date':
v = datetime.datetime.strptime(v, '%Y-%m-%d %H:%M:%S.%f')
setattr(ul, k, v)
Session().add(ul)
def teardown_class(cls):
def test_index(self):
self.log_user()
response = self.app.get(url(controller='admin/admin', action='index'))
response.mustcontain('Admin journal')
def test_filter_all_entries(self):
response = self.app.get(url(controller='admin/admin', action='index',))
response.mustcontain('2034 entries')
def test_filter_journal_filter_exact_match_on_repository(self):
response = self.app.get(url(controller='admin/admin', action='index',
filter='repository:rhodecode'))
response.mustcontain('3 entries')
def test_filter_journal_filter_wildcard_on_repository(self):
filter='repository:*test*'))
response.mustcontain('862 entries')
def test_filter_journal_filter_prefix_on_repository(self):
filter='repository:test*'))
response.mustcontain('257 entries')
def test_filter_journal_filter_prefix_on_repository_and_user(self):
filter='repository:test* AND username:demo'))
response.mustcontain('130 entries')
def test_filter_journal_filter_prefix_on_repository_or_other_repo(self):
filter='repository:test* OR repository:rhodecode'))
response.mustcontain('260 entries') # 257 + 3
def test_filter_journal_filter_exact_match_on_username(self):
filter='username:demo'))
response.mustcontain('1087 entries')
def test_filter_journal_filter_wildcard_on_username(self):
filter='username:*test*'))
response.mustcontain('100 entries')
def test_filter_journal_filter_prefix_on_username(self):
filter='username:demo*'))
response.mustcontain('1101 entries')
def test_filter_journal_filter_prefix_on_user_or_other_user(self):
filter='username:demo OR username:volcan'))
response.mustcontain('1095 entries') # 1087 + 8
def test_filter_journal_filter_wildcard_on_action(self):
filter='action:*pull_request*'))
response.mustcontain('187 entries')
\ No newline at end of file
def test_filter_journal_filter_on_date(self):
filter='date:20121010'))
response.mustcontain('47 entries')
def test_filter_journal_filter_on_date_2(self):
filter='date:20121020'))
response.mustcontain('17 entries')
Status change: