Form factory refactor

This commit is contained in:
Maxime Beauchemin 2015-09-04 22:14:07 -07:00
parent 6dd81a3e95
commit 1283bc0788
6 changed files with 139 additions and 152 deletions

View File

@ -6,3 +6,5 @@
* Save / bookmark / url shortener
* SQL: Find a way to manage granularity
* Create ~/.panoramix/ to host DB and config, generate default config there
* Add a per-datasource permission

90
panoramix/forms.py Normal file
View File

@ -0,0 +1,90 @@
from wtforms import Field, Form, SelectMultipleField, SelectField, TextField
from flask_appbuilder.fieldwidgets import Select2Widget, Select2ManyWidget
class OmgWtForm(Form):
field_order = tuple()
css_classes = dict()
@property
def fields(self):
fields = []
for field in self.field_order:
if hasattr(self, field):
obj = getattr(self, field)
if isinstance(obj, Field):
fields.append(getattr(self, field))
return fields
def get_field(self, fieldname):
return getattr(self, fieldname)
def field_css_classes(self, fieldname):
if fieldname in self.css_classes:
return " ".join(self.css_classes[fieldname])
return ""
def form_factory(datasource, viz, form_args=None):
from panoramix.viz import viz_types
row_limits = [10, 50, 100, 500, 1000, 5000, 10000]
series_limits = [0, 5, 10, 25, 50, 100, 500]
group_by_choices = [(s, s) for s in datasource.groupby_column_names]
# Pool of all the fields that can be used in Panoramix
px_form_fields = {
'viz_type': SelectField(
'Viz',
choices=[(k, v.verbose_name) for k, v in viz_types.items()]),
'metrics': SelectMultipleField(
'Metrics', choices=datasource.metrics_combo),
'groupby': SelectMultipleField(
'Group by',
choices=[(s, s) for s in datasource.groupby_column_names]),
'granularity': TextField('Time Granularity', default="one day"),
'since': TextField('Since', default="one day ago"),
'until': TextField('Until', default="now"),
'row_limit':
SelectField(
'Row limit', choices=[(s, s) for s in row_limits]),
'limit':
SelectField(
'Series limit', choices=[(s, s) for s in series_limits]),
'rolling_type': SelectField(
'Rolling',
choices=[(s, s) for s in ['mean', 'sum', 'std']]),
'rolling_periods': TextField('Periods',),
'series': SelectField('Series', choices=group_by_choices),
'entity': SelectField('Entity', choices=group_by_choices),
'x': SelectField('X Axis', choices=datasource.metrics_combo),
'y': SelectField('Y Axis', choices=datasource.metrics_combo),
'size': SelectField('Bubble Size', choices=datasource.metrics_combo),
}
field_css_classes = {k: ['form-control'] for k in px_form_fields.keys()}
select2 = [
'viz_type', 'metrics', 'groupby',
'row_limit', 'rolling_type', 'series',
'entity', 'x', 'y', 'size',]
field_css_classes['since'] += ['select2_free_since']
field_css_classes['until'] += ['select2_free_until']
field_css_classes['granularity'] += ['select2_free_granularity']
for field in select2:
field_css_classes[field] += ['select2']
class QueryForm(OmgWtForm):
field_order = viz.form_fields
css_classes = field_css_classes
for i in range(10):
setattr(QueryForm, 'flt_col_' + str(i), SelectField(
'Filter 1', choices=[(s, s) for s in datasource.filterable_column_names]))
setattr(QueryForm, 'flt_op_' + str(i), SelectField(
'Filter 1', choices=[(m, m) for m in ['in', 'not in']]))
setattr(QueryForm, 'flt_eq_' + str(i), TextField("Super"))
for ff in viz.form_fields:
if isinstance(ff, basestring):
ff = [ff]
for s in ff:
setattr(QueryForm, s, px_form_fields[s])
return QueryForm

View File

