@@ -4,57 +4,140 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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
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 rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
from rhodecode.lib.base import BaseController, render
from rhodecode.model.db import UserLog
from rhodecode.lib.utils2 import safe_int
from rhodecode.model.db import UserLog, User
from rhodecode.lib.utils2 import safe_int, remove_prefix
from rhodecode.lib.indexers import JOURNAL_SCHEMA
log = logging.getLogger(__name__)
def _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:
qry = None
if search_term:
qp = QueryParser('repository', schema=JOURNAL_SCHEMA)
qry = qp.parse(unicode(search_term))
log.debug('Filtering using query %r' % qry)
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':
##special case for username
if isinstance(term, query.Wildcard):
#only support wildcards with * at beggining
val = remove_prefix(val, prefix='*')
return getattr(UserLog, 'user_id').in_(
[x.user_id for x in
User.query().filter(User.username.endswith(val))])
elif isinstance(term, query.Prefix):
User.query().filter(User.username.startswith(val))])
# term == exact match, case insensitive
field = getattr(UserLog, 'user')
val = User.get_by_username(val, case_insensitive=True)
else:
field = getattr(UserLog, field)
#sql filtering
return field.endsswith(val)
return field.startswith(val)
return field == val
if isinstance(qry, (query.And, query.Term, query.Prefix, query.Wildcard)):
if not isinstance(qry, query.And):
qry = [qry]
for term in qry:
field = term.fieldname
val = term.text
user_log = user_log.filter(get_filterion(field, val, term))
elif isinstance(qry, query.Or):
filters = []
if isinstance(term, query.Term):
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))\
.order_by(UserLog.action_date.desc())
.options(joinedload(UserLog.repository))
#FILTERING
c.search_term = request.GET.get('filter')
try:
users_log = _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)
c.users_log = Page(users_log, page=p, items_per_page=10)
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')
@@ -14,49 +14,49 @@
import os
import sys
import traceback
from os.path import dirname as dn, join as jn
#to get the rhodecode import
sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
from string import strip
from shutil import rmtree
from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
from whoosh.fields import TEXT, ID, STORED, NUMERIC, BOOLEAN, Schema, FieldType
from whoosh.fields import TEXT, ID, STORED, NUMERIC, BOOLEAN, Schema, FieldType, DATETIME
from whoosh.index import create_in, open_dir
from whoosh.formats import Characters
from whoosh.highlight import highlight, HtmlFormatter, ContextFragmenter
from webhelpers.html.builder import escape, literal
from sqlalchemy import engine_from_config
from rhodecode.model import init_model
from rhodecode.model.scm import ScmModel
from rhodecode.model.repo import RepoModel
from rhodecode.config.environment import load_environment
from rhodecode.lib.utils2 import LazyProperty
from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\
load_rcextensions
# CUSTOM ANALYZER wordsplit + lowercase filter
ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
#INDEX SCHEMA DEFINITION
SCHEMA = Schema(
fileid=ID(unique=True),
owner=TEXT(),
@@ -68,48 +68,57 @@ SCHEMA = Schema(
extension=TEXT(stored=True)
)
IDX_NAME = 'HG_INDEX'
FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
FRAGMENTER = ContextFragmenter(200)
CHGSETS_SCHEMA = Schema(
raw_id=ID(unique=True, stored=True),
date=NUMERIC(stored=True),
last=BOOLEAN(),
repository=ID(unique=True, stored=True),
author=TEXT(stored=True),
message=FieldType(format=Characters(), analyzer=ANALYZER,
scorable=True, stored=True),
parents=TEXT(),
added=TEXT(),
removed=TEXT(),
changed=TEXT(),
CHGSET_IDX_NAME = 'CHGSET_INDEX'
# used only to generate queries in journal
JOURNAL_SCHEMA = Schema(
username=TEXT(),
date=DATETIME(),
action=TEXT(),
repository=TEXT(),
ip=TEXT(),
class MakeIndex(BasePasterCommand):
max_args = 1
min_args = 1
usage = "CONFIG_FILE"
summary = "Creates index for full text search given configuration file"
group_name = "RhodeCode"
takes_config_file = -1
parser = Command.standard_parser(verbose=True)
def command(self):
logging.config.fileConfig(self.path_to_ini_file)
from pylons import config
add_cache(config)
engine = engine_from_config(config, 'sqlalchemy.db1.')
init_model(engine)
index_location = config['index_dir']
repo_location = self.options.repo_location \
if self.options.repo_location else RepoModel().repos_path
repo_list = map(strip, self.options.repo_list.split(',')) \
if self.options.repo_list else None
repo_update_list = map(strip, self.options.repo_update_list.split(',')) \
## -*- 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" id="q_filter" size="15" type="text" name="filter" value="${c.search_term or _('quick filter...')}"/>
<input type='submit' value="${_('filter')}" class="ui-btn"/>
${_('Admin journal')}
</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(){
YUD.get('q_filter').value = '';
});
YUE.on('filter_form','submit',function(e){
YUE.preventDefault(e)
var val = YUD.get('q_filter').value;
window.location = "${url.current(filter='__FILTER__')}".replace('__FILTER__',val);
</script>
Status change: