[ 
https://issues.apache.org/jira/browse/AIRFLOW-2799?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16679841#comment-16679841
 ] 

ASF GitHub Bot commented on AIRFLOW-2799:
-----------------------------------------

Fokko closed pull request #4061: [AIRFLOW-2799] Fix filtering UI objects by 
datetime
URL: https://github.com/apache/incubator-airflow/pull/4061
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/airflow/utils/timezone.py b/airflow/utils/timezone.py
index 6d49fbcbb3..5adaa2f5c4 100644
--- a/airflow/utils/timezone.py
+++ b/airflow/utils/timezone.py
@@ -164,9 +164,9 @@ def datetime(*args, **kwargs):
     return dt.datetime(*args, **kwargs)
 
 
-def parse(string):
+def parse(string, timezone=None):
     """
     Parse a time string and return an aware datetime
     :param string: time string
     """
-    return pendulum.parse(string, tz=TIMEZONE)
+    return pendulum.parse(string, tz=timezone or TIMEZONE)
diff --git a/airflow/www/utils.py b/airflow/www/utils.py
index 6404d27f95..757d2268bf 100644
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -38,7 +38,7 @@
 
 from flask import after_this_request, request, Response
 from flask_admin.model import filters
-from flask_admin.contrib.sqla.filters import FilterConverter
+import flask_admin.contrib.sqla.filters as sqlafilters
 from flask_login import current_user
 
 from airflow import configuration, models, settings
@@ -448,7 +448,43 @@ def __call__(self, field, **kwargs):
         return wtforms.widgets.core.HTMLString(html)
 
 
-class UtcFilterConverter(FilterConverter):
+class UtcDateTimeFilterMixin(object):
+    def clean(self, value):
+        dt = super(UtcDateTimeFilterMixin, self).clean(value)
+        return timezone.make_aware(dt, timezone=timezone.utc)
+
+
+class UtcDateTimeEqualFilter(UtcDateTimeFilterMixin, 
sqlafilters.DateTimeEqualFilter):
+    pass
+
+
+class UtcDateTimeNotEqualFilter(UtcDateTimeFilterMixin, 
sqlafilters.DateTimeNotEqualFilter):
+    pass
+
+
+class UtcDateTimeGreaterFilter(UtcDateTimeFilterMixin, 
sqlafilters.DateTimeGreaterFilter):
+    pass
+
+
+class UtcDateTimeSmallerFilter(UtcDateTimeFilterMixin, 
sqlafilters.DateTimeSmallerFilter):
+    pass
+
+
+class UtcDateTimeBetweenFilter(UtcDateTimeFilterMixin, 
sqlafilters.DateTimeBetweenFilter):
+    pass
+
+
+class UtcDateTimeNotBetweenFilter(UtcDateTimeFilterMixin, 
sqlafilters.DateTimeNotBetweenFilter):
+    pass
+
+
+class UtcFilterConverter(sqlafilters.FilterConverter):
+
+    utcdatetime_filters = (UtcDateTimeEqualFilter, UtcDateTimeNotEqualFilter,
+                           UtcDateTimeGreaterFilter, UtcDateTimeSmallerFilter,
+                           UtcDateTimeBetweenFilter, 
UtcDateTimeNotBetweenFilter,
+                           sqlafilters.FilterEmpty)
+
     @filters.convert('utcdatetime')
     def conv_utcdatetime(self, column, name, **kwargs):
-        return self.conv_datetime(column, name, **kwargs)
+        return [f(column, name, **kwargs) for f in self.utcdatetime_filters]
diff --git a/airflow/www_rbac/utils.py b/airflow/www_rbac/utils.py
index 0176a5312c..b25e1541ab 100644
--- a/airflow/www_rbac/utils.py
+++ b/airflow/www_rbac/utils.py
@@ -37,7 +37,10 @@
 from pygments import highlight, lexers
 from pygments.formatters import HtmlFormatter
 from flask import request, Response, Markup, url_for
-from airflow import configuration
+from flask_appbuilder.models.sqla.interface import SQLAInterface
+import flask_appbuilder.models.sqla.filters as fab_sqlafilters
+import sqlalchemy as sqla
+from airflow import configuration, settings
 from airflow.models import BaseOperator
 from airflow.operators.subdag_operator import SubDagOperator
 from airflow.utils import timezone
