Skip to content

Commit

Permalink
[wtforms] Strip leading/trailing whitespace (#7084)
Browse files Browse the repository at this point in the history
  • Loading branch information
john-bodley authored Apr 23, 2019
1 parent 845218d commit cf87173
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 0 deletions.
193 changes: 193 additions & 0 deletions superset/migrations/versions/d94d33dbe938_form_strip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""form strip
Revision ID: d94d33dbe938
Revises: 80aa3f04bc82
Create Date: 2019-03-21 10:22:01.610217
"""

# revision identifiers, used by Alembic.
revision = 'd94d33dbe938'
down_revision = '80aa3f04bc82'

from alembic import op
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text

from superset import db
from superset.utils.core import MediumText

Base = declarative_base()


class BaseColumnMixin(object):
id = Column(Integer, primary_key=True)
column_name = Column(String(255))
description = Column(Text)
type = Column(String(32))
verbose_name = Column(String(1024))


class BaseDatasourceMixin(object):
id = Column(Integer, primary_key=True)
description = Column(Text)


class BaseMetricMixin(object):
id = Column(Integer, primary_key=True)
d3format = Column(String(128))
description = Column(Text)
metric_name = Column(String(512))
metric_type = Column(String(32))
verbose_name = Column(String(1024))
warning_text = Column(Text)


class Annotation(Base):
__tablename__ = 'annotation'

id = Column(Integer, primary_key=True)
long_descr = Column(Text)
json_metadata = Column(Text)
short_descr = Column(String(500))


class Dashboard(Base):
__tablename__ = 'dashboards'

id = Column(Integer, primary_key=True)
css = Column(Text)
dashboard_title = Column(String(500))
description = Column(Text)
json_metadata = Column(Text)
position_json = Column(MediumText())
slug = Column(String(255))


class Database(Base):
__tablename__ = 'dbs'

id = Column(Integer, primary_key=True)
database_name = Column(String(250))
extra = Column(Text)
force_ctas_schema = Column(String(250))
sqlalchemy_uri = Column(String(1024))
verbose_name = Column(String(250))


class DruidCluster(Base):
__tablename__ = 'clusters'

id = Column(Integer, primary_key=True)
broker_host = Column(String(255))
broker_endpoint = Column(String(255))
cluster_name = Column(String(250))
verbose_name = Column(String(250))


class DruidColumn(BaseColumnMixin, Base):
__tablename__ = 'columns'

dimension_spec_json = Column(Text)


class DruidDatasource(BaseDatasourceMixin, Base):
__tablename__ = 'datasources'

datasource_name = Column(String(255))
default_endpoint = Column(Text)
fetch_values_from = Column(String(100))


class DruidMetric(BaseMetricMixin, Base):
__tablename__ = 'metrics'

json = Column(Text)


class Slice(Base):
__tablename__ = 'slices'

id = Column(Integer, primary_key=True)
description = Column(Text)
params = Column(Text)
slice_name = Column(String(250))
viz_type = Column(String(250))


class SqlaTable(BaseDatasourceMixin, Base):
__tablename__ = 'tables'

default_endpoint = Column(MediumText())
fetch_values_predicate = Column(String(1000))
main_dttm_col = Column(String(250))
schema = Column(String(255))
sql = Column(Text)
table_name = Column(String(250))
template_params = Column(Text)


class SqlMetric(BaseMetricMixin, Base):
__tablename__ = 'sql_metrics'

expression = Column(Text)


class TableColumn(BaseColumnMixin, Base):
__tablename__ = 'table_columns'

database_expression = Column(String(255))
expression = Column(Text)
python_date_format = Column(String(255))


def upgrade():
bind = op.get_bind()
session = db.Session(bind=bind)

tables = [
Annotation,
Dashboard,
Database,
DruidCluster,
DruidColumn,
DruidDatasource,
DruidMetric,
Slice,
SqlaTable,
SqlMetric,
TableColumn,
]

for table in tables:
for record in session.query(table).all():
for col in record.__table__.columns.values():
if not col.primary_key:
value = getattr(record, col.name)

if value is not None and value.strip() == '':
setattr(record, col.name, None)

session.commit()

session.close()


def downgrade():
pass
27 changes: 27 additions & 0 deletions superset/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,20 @@
import functools
import logging
import traceback
from typing import Any, Dict

from flask import abort, flash, g, get_flashed_messages, redirect, Response
from flask_appbuilder import BaseView, ModelView
from flask_appbuilder.actions import action
from flask_appbuilder.forms import DynamicForm
from flask_appbuilder.models.sqla.filters import BaseFilter
from flask_appbuilder.widgets import ListWidget
from flask_babel import get_locale
from flask_babel import gettext as __
from flask_babel import lazy_gettext as _
from flask_wtf.form import FlaskForm
import simplejson as json
from wtforms.fields.core import Field, UnboundField
import yaml

from superset import conf, db, get_feature_flags, security_manager
Expand Down Expand Up @@ -368,3 +372,26 @@ def check_ownership(obj, raise_if_false=True):
raise security_exception
else:
return False


def bind_field(
self,
form: DynamicForm,
unbound_field: UnboundField,
options: Dict[Any, Any],
) -> Field:
"""
Customize how fields are bound by stripping all whitespace.
:param form: The form
:param unbound_field: The unbound field
:param options: The field options
:returns: The bound field
"""

filters = unbound_field.kwargs.get('filters', [])
filters.append(lambda x: x.strip() if isinstance(x, str) else x)
return unbound_field.bind(form=form, filters=filters, **options)


FlaskForm.Meta.bind_field = bind_field
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ exclude =
superset/templates
venv
ignore =
E121
E125
FI12
FI15
FI16
Expand Down

0 comments on commit cf87173

Please sign in to comment.