diff --git a/TODO.md b/TODO.md
index 60c7788f683b5..40c646ac8671f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -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
+
diff --git a/panoramix/forms.py b/panoramix/forms.py
new file mode 100644
index 0000000000000..82b50c28b50ff
--- /dev/null
+++ b/panoramix/forms.py
@@ -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
diff --git a/panoramix/templates/panoramix/datasource.html b/panoramix/templates/panoramix/datasource.html
index 6ab628e2570f1..2a23e41abd572 100644
--- a/panoramix/templates/panoramix/datasource.html
+++ b/panoramix/templates/panoramix/datasource.html
@@ -38,32 +38,27 @@ <h3>
 
     <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;">
diff --git a/panoramix/templates/panoramix/viz_highcharts.html b/panoramix/templates/panoramix/viz_highcharts.html
index 451ba613c167e..6af92d2cab3f7 100644
--- a/panoramix/templates/panoramix/viz_highcharts.html
+++ b/panoramix/templates/panoramix/viz_highcharts.html
@@ -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 %}
diff --git a/panoramix/templates/panoramix/viz_table.html b/panoramix/templates/panoramix/viz_table.html
index 18fc6e122fba1..4c5b6e09a4396 100644
--- a/panoramix/templates/panoramix/viz_table.html
+++ b/panoramix/templates/panoramix/viz_table.html
@@ -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>
diff --git a/panoramix/viz.py b/panoramix/viz.py
index 71a7fa03e89d7..0c31b48379fb5 100644
--- a/panoramix/viz.py
+++ b/panoramix/viz.py
@@ -2,12 +2,13 @@
 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 @@
 }
 
 
-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 @@ def __init__(self, datasource, form_data, view):
 
 
     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 @@ def render(self, *args, **kwargs):
 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 @@ def render(self):
             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 @@ def render(self):
             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 @@ def render(self):
             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 @@ def render(self):
         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 @@ def bake_query(self):
         """
         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