@@ -378,3 +381,69 @@ def get_chart_height(dag):
     charts, that is charts that take up space based on the size of the 
components within.
     """
     return 600 + len(dag.tasks) * 10
+
+
+class UtcAwareFilterMixin(object):
+    def apply(self, query, value):
+        value = timezone.parse(value, timezone=timezone.utc)
+
+        return super(UtcAwareFilterMixin, self).apply(query, value)
+
+
+class UtcAwareFilterEqual(UtcAwareFilterMixin, fab_sqlafilters.FilterEqual):
+    pass
+
+
+class UtcAwareFilterGreater(UtcAwareFilterMixin, 
fab_sqlafilters.FilterGreater):
+    pass
+
+
+class UtcAwareFilterSmaller(UtcAwareFilterMixin, 
fab_sqlafilters.FilterSmaller):
+    pass
+
+
+class UtcAwareFilterNotEqual(UtcAwareFilterMixin, 
fab_sqlafilters.FilterNotEqual):
+    pass
+
+
+class UtcAwareFilterConverter(fab_sqlafilters.SQLAFilterConverter):
+
+    conversion_table = (
+        (('is_utcdatetime', [UtcAwareFilterEqual,
+                             UtcAwareFilterGreater,
+                             UtcAwareFilterSmaller,
+                             UtcAwareFilterNotEqual]),) +
+        fab_sqlafilters.SQLAFilterConverter.conversion_table
+    )
+
+
+class CustomSQLAInterface(SQLAInterface):
+    """
+    FAB does not know how to handle columns with leading underscores because
+    they are not supported by WTForm. This hack will remove the leading
+    '_' from the key to lookup the column names.
+
+    """
+    def __init__(self, obj):
+        super(CustomSQLAInterface, self).__init__(obj)
+
+        self.session = settings.Session()
+
+        def clean_column_names():
+            if self.list_properties:
+                self.list_properties = dict(
+                    (k.lstrip('_'), v) for k, v in 
self.list_properties.items())
+            if self.list_columns:
+                self.list_columns = dict(
+                    (k.lstrip('_'), v) for k, v in self.list_columns.items())
+
+        clean_column_names()
+
+    def is_utcdatetime(self, col_name):
+        from airflow.utils.sqlalchemy import UtcDateTime
+        obj = self.list_columns[col_name].type
+        return isinstance(obj, UtcDateTime) or \
+            isinstance(obj, sqla.types.TypeDecorator) and \
+            isinstance(obj.impl, UtcDateTime)
+
+    filter_converter_class = UtcAwareFilterConverter
diff --git a/airflow/www_rbac/views.py b/airflow/www_rbac/views.py
index 8d0a8b0ea8..29e8da1b9c 100644
--- a/airflow/www_rbac/views.py
+++ b/airflow/www_rbac/views.py
@@ -41,7 +41,6 @@
 from flask_appbuilder import BaseView, ModelView, expose, has_access
 from flask_appbuilder.actions import action
 from flask_appbuilder.models.sqla.filters import BaseFilter
-from flask_appbuilder.models.sqla.interface import SQLAInterface
 from flask_babel import lazy_gettext
 from past.builtins import unicode
 from pygments import highlight, lexers
@@ -1839,27 +1838,7 @@ class AirflowModelView(ModelView):
     list_widget = AirflowModelListWidget
     page_size = PAGE_SIZE
 
-    class CustomSQLAInterface(SQLAInterface):
-        """
-        FAB does not know how to handle columns with leading underscores 
because
-        they are not supported by WTForm. This hack will remove the leading
-        '_' from the key to lookup the column names.
-
-        """
-        def __init__(self, obj):
-            super(AirflowModelView.CustomSQLAInterface, self).__init__(obj)
-
-            self.session = settings.Session()
-
-            def clean_column_names():
-                if self.list_properties:
-                    self.list_properties = dict(
-                        (k.lstrip('_'), v) for k, v in 
self.list_properties.items())
-                if self.list_columns:
-                    self.list_columns = dict(
-                        (k.lstrip('_'), v) for k, v in 
self.list_columns.items())
-
-            clean_column_names()
+    CustomSQLAInterface = wwwutils.CustomSQLAInterface
 
 
 class SlaMissModelView(AirflowModelView):
diff --git a/tests/www/test_views.py b/tests/www/test_views.py
index 734b28bcdc..87b5ff720b 100644
--- a/tests/www/test_views.py
+++ b/tests/www/test_views.py
@@ -770,5 +770,23 @@ def 
test_dt_nr_dr_form_with_base_date_and_num_runs_and_execution_date_within(sel
         
self.tester.test_with_base_date_and_num_runs_and_execution_date_within()
 
 
+class TestTaskInstanceView(unittest.TestCase):
+    TI_ENDPOINT = '/admin/taskinstance/?flt2_execution_date_greater_than={}'
+
+    def setUp(self):
+        super(TestTaskInstanceView, self).setUp()
+        configuration.load_test_config()
+        app = application.create_app(testing=True)
+        app.config['WTF_CSRF_METHODS'] = []
+        self.app = app.test_client()
+
+    def test_start_date_filter(self):
+        resp = self.app.get(self.TI_ENDPOINT.format('2018-10-09+22:44:31'))
+        # We aren't checking the logic of the date filter itself (that is built
+        # in to flask-admin) but simply that our UTC conversion was run - i.e. 
it
+        # doesn't blow up!
+        self.assertEqual(resp.status_code, 200)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/www_rbac/test_views.py b/tests/www_rbac/test_views.py
index 4b6d9d7d12..ef5899dc27 100644
--- a/tests/www_rbac/test_views.py
+++ b/tests/www_rbac/test_views.py
@@ -1375,5 +1375,18 @@ def test_log_success_for_user(self):
         self.check_content_in_response('"metadata":', resp)
 
 
+class TestTaskInstanceView(TestBase):
+    TI_ENDPOINT = '/taskinstance/list/?_flt_0_execution_date={}'
+
+    def test_start_date_filter(self):
+        resp = self.client.get(self.TI_ENDPOINT.format(
+            self.percent_encode('2018-10-09 22:44:31')))
+        # We aren't checking the logic of the date filter itself (that is built
+        # in to FAB) but simply that our UTC conversion was run - i.e. it
+        # doesn't blow up!
+        self.check_content_in_response('List Task Instance', resp)
+        pass
+
+
 if __name__ == '__main__':
     unittest.main()


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


> Filtering UI objects by datetime is broken 
> -------------------------------------------
>
>                 Key: AIRFLOW-2799
>                 URL: https://issues.apache.org/jira/browse/AIRFLOW-2799
>             Project: Apache Airflow
>          Issue Type: Bug
>          Components: ui, webserver
>    Affects Versions: 1.10.0
>         Environment: Debian Stretch, Python 3.5.3
>            Reporter: Kevin Campbell
>            Assignee: Ash Berlin-Taylor
>            Priority: Major
>             Fix For: 1.10.1
>
>
> On master (49fd23a3ee0269e2b974648f4a823c1d0b6c12ec) searching objects via 
> the user interface is broken for datetime fields.
> Create a new installation
>  Create a test dag (example_bash_operator)
>  Start webserver and scheduler
>  Enable dag
> On web UI, go to Browse > Task Instances
>  Search for task instances with execution_date greater than 5 days ago
>  You will get an exception
> {code:java}
>                           ____/ (  (    )   )  \___
>                          /( (  (  )   _    ))  )   )\
>                        ((     (   )(    )  )   (   )  )
>                      ((/  ( _(   )   (   _) ) (  () )  )
>                     ( (  ( (_)   ((    (   )  .((_ ) .  )_
>                    ( (  )    (      (  )    )   ) . ) (   )
>                   (  (   (  (   ) (  _  ( _) ).  ) . ) ) ( )
>                   ( (  (   ) (  )   (  ))     ) _)(   )  )  )
>                  ( (  ( \ ) (    (_  ( ) ( )  )   ) )  )) ( )
>                   (  (   (  (   (_ ( ) ( _    )  ) (  )  )   )
>                  ( (  ( (  (  )     (_  )  ) )  _)   ) _( ( )
>                   ((  (   )(    (     _    )   _) _(_ (  (_ )
>                    (_((__(_(__(( ( ( |  ) ) ) )_))__))_)___)
>                    ((__)        \\||lll|l||///          \_))
>                             (   /(/ (  )  ) )\   )
>                           (    ( ( ( | | ) ) )\   )
>                            (   /(| / ( )) ) ) )) )
>                          (     ( ((((_(|)_)))))     )
>                           (      ||\(|(|)|/||     )
>                         (        |(||(||)||||        )
>                           (     //|/l|||)|\\ \     )
>                         (/ / //  /|//||||\\  \ \  \ _)
> -------------------------------------------------------------------------------
> Node: wave.diffractive.io
> -------------------------------------------------------------------------------
> Traceback (most recent call last):
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/base.py",
>  line 1116, in _execute_context
>     context = constructor(dialect, self, conn, *args)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/default.py",
>  line 649, in _init_compiled
>     for key in compiled_params
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/default.py",
>  line 649, in <genexpr>
>     for key in compiled_params
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/sql/type_api.py",
>  line 1078, in process
>     return process_param(value, dialect)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy_utc/sqltypes.py",
>  line 30, in process_bind_param
>     raise ValueError('naive datetime is disallowed')
> ValueError: naive datetime is disallowed
> The above exception was the direct cause of the following exception:
> Traceback (most recent call last):
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask/app.py",
>  line 1982, in wsgi_app
>     response = self.full_dispatch_request()
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask/app.py",
>  line 1614, in full_dispatch_request
>     rv = self.handle_user_exception(e)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask/app.py",
>  line 1517, in handle_user_exception
>     reraise(exc_type, exc_value, tb)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask/_compat.py",
>  line 33, in reraise
>     raise value
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask/app.py",
>  line 1612, in full_dispatch_request
>     rv = self.dispatch_request()
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask/app.py",
>  line 1598, in dispatch_request
>     return self.view_functions[rule.endpoint](**req.view_args)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask_admin/base.py",
>  line 69, in inner
>     return self._run_view(f, *args, **kwargs)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask_admin/base.py",
>  line 368, in _run_view
>     return fn(self, *args, **kwargs)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask_admin/model/base.py",
>  line 1818, in index_view
>     view_args.search, view_args.filters)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/flask_admin/contrib/sqla/view.py",
>  line 969, in get_list
>     count = count_query.scalar() if count_query else None
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/orm/query.py",
>  line 2843, in scalar
>     ret = self.one()
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/orm/query.py",
>  line 2814, in one
>     ret = self.one_or_none()
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/orm/query.py",
>  line 2784, in one_or_none
>     ret = list(self)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/orm/query.py",
>  line 2855, in __iter__
>     return self._execute_and_instances(context)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/orm/query.py",
>  line 2878, in _execute_and_instances
>     result = conn.execute(querycontext.statement, self._params)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/base.py",
>  line 945, in execute
>     return meth(self, multiparams, params)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/sql/elements.py",
>  line 263, in _execute_on_connection
>     return connection._execute_clauseelement(self, multiparams, params)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/base.py",
>  line 1053, in _execute_clauseelement
>     compiled_sql, distilled_params
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/base.py",
>  line 1121, in _execute_context
>     None, None)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/base.py",
>  line 1402, in _handle_dbapi_exception
>     exc_info
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/util/compat.py",
>  line 203, in raise_from_cause
>     reraise(type(exception), exception, tb=exc_tb, cause=cause)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/util/compat.py",
>  line 186, in reraise
>     raise value.with_traceback(tb)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/base.py",
>  line 1116, in _execute_context
>     context = constructor(dialect, self, conn, *args)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/default.py",
>  line 649, in _init_compiled
>     for key in compiled_params
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/engine/default.py",
>  line 649, in <genexpr>
>     for key in compiled_params
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy/sql/type_api.py",
>  line 1078, in process
>     return process_param(value, dialect)
>   File 
> "/home/kev/.virtualenvs/airflow/local/lib/python3.5/site-packages/sqlalchemy_utc/sqltypes.py",
>  line 30, in process_bind_param
>     raise ValueError('naive datetime is disallowed')
> sqlalchemy.exc.StatementError: (builtins.ValueError) naive datetime is 
> disallowed [SQL: 'SELECT count(%(count_2)s) AS count_1 \nFROM task_instance 
> \nWHERE task_instance.execution_date > %(execution_date_1)s'] [parameters: 
> [{}]]
> {code}
> This appears to have been introduced in 
> https://issues.apache.org/jira/browse/AIRFLOW-288
> I've written a patch for this, which appears to resolve the issue. Will raise 
> a PR.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to