@ -38,32 +38,27 @@ form input.form-control {
<hr>
<form id="query" method="GET" style="display: none;">
<div>{{ form.viz_type.label }}: {{ form.viz_type(class_="form-control select2") }}</div>
{% if 'metrics' not in viz.hidden_fields %}
<div>{{ form.metrics.label }}: {{ form.metrics(class_="form-control select2") }}</div>
{% endif %}
{% if 'granularity' not in viz.hidden_fields %}
<div>{{ form.granularity.label }}
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
title="Supports natural language time as in '10 seconds', '1 day' or '1 week'"
id="blah"></i>
{{ form.granularity(class_="form-control select2_free_granularity") }}</div>
{% endif %}
<div class="row">
<div class="form-group">
<div class="col-xs-6">{{ form.since.label }}
<i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
title="Supports natural language time as in '1 day ago', '28 days' or '3 years'"
id="blah"></i>
{{ form.since(class_="form-control select2_free_since") }}</div>
<div class="col-xs-6">{{ form.until.label }}
{{ form.until(class_="form-control select2_free_until") }}</div>
</div>
</div>
{% if 'groupby' not in viz.hidden_fields %}
<div>{{ form.groupby.label }}: {{ form.groupby(class_="form-control select2") }}</div>
{% endif %}
{% block extra_fields %}{% endblock %}
{% for fieldname in form.field_order %}
{% if not fieldname.__iter__ %}
<div>
{% set field = form.get_field(fieldname)%}
{{ field.label }}:
{{ field(class_=form.field_css_classes(field.name)) }}
</div>
{% else %}
<div class="row">
<div class="form-group">
{% for name in fieldname %}
<div class="col-xs-{{ (12 / fieldname|length) | int }}">
{% set field = form.get_field(name)%}
{{ field.label }}:
{{ field(class_=form.field_css_classes(field.name)) }}
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
<hr>
<h4>Filters</h4>
<div id="flt0" style="display: none;">

View File

@ -4,36 +4,6 @@
<div id="chart"></div>
{% endblock %}
{% block extra_fields %}
{% if form.compare %}
<div>{{ form.compare.label }}: {{ form.compare(class_="form-control") }}</div>
{% endif %}
{% if form.rolling_type %}
<div class="row">
<span class="col col-sm-5">{{ form.rolling_type.label }}: {{ form.rolling_type(class_="form-control select2") }}</span>
<span class="col col-sm-4">{{ form.rolling_periods.label }}: {{ form.rolling_periods(class_="form-control") }}</span>
</div>
{% endif %}
{% if form.limit %}
<div>{{ form.limit.label }}: {{ form.limit(class_="form-control select2") }}</div>
{% endif %}
{% if form.series %}
<div>{{ form.series.label }}: {{ form.series(class_="form-control select2") }}</div>
{% endif %}
{% if form.entity %}
<div>{{ form.entity.label }}: {{ form.entity(class_="form-control select2") }}</div>
{% endif %}
{% if form.size %}
<div>{{ form.size.label }}: {{ form.size(class_="form-control select2") }}</div>
{% endif %}
{% if form.x %}
<div>{{ form.x.label }}: {{ form.x(class_="form-control select2") }}</div>
{% endif %}
{% if form.y %}
<div>{{ form.y.label }}: {{ form.y(class_="form-control select2") }}</div>
{% endif %}
{% endblock %}
{% block tail %}
{{ super() }}
{% if viz.stockchart %}

View File

@ -33,10 +33,6 @@
{% endif %}
{% endblock %}
{% block extra_fields %}
<div>{{ form.row_limit.label }}: {{ form.row_limit(class_="form-control select2") }}</div>
{% endblock %}
{% block tail %}
{{ super() }}
<script src="{{ url_for('static', filename='jquery.dataTables.min.js') }}"></script>

View File

@ -2,12 +2,13 @@ from datetime import datetime
from flask import flash, request
import pandas as pd
from collections import OrderedDict
from panoramix import utils
from panoramix.highchart import Highchart, HighchartBubble
from wtforms import Form, SelectMultipleField, SelectField, TextField
import config
import logging
import numpy as np
from panoramix import utils
from panoramix.highchart import Highchart, HighchartBubble
from panoramix.forms import form_factory
CHART_ARGS = {
'height': 700,
@ -16,58 +17,14 @@ CHART_ARGS = {
}
class OmgWtForm(Form):
field_order = (
'viz_type', 'granularity', 'since', 'group_by', 'limit')
def fields(self):
fields = []
for field in self.field_order:
if hasattr(self, field):
obj = getattr(self, field)
if isinstance(obj, Field):
fields.append(getattr(self, field))
return fields
def form_factory(datasource, form_args=None, extra_fields_dict=None):
extra_fields_dict = extra_fields_dict or {}
if form_args:
limit = form_args.get("limit")
try:
limit = int(limit)
if limit not in limits:
limits.append(limit)
limits = sorted(limits)
except:
pass
class QueryForm(OmgWtForm):
viz_type = SelectField(
'Viz',
choices=[(k, v.verbose_name) for k, v in viz_types.items()])
metrics = SelectMultipleField('Metrics', choices=datasource.metrics_combo)
groupby = SelectMultipleField(
'Group by', choices=[
(s, s) for s in datasource.groupby_column_names])
granularity = TextField('Time Granularity', default="one day")
since = TextField('Since', default="one day ago")
until = TextField('Until', default="now")
for i in range(10):
setattr(QueryForm, 'flt_col_' + str(i), SelectField(
'Filter 1', choices=[(s, s) for s in datasource.filterable_column_names]))
setattr(QueryForm, 'flt_op_' + str(i), SelectField(
'Filter 1', choices=[(m, m) for m in ['in', 'not in']]))
setattr(QueryForm, 'flt_eq_' + str(i), TextField("Super"))
for k, v in extra_fields_dict.items():
setattr(QueryForm, k, v)
return QueryForm
class BaseViz(object):
verbose_name = "Base Viz"
template = "panoramix/datasource.html"
hidden_fields = []
form_fields = [
'viz_type', 'metrics', 'groupby', 'granularity',
('since', 'until')]
def __init__(self, datasource, form_data, view):
self.datasource = datasource
self.form_class = self.form_class()
@ -90,7 +47,7 @@ class BaseViz(object):
def form_class(self):
return form_factory(self.datasource, request.args)
return form_factory(self.datasource, self, request.args)
def query_filters(self):
args = self.form_data
@ -159,6 +116,7 @@ class BaseViz(object):
class TableViz(BaseViz):
verbose_name = "Table View"
template = 'panoramix/viz_table.html'
form_fields = BaseViz.form_fields + ['row_limit']
def query_obj(self):
d = super(TableViz, self).query_obj()
@ -178,17 +136,11 @@ class TableViz(BaseViz):
if self.form_data.get("granularity") == "all" and 'timestamp' in df:
del df['timestamp']
for m in self.metrics:
import numpy as np
df[m + '__perc'] = np.rint((df[m] / np.max(df[m])) * 100)
return super(TableViz, self).render(df=df)
def form_class(self):
limits = [10, 50, 100, 500, 1000, 5000, 10000]
return form_factory(self.datasource, request.args,
extra_fields_dict={
'row_limit':
SelectField('Row limit', choices=[(s, s) for s in limits])
})
return form_factory(self.datasource, self, request.args)
class HighchartsViz(BaseViz):
@ -204,28 +156,12 @@ class BubbleViz(HighchartsViz):
verbose_name = "Bubble Chart"
chart_type = 'bubble'
hidden_fields = ['granularity', 'metrics', 'groupby']
form_fields = [
'viz_type', 'since', 'until',
'series', 'entity', 'x', 'y', 'size', 'limit']
def form_class(self):
datasource = self.datasource
limits = [0, 5, 10, 25, 50, 100, 500]
return form_factory(self.datasource, request.args,
extra_fields_dict={
#'compare': TextField('Period Compare',),
'series': SelectField(
'Series', choices=[
(s, s) for s in datasource.groupby_column_names]),
'entity': SelectField(
'Entity', choices=[
(s, s) for s in datasource.groupby_column_names]),
'x': SelectField(
'X Axis', choices=datasource.metrics_combo),
'y': SelectField(
'Y Axis', choices=datasource.metrics_combo),
'size': SelectField(
'Bubble Size', choices=datasource.metrics_combo),
'limit': SelectField(
'Limit', choices=[(s, s) for s in limits]),
})
return form_factory(self.datasource, self, request.args)
def query_obj(self):
d = super(BubbleViz, self).query_obj()
@ -264,12 +200,18 @@ class BubbleViz(HighchartsViz):
return super(BubbleViz, self).render(error_msg=self.error_msg)
class TimeSeriesViz(HighchartsViz):
verbose_name = "Time Series - Line Chart"
chart_type = "spline"
stockchart = True
sort_legend_y = True
form_fields = [
'viz_type',
'granularity', ('since', 'until'),
'metrics',
'groupby', 'limit',
('rolling_type', 'rolling_periods'),
]
def render(self):
if request.args.get("granularity") == "all":
@ -285,7 +227,6 @@ class TimeSeriesViz(HighchartsViz):
values=metrics,)
rolling_periods = request.args.get("rolling_periods")
limit = request.args.get("limit")
rolling_type = request.args.get("rolling_type")
if rolling_periods and rolling_type:
if rolling_type == 'mean':
@ -306,17 +247,7 @@ class TimeSeriesViz(HighchartsViz):
return super(TimeSeriesViz, self).render(chart_js=chart.javascript_cmd)
def form_class(self):
limits = [0, 5, 10, 25, 50, 100, 500]
return form_factory(self.datasource, request.args,
extra_fields_dict={
#'compare': TextField('Period Compare',),
'rolling_type': SelectField(
'Rolling',
choices=[(s, s) for s in ['mean', 'sum', 'std']]),
'rolling_periods': TextField('Periods',),
'limit': SelectField(
'Series limit', choices=[(s, s) for s in limits])
})
return form_factory(self.datasource, self, request.args)
def bake_query(self):
"""
@ -324,14 +255,17 @@ class TimeSeriesViz(HighchartsViz):
"""
return self.datasource.query(**self.query_obj())
class TimeSeriesCompareViz(TimeSeriesViz):
verbose_name = "Time Series - Percent Change"
compare = 'percent'
class TimeSeriesCompareValueViz(TimeSeriesViz):
verbose_name = "Time Series - Value Change"
compare = 'value'
class TimeSeriesAreaViz(TimeSeriesViz):
verbose_name = "Time Series - Stacked Area Chart"
stacked=True