Mercurial > hg > ltpdarepo
changeset 107:22b950fee548
Complete named queries support.
author | Daniele Nicolodi <daniele@grinta.net> |
---|---|
date | Tue, 23 Aug 2011 18:04:12 +0200 |
parents | 0d259fd52ca0 |
children | f075650d3e1d |
files | src/ltpdarepo/query.py src/ltpdarepo/static/style.css src/ltpdarepo/templates/namedquery.html src/ltpdarepo/templates/queries/view.html src/ltpdarepo/templates/query.html src/ltpdarepo/views/browse.py src/ltpdarepo/views/queries.py |
diffstat | 7 files changed, 91 insertions(+), 56 deletions(-) [+] |
line wrap: on
line diff
--- a/src/ltpdarepo/query.py Tue Aug 23 14:38:17 2011 +0200 +++ b/src/ltpdarepo/query.py Tue Aug 23 18:04:12 2011 +0200 @@ -1,13 +1,13 @@ import re -from flask import g +from flask import Markup, g, json from MySQLdb.cursors import DictCursor - -from ltpdarepo.form import Form from wtforms.fields import TextField, HiddenField from wtforms import validators from wtforms.validators import ValidationError +from ltpdarepo.form import Form + class IQuery(Form): query = HiddenField() @@ -63,3 +63,10 @@ curs = g.db.cursor() curs.execute("""DELETE FROM queries WHERE id=%s""", self.id) g.db.commit() + + def tostring(self): + criteria = json.loads(self.query) + print criteria + query = ["%s %s '%s'" % (field, op, value) for field, op, value in criteria] + string = ' AND '.join(s.replace(' ', ' ') for s in query) + return Markup(string)
--- a/src/ltpdarepo/static/style.css Tue Aug 23 14:38:17 2011 +0200 +++ b/src/ltpdarepo/static/style.css Tue Aug 23 18:04:12 2011 +0200 @@ -505,6 +505,12 @@ padding: 4px 4px 5px 5px; } +.querystring { + margin-top: 3em; + color: #888; + white-space: pre-wrap; +} + /* permisions display */ .permissions {
--- a/src/ltpdarepo/templates/namedquery.html Tue Aug 23 14:38:17 2011 +0200 +++ b/src/ltpdarepo/templates/namedquery.html Tue Aug 23 18:04:12 2011 +0200 @@ -1,3 +1,6 @@ {% extends "objs.html" %} {% block title %}{{ query.title }} — {{ database.id }}{% endblock %} {% block pagetitle %}{{ query.title }} — Database «{{ database.id }}»{% endblock %} +{% block below %} +<pre class="querystring">{{ querystring }}</pre> +{% endblock %}
--- a/src/ltpdarepo/templates/queries/view.html Tue Aug 23 14:38:17 2011 +0200 +++ b/src/ltpdarepo/templates/queries/view.html Tue Aug 23 18:04:12 2011 +0200 @@ -3,7 +3,7 @@ {% block body %} <h2>Query «{{ query.title }}»</h2> <p class="field"><span class="label">Database:</span> <tt>{{ query.db }}</tt></p> -<p class="field"><span class="label">Criteria:</span> <tt>{{ query.query }}</tt></p> +<p class="field"><span class="label">Query:</span> <tt>{{ query.tostring() }}</tt></p> <ul class="actions"> <li><a href="{{ url_for('manage.queries.edit', id=query.id) }}">Edit</a></li> <li><a href="{{ url_for('manage.queries.drop', id=query.id) }}">Drop</a></li>
--- a/src/ltpdarepo/templates/query.html Tue Aug 23 14:38:17 2011 +0200 +++ b/src/ltpdarepo/templates/query.html Tue Aug 23 18:04:12 2011 +0200 @@ -11,6 +11,8 @@ $.dropdown.replace($("select")); $("#save").live('click', function () { var query = $(".query form").serialize(); + if (! query) + return false; var form = $(document.createElement('form')).attr({ 'action': '{{ url_for('manage.queries.create') }}', 'method': 'POST' });
--- a/src/ltpdarepo/views/browse.py Tue Aug 23 14:38:17 2011 +0200 +++ b/src/ltpdarepo/views/browse.py Tue Aug 23 18:04:12 2011 +0200 @@ -4,7 +4,7 @@ import dateutil.tz import dateutil.parser -from flask import Blueprint, abort, g, request, render_template, json, make_response +from flask import Blueprint, Markup, abort, g, request, render_template, json, make_response from MySQLdb.cursors import DictCursor from wtforms import Form from wtforms.fields import Field @@ -26,7 +26,6 @@ EXTRA = ('analysis', 'author', 'additional_authors', 'comments', 'reference_ids', 'created', ) MORE = ('version', 'ip', 'hostname', 'os', 'validated', 'vdate') TIMESERIESFIELDS = ('id', 'name', 't0', 'nsecs', 'quantity', 'keywords', 'title', 'description') -ALLFIELDS = FIELDS + EXTRA PAGESIZE = 20 @@ -238,6 +237,13 @@ return self.strftime('%Y-%m-%d %H:%M:%S %Z') +class DateTimeJSONEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, datetime): + return obj.strftime('%Y-%m-%dT%H:%M:%S%Z') + return super(DateTimeJSONEncoder, self).default(obj) + + class Request(object): """Retrieves and validates query parameters from the request""" @@ -248,27 +254,35 @@ 'enum': unicode, 'datetime': datetimefield} - def __init__(self, request, columns, indexes={}): + def __init__(self, formdata, columns, indexes={}): # incoming data - self.formdata = request.args - # column names mapping used to validate orderby parameter + self.formdata = formdata + # column names mapping. used to validate orderby parameter self.columns = columns - # indexes description used to validate query parameters + # indexes description. used to validate query parameters self.indexes = indexes + # process incoming data self.process(self.formdata) + @staticmethod + def fromqs(string, columns, indexes={}): + from urlparse import parse_qs + from werkzeug.datastructures import MultiDict + # parse query string + formdata = parse_qs(string, keep_blank_values=True, strict_parsing=True) + formdata = MultiDict(formdata) + return Request(formdata, columns, indexes) + def process(self, formdata): fields = formdata.getlist('field') ops = formdata.getlist('operator') values = formdata.getlist('value') - self.where, self.vals, self.criteria = self.parsequery(fields, ops, values) - - def parsequery(self, fields, ops, values): + self.parsequery(zip(fields, ops, values)) + def parsequery(self, items): query, vals, parsed = [], [], [] - - for field, op, value in zip(fields, ops, values): + for field, op, value in items: # index description index = self.indexes[field] validate = self.validators[index['type']] @@ -287,7 +301,9 @@ # append to parse results parsed.append((field, op, value, error)) - return ' AND '.join(query), vals, parsed + self.where = ' AND '.join(query) + self.vals = vals + self.criteria = parsed @property def order(self): @@ -309,6 +325,19 @@ def query(self): return self.where, self.vals + def dumps(self): + dump = [(field, op, value) for field, op, value, err in self.criteria if not err] + return json.dumps(dump, cls=DateTimeJSONEncoder) + + def loads(self, string): + criteria = json.loads(string) + self.parsequery(criteria) + + def tostring(self): + query = ["%s %s '%s'" % (field, op, value) for field, op, value, err in self.criteria if not err] + string = ' AND '.join(s.replace(' ', ' ') for s in query) + return Markup(string) + class DateTimeField(Field): """Text input and that uses `dateutil.parser.parse` to parse the @@ -386,7 +415,7 @@ abort(404) # parse request - r = Request(request, Objs.columns) + r = Request(request.args, Objs.columns) count = Objs(database).count() batch = Pagination(r.page, size=PAGESIZE, count=count) @@ -408,7 +437,7 @@ if obj is None: # not found abort(404) - return render_template('obj.html', obj=obj, database=db, fields=FIELDS+EXTRA+MORE) + return render_template('obj.html', obj=obj, database=db, fields=FIELDS+EXTRA) @app.route('/<database>/<int:objid>/<frmt>') @@ -457,7 +486,7 @@ abort(404) # parse request - r = Request(request, Objs.columns) + r = Request(request.args, Objs.columns) q = '%' + request.args.get('q', '') + '%' # collect objects @@ -482,7 +511,7 @@ indexes = Indexes(database, Objs.columns) # parse request - r = Request(request, Objs.columns, indexes) + r = Request(request.args, Objs.columns, indexes) # collect objects count = Objs(database).filter(*r.query).count() @@ -502,31 +531,28 @@ if db is None: # not found abort(404) + query = Query.load(id) + if query is None: + # not found + abort(404) - query = Query.load(id) - params = json.loads(query.query) - fields, ops, values = [], [], [] - for q in params: - fields.append(q[0]) - ops.append(q[1]) - values.append(q[2]) + # applicable search criteria + indexes = Indexes(database, Objs.columns) - # build query string - q = ['`%s` %s %%s' % v for v in zip(fields, ops)] - where = ' AND '.join(q) + # parse request + r = Request(request.args, Objs.columns, indexes) - # simple string representation of the query - querystring = ' AND '.join("%s %s %s" % tuple(v) for v in params) + # load query + r.loads(query.query) - # get objects - r = Request() - count = Objs(database).filter(where, *values).count() + # collect objects + count = Objs(database).filter(*r.query).count() batch = Pagination(r.page, size=PAGESIZE, count=count) - objs = Objs(database).filter(where, *values).orderby(*r.order).limit(*batch.limits).all() + objs = Objs(database).filter(*r.query).orderby(*r.order).limit(*batch.limits).all() return render_template('namedquery.html', objs=objs, batch=batch, fields=FIELDS, database=db, - query=query, querystring=querystring) + query=query, querystring=r.tostring()) @app.route('/<database>/timeseries') @@ -541,7 +567,7 @@ indexes = Indexes(database, Timeseries.columns) # parse reuest - r = Request(request, Timeseries.columns, indexes) + r = Request(request.args, Timeseries.columns, indexes) t = Timerange(request.args) # collect objects
--- a/src/ltpdarepo/views/queries.py Tue Aug 23 14:38:17 2011 +0200 +++ b/src/ltpdarepo/views/queries.py Tue Aug 23 18:04:12 2011 +0200 @@ -1,15 +1,13 @@ -from flask import Blueprint, render_template, g, abort, redirect, request, json, session, url_for, flash - +from flask import Blueprint, render_template, g, abort, redirect, request, session, url_for, flash +from wtforms.fields import SubmitField from MySQLdb.cursors import DictCursor +from ltpdarepo.form import Form from ltpdarepo.security import require from ltpdarepo.query import Query, IQuery -from ltpdarepo.database import Database app = Blueprint('manage.queries', __name__) -from .browse import MAPPING, RMAPPING - @app.route('/') @require('admin') @@ -34,28 +32,23 @@ @require('admin') def create(): if 'title' not in request.values: - from urlparse import parse_qs - parsed = parse_qs(request.values['query'], keep_blank_values=True, strict_parsing=True) - fields = parsed['field'] - ops = parsed['operator'] - values = parsed['value'] - # query json representation - query = json.dumps(zip(fields, ops, values)) - # simple string representation - querystring = ' AND '.join("%s %s '%s'" % (MAPPING.get(f), o, v) for f, o, v in zip(fields, ops, values)) + + from .browse import Indexes, Request, Objs + indexes = Indexes(request.values.get('db', ''), Objs.columns) + query = Request.fromqs(request.values.get('query', ''), Objs.columns, indexes).dumps() form = IQuery() form.query.data = query form.csrf.data = session['_token'] return render_template('queries/create.html', form=form) - + form = IQuery() if request.method == 'POST' and form.validate(): query = Query() form.update(query) query.create() return redirect(url_for('.index')) - + return render_template('queries/create.html', form=form, query='') @@ -74,8 +67,6 @@ return render_template('queries/edit.html', form=form) -from ltpdarepo.form import Form -from wtforms.fields import SubmitField class IConfirm(Form): ok = SubmitField() cancel = SubmitField()