changeset 3062:a08624dd675e beta

Implemented filtering of admin journal based on Whoosh Query language ref #210
author Marcin Kuzminski <marcin@python-works.com>
date Wed, 05 Dec 2012 21:14:31 +0100
parents 7727faad5baf
children ca2b21819dfd
files rhodecode/controllers/admin/admin.py rhodecode/lib/indexers/__init__.py rhodecode/templates/admin/admin.html
diffstat 3 files changed, 117 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/controllers/admin/admin.py	Wed Dec 05 17:32:56 2012 +0100
+++ b/rhodecode/controllers/admin/admin.py	Wed Dec 05 21:14:31 2012 +0100
@@ -25,18 +25,89 @@
 
 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()
@@ -45,14 +116,26 @@
 
     @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'):
--- a/rhodecode/lib/indexers/__init__.py	Wed Dec 05 17:32:56 2012 +0100
+++ b/rhodecode/lib/indexers/__init__.py	Wed Dec 05 21:14:31 2012 +0100
@@ -35,7 +35,7 @@
 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
@@ -89,6 +89,15 @@
 
 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):
 
--- a/rhodecode/templates/admin/admin.html	Wed Dec 05 17:32:56 2012 +0100
+++ b/rhodecode/templates/admin/admin.html	Wed Dec 05 21:14:31 2012 +0100
@@ -6,7 +6,12 @@
 </%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()">
@@ -25,4 +30,16 @@
 	    </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>
+