Repository: apache/airflow

Issue:
WebUI: Action on selection in task instance list yields an error
**Apache Airflow version**: v2.0.0.dev0 (latest master)

**Environment**:
- **OS**: Ubuntu 18.04.4 LTS
- **Others**: Python 3.6.9

**What happened**:

Selecting a task in the the **task instance list** (*http:localhost:8080/taskinstance/list/*) and **performing an Action** on it (e.g. *Set state to 'failed'*) yields an error.

Error message:

```
Something bad has happened.
Please consider letting us know by creating a bug report using Github.

Python version: 3.6.12
Airflow version: 2.0.0.dev0
Node: emma
-------------------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/airflow/.local/lib/python3.6/site-packages/flask/app.py", line 2447, in wsgi_app
    response = self.full_dispatch_request()
  File "/home/airflow/.local/lib/python3.6/site-packages/flask/app.py", line 1952, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/home/airflow/.local/lib/python3.6/site-packages/flask/app.py", line 1821, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/home/airflow/.local/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/home/airflow/.local/lib/python3.6/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/home/airflow/.local/lib/python3.6/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/views.py", line 686, in action_post
    self.datamodel.get(self._deserialize_pk_if_composite(pk)) for pk in pks
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/views.py", line 686, in <listcomp>
    self.datamodel.get(self._deserialize_pk_if_composite(pk)) for pk in pks
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/models/sqla/interface.py", line 870, in get
    query, _filters, select_columns=select_columns
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/models/sqla/interface.py", line 324, in apply_all
    select_columns,
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/models/sqla/interface.py", line 272, in _apply_inner_all
    query = self.apply_filters(query, inner_filters)
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/models/sqla/interface.py", line 162, in apply_filters
    return filters.apply_all(query)
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/models/filters.py", line 295, in apply_all
    query = flt.apply(query, value)
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/models/sqla/filters.py", line 137, in apply
    query, field = get_field_setup_query(query, self.model, self.column_name)
  File "/home/airflow/.local/lib/python3.6/site-packages/flask_appbuilder/models/sqla/filters.py", line 40, in get_field_setup_query
    if not hasattr(model, column_name):
TypeError: hasattr(): attribute name must be string
```

**How to reproduce it**:

I wanted to take an Action on a task instance of a DAG with `schedule_interval=None`. I am attaching a minimal DAG file used for reproducing this error. 

<details>
<summary>DAG file</summary>

```
from airflow import DAG
from datetime import timedelta, datetime
from airflow.operators.bash_operator import BashOperator

dag = DAG(
    'simple_dag',
    default_args= {
        'owner': 'airflow',
        'depends_on_past': False,
        'retries' : 0,
        'start_date': datetime(1970, 1, 1),
        'retry_delay': timedelta(seconds=30),
    },
    description='',
    schedule_interval=None,
    catchup=False,
)

t1 = BashOperator(
    task_id='task1',
    bash_command='echo 1',
    dag=dag
)
```

</details>

**Anything else we need to know**:

Taking the same Action on the DagRun list *(http://localhost:8080/dagrun/list)* works.

Great project btw 🙌. Really enjoying using it.


Discussion:
Thanks for opening your first issue here! Be sure to follow the issue template!

Any ideas here @ashb @mik-laj @ryanahamilton ? I was able to reproduce this
I was able to reproduce as well. The UI refresh updates didn't touch anything related to this. I thought the recent FAB upgrade from 3.0 to 3.1 could be the culprit, but I just tested the commit prior to that upgrade and was able to reproduce with FAB 3.0 as well.
Seeing this too;  at `/taskinstance/list/?_flt_0_state=queued#`, selected all tasks, tried to clear them.

Resolving patch:
diff --git a/airflow/www/utils.py b/airflow/www/utils.py
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -17,12 +17,14 @@
 # under the License.
 import json
 import time
+from typing import Any, List, Optional
 from urllib.parse import urlencode
 
 import markdown
 import sqlalchemy as sqla
 from flask import Markup, Response, request, url_for
 from flask_appbuilder.forms import FieldConverter
+from flask_appbuilder.models.filters import Filters
 from flask_appbuilder.models.sqla import filters as fab_sqlafilters
 from flask_appbuilder.models.sqla.interface import SQLAInterface
 from pygments import highlight, lexers
@@ -437,6 +439,43 @@ def is_utcdatetime(self, col_name):
                 isinstance(obj.impl, UtcDateTime)
         return False
 
+    # This is a local fix until https://github.com/dpgaspar/Flask-AppBuilder/pull/1493 is merged and released.
+    def get(
+        self,
+        id,
+        filters: Optional[Filters] = None,
+        select_columns: Optional[List[str]] = None,
+    ) -> Any:
+        """
+        Returns the result for a model get, applies filters and supports dotted
+        notation for joins and granular selecting query columns.
+
+        :param id: The model id (pk).
+        :param filters: A Filter class that contains all filters to apply.
+        :param select_columns: A List of columns to be specifically selected.
+        on the query. Supports dotted notation.
+        :return: Model instance if found, or none
+        """
+        pk = self.get_pk_name()
+        if filters:
+            _filters = filters.copy()
+        else:
+            _filters = Filters(self.filter_converter_class, self)
+
+        if self.is_pk_composite():
+            for _pk, _id in zip(pk, id):
+                _filters.add_filter(_pk, self.FilterEqual, _id)
+        else:
+            _filters.add_filter(pk, self.FilterEqual, id)
+        query = self.session.query(self.obj)
+        item = self.apply_all(
+            query, _filters, select_columns=select_columns
+        ).one_or_none()
+        if item:
+            if hasattr(item, self.obj.__name__):
+                return getattr(item, self.obj.__name__)
+        return item
+
     filter_converter_class = UtcAwareFilterConverter
 
 


Test patch: