Changeset - a08624dd675e
[Not reviewed]
beta
0 3 0
Marcin Kuzminski - 13 years ago 2012-12-05 21:14:31
marcin@python-works.com
Implemented filtering of admin journal based on Whoosh Query language
ref #210
3 files changed with 117 insertions and 8 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/admin/admin.py
Show inline comments
 
@@ -16,45 +16,128 @@
 
# (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):
 
                return getattr(UserLog, 'user_id').in_(
 
                    [x.user_id for x in
 
                     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
 
        if isinstance(term, query.Wildcard):
 
            return field.endsswith(val)
 
        elif isinstance(term, query.Prefix):
 
            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 = []
 
        for term in qry:
 
            field = term.fieldname
 
            val = term.text
 
            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')
rhodecode/lib/indexers/__init__.py
Show inline comments
 
@@ -26,25 +26,25 @@ import os
 
import sys
 
import traceback
 
import logging
 
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
 
@@ -80,24 +80,33 @@ CHGSETS_SCHEMA = Schema(
 
    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)
 

	
rhodecode/templates/admin/admin.html
Show inline comments
 
## -*- 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>
 

	
 
<%def name="page_nav()">
 
	${self.menu('admin')}
 
</%def>
 
<%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}
 
	    </div>
 
	</div>
 
</div>
 

	
 
<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>
 
</%def>
 

	
0 comments (0 inline, 0 general)