# encoding=utf-8
#
# Copyright 2012 Nebula, Inc.
# Copyright 2014 IBM Corp.
#
#    Licensed 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.

import unittest
from unittest import mock
import uuid

from django import forms
from django import http
from django import shortcuts
from django.template import defaultfilters
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.translation import ungettext_lazy

from horizon import exceptions
from horizon import tables
from horizon.tables import actions
from horizon.tables import formset as table_formset
from horizon.tables import views as table_views
from horizon.test import helpers as test


class FakeObject(object):
    def __init__(self, id, name, value, status, optional=None, excluded=None):
        self.id = id
        self.name = name
        self.value = value
        self.status = status
        self.optional = optional
        self.excluded = excluded
        self.extra = "extra"

    def __str__(self):
        return u"%s: %s" % (self.__class__.__name__, self.name)


TEST_DATA = (
    FakeObject('1', 'object_1', 'value_1', 'up', 'optional_1', 'excluded_1'),
    FakeObject('2', 'object_2', '<strong>evil</strong>', 'down', 'optional_2'),
    FakeObject('3', 'object_3', 'value_3', 'up'),
    FakeObject('4', u'öbject_4', u'välue_1', u'üp', u'öptional_1',
               u'exclüded_1'),
)

TEST_DATA_2 = (
    FakeObject('1', 'object_1', 'value_1', 'down', 'optional_1', 'excluded_1'),
)

TEST_DATA_3 = (
    FakeObject('1', 'object_1', 'value_1', 'up', 'optional_1', 'excluded_1'),
)

TEST_DATA_4 = (
    FakeObject('1', 'object_1', 2, 'up'),
    FakeObject('2', 'object_2', 4, 'up'),
)

TEST_DATA_5 = (
    FakeObject('1', 'object_1', 'value_1',
               'A Status that is longer than 35 characters!', 'optional_1'),
)

TEST_DATA_6 = (
    FakeObject('1', 'object_1', 'DELETED', 'down'),
    FakeObject('2', 'object_2', 'CREATED', 'up'),
    FakeObject('3', 'object_3', 'STANDBY', 'standby'),
)

TEST_DATA_7 = (
    FakeObject('1', 'wrapped name', 'wrapped value', 'status',
               'not wrapped optional'),
)

TEST_DATA_8 = (
    FakeObject('1', 'object_1', 'value_1',
               'started', 'optional_1', 'excluded_1'),
    FakeObject('2', 'object_1', 'value_1',
               'half', 'optional_1', 'excluded_1'),
    FakeObject('3', 'object_1', 'value_1',
               'finished', 'optional_1', 'excluded_1'),
)


class MyLinkAction(tables.LinkAction):
    name = "login"
    verbose_name = "Log In"
    url = "login"
    attrs = {
        "class": "ajax-modal",
    }

    def get_link_url(self, datum=None, *args, **kwargs):
        return reverse(self.url)


class MyAction(tables.Action):
    name = "delete"
    verbose_name = "Delete Me"
    verbose_name_plural = "Delete Them"

    def allowed(self, request, obj=None):
        return getattr(obj, 'status', None) != 'down'

    def handle(self, data_table, request, object_ids):
        return shortcuts.redirect('http://example.com/?ids=%s'
                                  % ",".join(object_ids))


class MyColumn(tables.Column):
    pass


class MyRowSelectable(tables.Row):
    ajax = True

    def can_be_selected(self, datum):
        return datum.value != 'DELETED'


class MyRowSortable(tables.Row):
    ajax = True

    @classmethod
    def get_data(cls, request, obj_id):
        return TEST_DATA_8[0]


class MyRow(tables.Row):
    ajax = True

    @classmethod
    def get_data(cls, request, obj_id):
        return TEST_DATA_2[0]


class MyBatchAction(tables.BatchAction):
    name = "batch"

    def action(self, request, object_ids):
        pass

    @staticmethod
    def action_present(count):
        # Translators: test code, don't really have to translate
        return ungettext_lazy(
            u"Batch Item",
            u"Batch Items",
            count
        )

    @staticmethod
    def action_past(count):
        # Translators: test code, don't really have to translate
        return ungettext_lazy(
            u"Batched Item",
            u"Batched Items",
            count
        )


class MyBatchActionWithHelpText(MyBatchAction):
    name = "batch_help"
    help_text = "this is help."

    @staticmethod
    def action_present(count):
        # No translation
        return u"BatchHelp Item"

    @staticmethod
    def action_past(count):
        # No translation
        return u"BatchedHelp Item"


class MyToggleAction(tables.BatchAction):
    name = "toggle"

    def action_present(self, count):
        if self.current_present_action:
            # Translators: test code, don't really have to translate
            return ungettext_lazy(
                u"Up Item",
                u"Up Items",
                count
            )
        else:
            # Translators: test code, don't really have to translate
            return ungettext_lazy(
                u"Down Item",
                u"Down Items",
                count
            )

    def action_past(self, count):
        if self.current_past_action:
            # Translators: test code, don't really have to translate
            return ungettext_lazy(
                u"Upped Item",
                u"Upped Items",
                count
            )
        else:
            # Translators: test code, don't really have to translate
            return ungettext_lazy(
                u"Downed Item",
                u"Downed Items",
                count
            )

    def allowed(self, request, obj=None):
        if not obj:
            return False
        self.down = getattr(obj, 'status', None) == 'down'
        if self.down:
            self.current_present_action = 1
        else:
            self.current_present_action = 0
        return self.down or getattr(obj, 'status', None) == 'up'

    def action(self, request, object_ids):
        if self.down:
            # up it
            self.current_past_action = 1
        else:
            self.current_past_action = 0


class MyDisabledAction(MyToggleAction):
    def allowed(self, request, obj=None):
        return False


class MyFilterAction(tables.FilterAction):
    def filter(self, table, objs, filter_string):
        q = filter_string.lower()

        def comp(obj):
            if q in obj.name.lower():
                return True
            return False

        return filter(comp, objs)


class MyServerFilterAction(tables.FilterAction):
    filter_type = 'server'
    filter_choices = (('name', 'Name', False),
                      ('status', 'Status', True))
    needs_preloading = True

    def filter(self, table, items, filter_string):
        filter_field = table.get_filter_field()
        if filter_field == 'name' and filter_string:
            return [item for item in items
                    if filter_string in item.name]
        return items


def get_name(obj):
    return "custom %s" % obj.name


def get_link(obj):
    return reverse('login')


class MyTable(tables.DataTable):
    tooltip_dict = {'up': {'title': 'service is up and running',
                           'style': 'color:green;cursor:pointer'},
                    'down': {'title': 'service is not available',
                             'style': 'color:red;cursor:pointer'}}
    id = tables.Column('id', hidden=True, sortable=False)
    name = tables.Column(get_name,
                         verbose_name="Verbose Name",
                         sortable=True,
                         form_field=forms.CharField(),
                         form_field_attributes={'class': 'test'})
    value = tables.Column('value',
                          sortable=True,
                          link='http://example.com/',
                          attrs={'class': 'green blue'},
                          summation="average",
                          link_classes=('link-modal',),
                          link_attrs={'data-type': 'modal dialog',
                                      'data-tip': 'click for dialog'})
    status = tables.Column('status', link=get_link, truncate=35,
                           cell_attributes_getter=tooltip_dict.get)
    optional = tables.Column('optional', empty_value='N/A')
    excluded = tables.Column('excluded')

    class Meta(object):
        name = "my_table"
        verbose_name = "My Table"
        status_columns = ["status"]
        columns = ('id', 'name', 'value', 'optional', 'status')
        row_class = MyRow
        column_class = MyColumn
        table_actions = (MyFilterAction, MyAction, MyBatchAction,
                         MyBatchActionWithHelpText)
        row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction,
                       MyBatchActionWithHelpText)


class MyProgressTable(MyTable):
    tooltip_dict = {'started': {'percent': '10%'},
                    'half': {'percent': '50%'},
                    'finished': {'percent': '100%'}}
    status = tables.Column('status', truncate=35,
                           status=True,
                           cell_attributes_getter=tooltip_dict.get)

    class Meta(object):
        name = "my_table"
        verbose_name = "My Table"
        status_columns = ["status"]
        columns = ('id', 'name', 'value', 'optional', 'status')
        row_class = MyRowSortable
        column_class = MyColumn


class TableWithColumnsPolicy(tables.DataTable):
    name = tables.Column('name')
    restricted = tables.Column('restricted',
                               policy_rules=[('compute', 'role:admin')])


class MyServerFilterTable(MyTable):
    class Meta(object):
        name = "my_table"
        verbose_name = "My Table"
        status_columns = ["status"]
        columns = ('id', 'name', 'value', 'optional', 'status')
        row_class = MyRow
        column_class = MyColumn
        table_actions = (MyServerFilterAction, MyAction, MyBatchAction)
        row_actions = (MyAction, MyLinkAction, MyBatchAction, MyToggleAction,
                       MyBatchActionWithHelpText)


class MyTableSelectable(MyTable):
    class Meta(object):
        name = "my_table"
        columns = ('id', 'name', 'value', 'status')
        row_class = MyRowSelectable
        status_columns = ["status"]
        multi_select = True


class MyTableNotAllowedInlineEdit(MyTable):
    name = tables.Column(get_name,
                         verbose_name="Verbose Name",
                         sortable=True,
                         form_field=forms.CharField(),
                         form_field_attributes={'class': 'test'})

    class Meta(object):
        name = "my_table"
        columns = ('id', 'name', 'value', 'optional', 'status')
        row_class = MyRow


class MyTableWrapList(MyTable):
    name = tables.Column('name',
                         form_field=forms.CharField(),
                         form_field_attributes={'class': 'test'},
                         wrap_list=True)
    value = tables.Column('value',

                          wrap_list=True)
    optional = tables.Column('optional',
                             wrap_list=False)


class NoActionsTable(tables.DataTable):
    id = tables.Column('id')

    class Meta(object):
        name = "no_actions_table"
        verbose_name = "No Actions Table"
        table_actions = ()
        row_actions = ()


class DisabledActionsTable(tables.DataTable):
    id = tables.Column('id')

    class Meta(object):
        name = "disabled_actions_table"
        verbose_name = "Disabled Actions Table"
        table_actions = (MyDisabledAction,)
        row_actions = ()
        multi_select = True


class DataTableTests(test.TestCase):
    def test_table_instantiation(self):
        """Tests everything that happens when the table is instantiated."""
        self.table = MyTable(self.request, TEST_DATA)
        # Properties defined on the table
        self.assertEqual(TEST_DATA, self.table.data)
        self.assertEqual("my_table", self.table.name)
        # Verify calculated options that weren't specified explicitly
        self.assertTrue(self.table._meta.actions_column)
        self.assertTrue(self.table._meta.multi_select)
        # Test for verbose_name
        self.assertEqual(u"My Table", str(self.table))
        # Column ordering and exclusion.
        # This should include auto-columns for multi_select and actions,
        # but should not contain the excluded column.
        # Additionally, auto-generated columns should use the custom
        # column class specified on the table.
        self.assertQuerysetEqual(self.table.columns.values(),
                                 ['<MyColumn: multi_select>',
                                  '<Column: id>',
                                  '<Column: name>',
                                  '<Column: value>',
                                  '<Column: optional>',
                                  '<Column: status>',
                                  '<MyColumn: actions>'])
        # Actions (these also test ordering)
        self.assertQuerysetEqual(self.table.base_actions.values(),
                                 ['<MyBatchAction: batch>',
                                  '<MyBatchActionWithHelpText: batch_help>',
                                  '<MyAction: delete>',
                                  '<MyFilterAction: filter>',
                                  '<MyLinkAction: login>',
                                  '<MyToggleAction: toggle>'])
        self.assertQuerysetEqual(self.table.get_table_actions(),
                                 ['<MyFilterAction: filter>',
                                  '<MyAction: delete>',
                                  '<MyBatchAction: batch>',
                                  '<MyBatchActionWithHelpText: batch_help>'])
        self.assertQuerysetEqual(self.table.get_row_actions(TEST_DATA[0]),
                                 ['<MyAction: delete>',
                                  '<MyLinkAction: login>',
                                  '<MyBatchAction: batch>',
                                  '<MyToggleAction: toggle>',
                                  '<MyBatchActionWithHelpText: batch_help>'])
        # Auto-generated columns
        multi_select = self.table.columns['multi_select']
        self.assertEqual("multi_select", multi_select.auto)
        self.assertEqual("multi_select_column",
                         multi_select.get_final_attrs().get('class', ""))
        actions = self.table.columns['actions']
        self.assertEqual("actions", actions.auto)
        self.assertEqual("actions_column",
                         actions.get_final_attrs().get('class', ""))
        # In-line edit action on column.
        name_column = self.table.columns['name']
        self.assertEqual(forms.CharField, name_column.form_field.__class__)
        self.assertEqual({'class': 'test'}, name_column.form_field_attributes)

    @override_settings(POLICY_CHECK_FUNCTION=lambda *args: False)
    def test_table_column_policy_not_allowed(self):
        self.table = TableWithColumnsPolicy(self.request, TEST_DATA)
        self.assertEqual(TEST_DATA, self.table.data)
        # The column "restricted" is not rendered because of policy
        expected_columns = ['<Column: name>']
        self.assertQuerysetEqual(self.table.columns.values(), expected_columns)

    @override_settings(POLICY_CHECK_FUNCTION=lambda *args: True)
    def test_table_column_policy_allowed(self):
        self.table = TableWithColumnsPolicy(self.request, TEST_DATA)
        self.assertEqual(TEST_DATA, self.table.data)
        # Policy check returns True so the column "restricted" is rendered
        expected_columns = ['<Column: name>', '<Column: restricted>']
        self.assertQuerysetEqual(self.table.columns.values(), expected_columns)

    def test_table_force_no_multiselect(self):
        class TempTable(MyTable):
            class Meta(object):
                columns = ('id',)
                table_actions = (MyFilterAction, MyAction,)
                row_actions = (MyAction, MyLinkAction,)
                multi_select = False
        self.table = TempTable(self.request, TEST_DATA)
        self.assertQuerysetEqual(self.table.columns.values(),
                                 ['<Column: id>',
                                  '<Column: actions>'])

    def test_table_force_no_actions_column(self):
        class TempTable(MyTable):
            class Meta(object):
                columns = ('id',)
                table_actions = (MyFilterAction, MyAction, MyBatchAction)
                row_actions = (MyAction, MyLinkAction,)
                actions_column = False
        self.table = TempTable(self.request, TEST_DATA)
        self.assertQuerysetEqual(self.table.columns.values(),
                                 ['<Column: multi_select>',
                                  '<Column: id>'])

    def test_table_natural_no_inline_editing(self):
        class TempTable(MyTable):
            name = tables.Column(get_name,
                                 verbose_name="Verbose Name",
                                 sortable=True)

            class Meta(object):
                name = "my_table"
                columns = ('id', 'name', 'value', 'optional', 'status')

        self.table = TempTable(self.request, TEST_DATA_2)
        name_column = self.table.columns['name']
        self.assertIsNone(name_column.update_action)
        self.assertIsNone(name_column.form_field)
        self.assertEqual({}, name_column.form_field_attributes)

    def test_table_natural_no_actions_column(self):
        class TempTable(MyTable):
            class Meta(object):
                columns = ('id',)
                table_actions = (MyFilterAction, MyAction, MyBatchAction)
        self.table = TempTable(self.request, TEST_DATA)
        self.assertQuerysetEqual(self.table.columns.values(),
                                 ['<Column: multi_select>',
                                  '<Column: id>'])

    def test_table_natural_no_multiselect(self):
        class TempTable(MyTable):
            class Meta(object):
                columns = ('id',)
                row_actions = (MyAction, MyLinkAction,)
        self.table = TempTable(self.request, TEST_DATA)
        self.assertQuerysetEqual(self.table.columns.values(),
                                 ['<Column: id>',
                                  '<Column: actions>'])

    def test_table_column_inheritance(self):
        class TempTable(MyTable):
            extra = tables.Column('extra')

            class Meta(object):
                name = "temp_table"
                table_actions = (MyFilterAction, MyAction, MyBatchAction)
                row_actions = (MyAction, MyLinkAction,)

        self.table = TempTable(self.request, TEST_DATA)
        self.assertQuerysetEqual(self.table.columns.values(),
                                 ['<Column: multi_select>',
                                  '<Column: id>',
                                  '<Column: name>',
                                  '<Column: value>',
                                  '<Column: status>',
                                  '<Column: optional>',
                                  '<Column: excluded>',
                                  '<Column: extra>',
                                  '<Column: actions>'])

    def test_table_construction(self):
        self.table = MyTable(self.request, TEST_DATA)
        # Verify we retrieve the right columns for headers
        columns = self.table.get_columns()
        self.assertQuerysetEqual(columns, ['<MyColumn: multi_select>',
                                           '<Column: id>',
                                           '<Column: name>',
                                           '<Column: value>',
                                           '<Column: optional>',
                                           '<Column: status>',
                                           '<MyColumn: actions>'])
        # Verify we retrieve the right rows from our data
        rows = self.table.get_rows()
        self.assertQuerysetEqual(rows, ['<MyRow: my_table__row__1>',
                                        '<MyRow: my_table__row__2>',
                                        '<MyRow: my_table__row__3>',
                                        '<MyRow: my_table__row__4>'])
        # Verify each row contains the right cells
        self.assertQuerysetEqual(rows[0].get_cells(),
                                 ['<Cell: multi_select, my_table__row__1>',
                                  '<Cell: id, my_table__row__1>',
                                  '<Cell: name, my_table__row__1>',
                                  '<Cell: value, my_table__row__1>',
                                  '<Cell: optional, my_table__row__1>',
                                  '<Cell: status, my_table__row__1>',
                                  '<Cell: actions, my_table__row__1>'])

    def test_table_column(self):
        self.table = MyTable(self.request, TEST_DATA)
        row = self.table.get_rows()[0]
        row3 = self.table.get_rows()[2]
        id_col = self.table.columns['id']
        name_col = self.table.columns['name']
        value_col = self.table.columns['value']
        # transform
        self.assertEqual('1', row.cells['id'].data)  # Standard attr access
        self.assertEqual('custom object_1', row.cells['name'].data)  # Callable
        # name and verbose_name
        self.assertEqual("Id", str(id_col))
        self.assertEqual("Verbose Name", str(name_col))
        # sortable
        self.assertFalse(id_col.sortable)
        self.assertNotIn("sortable", id_col.get_final_attrs().get('class', ""))
        self.assertTrue(name_col.sortable)
        self.assertIn("sortable", name_col.get_final_attrs().get('class', ""))
        # hidden
        self.assertTrue(id_col.hidden)
        self.assertIn("hide", id_col.get_final_attrs().get('class', ""))
        self.assertFalse(name_col.hidden)
        self.assertNotIn("hide", name_col.get_final_attrs().get('class', ""))
        # link, link_classes, link_attrs, and get_link_url
        self.assertIn('href="http://example.com/"', row.cells['value'].value)
        self.assertIn('class="link-modal"', row.cells['value'].value)
        self.assertIn('data-type="modal dialog"', row.cells['value'].value)
        self.assertIn('data-tip="click for dialog"', row.cells['value'].value)
        self.assertIn('href="/auth/login/"', row.cells['status'].value)
        # empty_value
        self.assertEqual("N/A", row3.cells['optional'].value)
        # classes
        self.assertEqual("green blue sortable anchor normal_column",
                         value_col.get_final_attrs().get('class', ""))
        # status
        cell_status = row.cells['status'].status
        self.assertTrue(cell_status)
        self.assertEqual('status_up',
                         row.cells['status'].get_status_class(cell_status))
        # status_choices
        id_col.status = True
        id_col.status_choices = (('1', False), ('2', True), ('3', None))
        cell_status = row.cells['id'].status
        self.assertFalse(cell_status)
        self.assertEqual('status_down',
                         row.cells['id'].get_status_class(cell_status))
        cell_status = row3.cells['id'].status
        self.assertIsNone(cell_status)
        self.assertEqual('warning',
                         row.cells['id'].get_status_class(cell_status))

        # Ensure data is not cached on the column across table instances
        self.table = MyTable(self.request, TEST_DATA_2)
        row = self.table.get_rows()[0]
        self.assertIn("down", row.cells['status'].value)

    def test_table_row(self):
        self.table = MyTable(self.request, TEST_DATA)
        row = self.table.get_rows()[0]
        self.assertEqual(self.table, row.table)
        self.assertEqual(TEST_DATA[0], row.datum)
        self.assertEqual('my_table__row__1', row.id)
        # Verify row status works even if status isn't set on the column
        self.assertTrue(row.status)
        self.assertEqual('status_up', row.status_class)
        # Check the cells as well
        cell_status = row.cells['status'].status
        self.assertTrue(cell_status)
        self.assertEqual('status_up',
                         row.cells['status'].get_status_class(cell_status))

    def test_table_column_truncation(self):
        self.table = MyTable(self.request, TEST_DATA_5)
        row = self.table.get_rows()[0]

        self.assertEqual(35, len(row.cells['status'].data))
        self.assertEqual(u'A Status that is longer than 35 ch…',
                         row.cells['status'].data)

    def test_table_rendering(self):
        self.table = MyTable(self.request, TEST_DATA)
        # Table actions
        table_actions = self.table.render_table_actions()
        resp = http.HttpResponse(table_actions)
        self.assertContains(resp, "table_search", 1)
        self.assertContains(resp, "my_table__filter__q", 1)
        self.assertContains(resp, "my_table__delete", 1)
        self.assertContains(resp, 'id="my_table__action_delete"', 1)

        # Table BatchActions
        self.assertContains(resp, 'id="my_table__action_batch_help"', 1)
        self.assertContains(resp, 'help_text="this is help."', 1)
        self.assertContains(resp, 'BatchHelp Item', 1)

        # Row actions
        row_actions = self.table.render_row_actions(TEST_DATA[0])
        resp = http.HttpResponse(row_actions)
        self.assertContains(resp, "<li", 4)
        self.assertContains(resp, "my_table__delete__1", 1)
        self.assertContains(resp, "my_table__toggle__1", 1)
        self.assertContains(resp, "/auth/login/", 1)
        self.assertContains(resp, "ajax-modal", 1)
        self.assertContains(resp, 'id="my_table__row_1__action_delete"', 1)

        # Row BatchActions
        row_actions = self.table.render_row_actions(TEST_DATA[0])
        resp = http.HttpResponse(row_actions)
        self.assertContains(resp, 'id="my_table__row_1__action_batch_help"', 1)
        self.assertContains(resp, 'help_text="this is help."', 1)
        self.assertContains(resp, 'value="my_table__batch_help__1"', 1)
        self.assertContains(resp, 'BatchHelp Item', 1)

        # Whole table
        resp = http.HttpResponse(self.table.render())
        self.assertContains(resp, '<table id="my_table"', 1)
        self.assertContains(resp, '<th ', 7)
        self.assertContains(resp, 'id="my_table__row__1"', 1)
        self.assertContains(resp, 'id="my_table__row__2"', 1)
        self.assertContains(resp, 'id="my_table__row__3"', 1)
        update_string = "action=row_update&amp;table=my_table&amp;obj_id="
        self.assertContains(resp, update_string, 4)
        self.assertContains(resp, "data-update-interval", 4)
        # Verify no table heading
        self.assertNotContains(resp, "<h3 class='table_title'")
        # Verify our XSS protection
        self.assertContains(resp, '&lt;strong&gt;evil&lt;/strong&gt;', 1)
        # Hidden Title = False shows the table title
        self.table._meta.hidden_title = False
        resp = http.HttpResponse(self.table.render())
        self.assertContains(resp, "<span class='table-title'>", 1)

        # Filter = False hides the search box
        self.table._meta.filter = False
        table_actions = self.table.render_table_actions()
        resp = http.HttpResponse(table_actions)
        self.assertContains(resp, "table_search", 0)

    def test_wrap_list_rendering(self):
        self.table = MyTableWrapList(self.request, TEST_DATA_7)
        row = self.table.get_rows()[0]
        name_cell = row.cells['name']
        value_cell = row.cells['value']
        optional_cell = row.cells['optional']

        # Check if is cell is rendered correctly.
        name_cell_rendered = name_cell.render()
        value_cell_rendered = value_cell.render()
        optional_cell_rendered = optional_cell.render()
        resp_name = http.HttpResponse(name_cell_rendered)
        resp_value = http.HttpResponse(value_cell_rendered)
        resp_optional = http.HttpResponse(optional_cell_rendered)
        self.assertContains(resp_name, '<ul>wrapped name</ul>', 1)
        self.assertContains(resp_value, '<ul>wrapped value</ul>', 1)
        self.assertContains(resp_optional, 'not wrapped optional', 1)
        self.assertNotContains(resp_optional, '<ul>')
        self.assertNotContains(resp_optional, '</ul>')

    def test_progress_bar_rendering(self):
        self.table = MyProgressTable(self.request, TEST_DATA_8)
        row = self.table.get_rows()[0]
        status_cell0 = row.cells['status']
        row = self.table.get_rows()[1]
        status_cell1 = row.cells['status']
        row = self.table.get_rows()[2]
        status_cell2 = row.cells['status']

        # Check if is cell is rendered correctly.
        status_cell0_rendered = status_cell0.render()
        resp = http.HttpResponse(status_cell0_rendered)
        self.assertContains(resp, 'warning', 1)
        self.assertContains(resp, 'percent="10%"', 1)

        status_cell1_rendered = status_cell1.render()
        resp = http.HttpResponse(status_cell1_rendered)
        self.assertContains(resp, 'warning', 1)
        self.assertContains(resp, 'percent="50%"', 1)

        status_cell2_rendered = status_cell2.render()
        resp = http.HttpResponse(status_cell2_rendered)
        self.assertContains(resp, 'warning', 1)
        self.assertContains(resp, 'percent="100%"', 1)

    def test_inline_edit_mod_checkbox_with_label(self):
        class TempTable(MyTable):
            name = tables.Column(get_name,
                                 verbose_name="Verbose Name",
                                 sortable=True,
                                 form_field=forms.BooleanField(
                                     label="Verbose Name"),
                                 form_field_attributes={'class': 'test'})

            class Meta(object):
                name = "my_table"
                columns = ('id', 'name', 'value', 'optional', 'status')

        self.table = TempTable(self.request, TEST_DATA_2)
        name_col = self.table.columns['name']
        name_col.auto = "form_field"

        row = self.table.get_rows()[0]
        name_cell = row.cells['name']
        name_cell.inline_edit_mod = True

        # Check if is cell is rendered correctly.
        name_cell_rendered = name_cell.render()
        resp = http.HttpResponse(name_cell_rendered)

        self.assertContains(resp,
                            '<input checked="checked" class="test" '
                            'id="name__1" name="name__1" type="checkbox" '
                            'value="custom object_1" />',
                            count=1, html=True)

    def test_table_search_action(self):
        class TempTable(MyTable):
            class Meta(object):
                name = "my_table"
                table_actions = (tables.NameFilterAction,)

        # with the filter string 2, it should return 2nd item
        action_string = "my_table__filter__q"
        req = self.factory.post('/my_url/', {action_string: '2'})
        self.table = TempTable(req, TEST_DATA)
        self.assertQuerysetEqual(self.table.get_table_actions(),
                                 ['<NameFilterAction: filter>'])
        handled = self.table.maybe_handle()
        self.assertIsNone(handled)
        self.assertQuerysetEqual(self.table.filtered_data,
                                 ['FakeObject: object_2'],
                                 transform=str)

        # with empty filter string, it should return all data
        req = self.factory.post('/my_url/', {action_string: ''})
        self.table = TempTable(req, TEST_DATA)
        handled = self.table.maybe_handle()
        self.assertIsNone(handled)
        self.assertQuerysetEqual(self.table.filtered_data,
                                 ['FakeObject: object_1',
                                  'FakeObject: object_2',
                                  'FakeObject: object_3',
                                  u'FakeObject: öbject_4'],
                                 transform=str)

        # with unknown value it should return empty list
        req = self.factory.post('/my_url/', {action_string: 'horizon'})
        self.table = TempTable(req, TEST_DATA)
        handled = self.table.maybe_handle()
        self.assertIsNone(handled)
        self.assertQuerysetEqual(self.table.filtered_data, [])

    def test_inline_edit_mod_textarea(self):
        class TempTable(MyTable):
            name = tables.Column(get_name,
                                 verbose_name="Verbose Name",
                                 sortable=True,
                                 form_field=forms.CharField(
                                     widget=forms.Textarea(),
                                     required=False),
                                 form_field_attributes={'class': 'test'})

            class Meta(object):
                name = "my_table"
                columns = ('id', 'name', 'value', 'optional', 'status')

        self.table = TempTable(self.request, TEST_DATA_2)
        name_col = self.table.columns['name']
        name_col.auto = "form_field"

        row = self.table.get_rows()[0]
        name_cell = row.cells['name']
        name_cell.inline_edit_mod = True

        # Check if is cell is rendered correctly.
        name_cell_rendered = name_cell.render()
        resp = http.HttpResponse(name_cell_rendered)

        self.assertContains(resp,
                            '<textarea class="test" cols="40" id="name__1" '
                            'name="name__1" rows="10">\r\ncustom object_1'
                            '</textarea>',
                            count=1, html=True)

    def test_table_actions(self):
        # Single object action
        action_string = "my_table__delete__1"
        req = self.factory.post('/my_url/', {'action': action_string})
        self.table = MyTable(req, TEST_DATA)
        self.assertEqual(('my_table', 'delete', '1'),
                         self.table.parse_action(action_string))
        handled = self.table.maybe_handle()
        self.assertEqual(302, handled.status_code)
        self.assertEqual("http://example.com/?ids=1", handled["location"])

        # Batch action (without toggle) conjugation behavior
        req = self.factory.get('/my_url/')
        self.table = MyTable(req, TEST_DATA_3)
        toggle_action = self.table.get_row_actions(TEST_DATA_3[0])[2]
        self.assertEqual("Batch Item", toggle_action.verbose_name)

        # Batch action with custom help text
        req = self.factory.get('/my_url/')
        self.table = MyTable(req, TEST_DATA_3)
        toggle_action = self.table.get_row_actions(TEST_DATA_3[0])[4]
        self.assertEqual("BatchHelp Item", toggle_action.verbose_name)

        # Single object toggle action
        # GET page - 'up' to 'down'
        req = self.factory.get('/my_url/')
        self.table = MyTable(req, TEST_DATA_3)
        self.assertEqual(5, len(self.table.get_row_actions(TEST_DATA_3[0])))
        toggle_action = self.table.get_row_actions(TEST_DATA_3[0])[3]
        self.assertEqual("Down Item", toggle_action.verbose_name)

        # Toggle from status 'up' to 'down'
        # POST page
        action_string = "my_table__toggle__1"
        req = self.factory.post('/my_url/', {'action': action_string})
        self.table = MyTable(req, TEST_DATA)
        self.assertEqual(('my_table', 'toggle', '1'),
                         self.table.parse_action(action_string))
        handled = self.table.maybe_handle()
        self.assertEqual(302, handled.status_code)
        self.assertEqual("/my_url/", handled["location"])
        self.assertEqual(u"Downed Item: object_1",
                         list(req._messages)[0].message)

        # Toggle from status 'down' to 'up'
        # GET page - 'down' to 'up'
        req = self.factory.get('/my_url/')
        self.table = MyTable(req, TEST_DATA_2)
        self.assertEqual(4, len(self.table.get_row_actions(TEST_DATA_2[0])))
        toggle_action = self.table.get_row_actions(TEST_DATA_2[0])[2]
        self.assertEqual("Up Item", toggle_action.verbose_name)

        # POST page
        action_string = "my_table__toggle__2"
        req = self.factory.post('/my_url/', {'action': action_string})
        self.table = MyTable(req, TEST_DATA)
        self.assertEqual(('my_table', 'toggle', '2'),
                         self.table.parse_action(action_string))
        handled = self.table.maybe_handle()
        self.assertEqual(302, handled.status_code)
        self.assertEqual("/my_url/", handled["location"])
        self.assertEqual(u"Upped Item: object_2",
                         list(req._messages)[0].message)

        # there are underscore in object-id.
        # (because swift support custom object id)
        action_string = "my_table__toggle__2__33__$$"
        req = self.factory.post('/my_url/', {'action': action_string})
        self.table = MyTable(req, TEST_DATA)
        self.assertEqual(('my_table', 'toggle', '2__33__$$'),
                         self.table.parse_action(action_string))

        # Multiple object action
        action_string = "my_table__delete"
        req = self.factory.post('/my_url/', {'action': action_string,
                                             'object_ids': [1, 2]})
        self.table = MyTable(req, TEST_DATA)
        self.assertEqual(('my_table', 'delete', None),
                         self.table.parse_action(action_string))
        handled = self.table.maybe_handle()
        self.assertEqual(302, handled.status_code)
        self.assertEqual("http://example.com/?ids=1,2", handled["location"])

        # Action with nothing selected
        req = self.factory.post('/my_url/', {'action': action_string})
        self.table = MyTable(req, TEST_DATA)
        self.assertEqual(('my_table', 'delete', None),
                         self.table.parse_action(action_string))
        handled = self.table.maybe_handle()
        self.assertIsNone(handled)
        self.assertEqual("Please select a row before taking that action.",
                         list(req._messages)[0].message)

        # Action with specific id and multiple ids favors single id
        action_string = "my_table__delete__3"
        req = self.factory.post('/my_url/', {'action': action_string,
                                             'object_ids': [1, 2]})
        self.table = MyTable(req, TEST_DATA)
        self.assertEqual(('my_table', 'delete', '3'),
                         self.table.parse_action(action_string))
        handled = self.table.maybe_handle()
        self.assertEqual(302, handled.status_code)
        self.assertEqual("http://example.com/?ids=3",
                         handled["location"])

        # At least one object in table
        # BatchAction is available
        req = self.factory.get('/my_url/')
        self.table = MyTable(req, TEST_DATA_2)
        self.assertQuerysetEqual(self.table.get_table_actions(),
                                 ['<MyFilterAction: filter>',
                                  '<MyAction: delete>',
                                  '<MyBatchAction: batch>',
                                  '<MyBatchActionWithHelpText: batch_help>'])

        # Zero objects in table
        # BatchAction not available
        req = self.factory.get('/my_url/')
        self.table = MyTable(req, None)
        self.assertQuerysetEqual(self.table.get_table_actions(),
                                 ['<MyFilterAction: filter>',
                                  '<MyAction: delete>'])

        # Filtering
        action_string = "my_table__filter__q"
        req = self.factory.post('/my_url/', {action_string: '2'})
        self.table = MyTable(req, TEST_DATA)
        handled = self.table.maybe_handle()
        self.assertIsNone(handled)
        self.assertQuerysetEqual(self.table.filtered_data,
                                 ['FakeObject: object_2'],
                                 transform=str)

        # Ensure filtering respects the request method, e.g. no filter here
        req = self.factory.get('/my_url/', {action_string: '2'})
        self.table = MyTable(req, TEST_DATA)
        handled = self.table.maybe_handle()
        self.assertIsNone(handled)
        self.assertQuerysetEqual(self.table.filtered_data,
                                 ['FakeObject: object_1',
                                  'FakeObject: object_2',
                                  'FakeObject: object_3',
                                  u'FakeObject: öbject_4'],
                                 transform=str)

        # Updating and preemptive actions
        params = {"table": "my_table", "action": "row_update", "obj_id": "1"}
        req = self.factory.get('/my_url/',
                               params,
                               HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.table = MyTable(req)
        resp = self.table.maybe_preempt()
        self.assertEqual(200, resp.status_code)
        # Make sure the data returned differs from the original
        self.assertContains(resp, "my_table__row__1")
        self.assertContains(resp, "status_down")

        # Verify that we don't get a response for a valid action with the
        # wrong method.
        params = {"table": "my_table", "action": "delete", "obj_id": "1"}
        req = self.factory.get('/my_url/', params)
        self.table = MyTable(req)
        resp = self.table.maybe_preempt()
        self.assertIsNone(resp)
        resp = self.table.maybe_handle()
        self.assertIsNone(resp)

        # Verbose names
        table_actions = self.table.get_table_actions()
        self.assertEqual("Filter", table_actions[0].verbose_name)
        self.assertEqual("Delete Me", table_actions[1].verbose_name)

        row_actions = self.table.get_row_actions(TEST_DATA[0])
        self.assertEqual("Delete Me", row_actions[0].verbose_name)
        self.assertEqual("Log In", row_actions[1].verbose_name)

    def test_server_filtering(self):
        filter_value_param = "my_table__filter__q"
        filter_field_param = '%s_field' % filter_value_param

        # Server Filtering
        req = self.factory.post('/my_url/')
        req.session[filter_value_param] = '2'
        req.session[filter_field_param] = 'name'
        self.table = MyServerFilterTable(req, TEST_DATA)
        handled = self.table.maybe_handle()
        self.assertIsNone(handled)
        self.assertQuerysetEqual(self.table.filtered_data,
                                 ['FakeObject: object_2'],
                                 transform=str)

        # Ensure API filtering does not filter on server, e.g. no filter here
        req = self.factory.post('/my_url/')
        req.session[filter_value_param] = 'up'
        req.session[filter_field_param] = 'status'
        self.table = MyServerFilterTable(req, TEST_DATA)
        handled = self.table.maybe_handle()
        self.assertIsNone(handled)
        self.assertQuerysetEqual(self.table.filtered_data,
                                 ['FakeObject: object_1',
                                  'FakeObject: object_2',
                                  'FakeObject: object_3',
                                  u'FakeObject: öbject_4'],
                                 transform=str)

    def test_column_uniqueness(self):
        table1 = MyTable(self.request)
        table2 = MyTable(self.request)
        # Regression test for launchpad bug 964345.
        self.assertNotEqual(id(table1), id(table2))
        self.assertNotEqual(id(table1.columns), id(table2.columns))
        t1cols = list(table1.columns.values())
        t2cols = list(table2.columns.values())
        self.assertEqual(t1cols[0].name, t2cols[0].name)
        self.assertNotEqual(id(t1cols[0]), id(t2cols[0]))
        self.assertNotEqual(id(t1cols[0].table),
                            id(t2cols[0].table))
        self.assertNotEqual(id(t1cols[0].table._data_cache),
                            id(t2cols[0].table._data_cache))

    def test_summation_row(self):
        # Test with the "average" method.
        table = MyTable(self.request, TEST_DATA_4)
        res = http.HttpResponse(table.render())
        self.assertContains(res, '<tr class="summation"', 1)
        self.assertContains(res, '<td>Summary</td>', 1)
        self.assertContains(res, '<td>3.0</td>', 1)

        # Test again with the "sum" method.
        table.columns['value'].summation = "sum"
        res = http.HttpResponse(table.render())
        self.assertContains(res, '<tr class="summation"', 1)
        self.assertContains(res, '<td>Summary</td>', 1)
        self.assertContains(res, '<td>6</td>', 1)

        # One last test with no summation.
        table.columns['value'].summation = None
        table.needs_summary_row = False
        res = http.HttpResponse(table.render())
        self.assertNotContains(res, '<tr class="summation"')
        self.assertNotContains(res, '<td>3.0</td>')
        self.assertNotContains(res, '<td>6</td>')

        # Even if "average" summation method is specified,
        # we have summation fields but no value is provided
        # if the provided data cannot be summed.
        table = MyTable(self.request, TEST_DATA)
        res = http.HttpResponse(table.render())
        self.assertContains(res, '<tr class="summation"')
        self.assertNotContains(res, '<td>3.0</td>')
        self.assertNotContains(res, '<td>6</td>')

    def test_table_action_attributes(self):
        table = MyTable(self.request, TEST_DATA)
        self.assertTrue(table.has_actions)
        self.assertTrue(table.needs_form_wrapper)
        res = http.HttpResponse(table.render())
        self.assertContains(res, "<form")

        table = MyTable(self.request, TEST_DATA, needs_form_wrapper=False)
        self.assertTrue(table.has_actions)
        self.assertFalse(table.needs_form_wrapper)
        res = http.HttpResponse(table.render())
        self.assertNotContains(res, "<form")

        table = NoActionsTable(self.request, TEST_DATA)
        self.assertFalse(table.has_actions)
        self.assertFalse(table.needs_form_wrapper)
        res = http.HttpResponse(table.render())
        self.assertNotContains(res, "<form")

    def test_table_actions_not_allowed_hide_multiselect(self):
        table = DisabledActionsTable(self.request, TEST_DATA)
        self.assertFalse(table.has_actions)
        self.assertFalse(table.needs_form_wrapper)
        res = http.HttpResponse(table.render())
        self.assertContains(res, "multi_select_column hidden")

    def test_table_action_object_display_is_id(self):
        action_string = "my_table__toggle__1"
        req = self.factory.post('/my_url/', {'action': action_string})
        self.table = MyTable(req, TEST_DATA)

        with mock.patch.object(
                self.table,
                'get_object_display',
                return_value=None) as mock_get_object_display:

            self.assertEqual(('my_table', 'toggle', '1'),
                             self.table.parse_action(action_string))
            handled = self.table.maybe_handle()

        self.assertEqual(302, handled.status_code)
        self.assertEqual("/my_url/", handled["location"])
        self.assertEqual(u"Downed Item: 1",
                         list(req._messages)[0].message)
        mock_get_object_display.assert_called_once_with(
            test.IsA(FakeObject))

    def test_table_column_can_be_selected(self):
        self.table = MyTableSelectable(self.request, TEST_DATA_6)
        # non selectable row
        row = self.table.get_rows()[0]
        # selectable
        row1 = self.table.get_rows()[1]
        row2 = self.table.get_rows()[2]

        id_col = self.table.columns['id']
        name_col = self.table.columns['name']
        value_col = self.table.columns['value']
        # transform
        self.assertEqual('1', row.cells['id'].data)  # Standard attr access
        self.assertEqual('custom object_1', row.cells['name'].data)  # Callable
        # name and verbose_name
        self.assertEqual("Id", str(id_col))
        self.assertEqual("Verbose Name", str(name_col))
        self.assertIn("sortable", name_col.get_final_attrs().get('class', ""))
        # hidden
        self.assertTrue(id_col.hidden)
        self.assertIn("hide", id_col.get_final_attrs().get('class', ""))
        self.assertFalse(name_col.hidden)
        self.assertNotIn("hide", name_col.get_final_attrs().get('class', ""))
        # link, link_classes, link_attrs and get_link_url
        self.assertIn('href="http://example.com/"', row.cells['value'].value)
        self.assertIn('class="link-modal"', row.cells['value'].value)
        self.assertIn('data-type="modal dialog"', row.cells['value'].value)
        self.assertIn('data-tip="click for dialog"', row.cells['value'].value)
        self.assertIn('href="/auth/login/"', row.cells['status'].value)
        # classes
        self.assertEqual("green blue sortable anchor normal_column",
                         value_col.get_final_attrs().get('class', ""))

        self.assertQuerysetEqual(row.get_cells(),
                                 ['<Cell: multi_select, my_table__row__1>',
                                  '<Cell: id, my_table__row__1>',
                                  '<Cell: name, my_table__row__1>',
                                  '<Cell: value, my_table__row__1>',
                                  '<Cell: status, my_table__row__1>',
                                  ])
        # can_be_selected = False
        self.assertEqual("", row.get_cells()[0].data)
        # can_be_selected = True
        self.assertIn('checkbox', row1.get_cells()[0].data)
        # status
        cell_status = row.cells['status'].status
        self.assertEqual('status_down',
                         row.cells['status'].get_status_class(cell_status))

        self.assertEqual(row.cells['status'].data, 'down')
        self.assertEqual(row.cells['status'].attrs,
                         {'title': 'service is not available',
                          'style': 'color:red;cursor:pointer'})
        self.assertEqual(row1.cells['status'].data, 'up')
        self.assertEqual(row1.cells['status'].attrs,
                         {'title': 'service is up and running',
                          'style': 'color:green;cursor:pointer'})
        self.assertEqual(row2.cells['status'].data, 'standby')
        self.assertEqual(row2.cells['status'].attrs, {})

        status_rendered = row.cells['status'].render()
        resp = http.HttpResponse(status_rendered)
        self.assertContains(resp, 'style="color:red;cursor:pointer"', 1)
        self.assertContains(resp, 'title="service is not available"', 1)

        status_rendered = row1.cells['status'].render()
        resp = http.HttpResponse(status_rendered)
        self.assertContains(resp, 'style="color:green;cursor:pointer"', 1)
        self.assertContains(resp, 'title="service is up and running"', 1)

        # status_choices
        id_col.status = True
        id_col.status_choices = (('1', False), ('2', True))
        cell_status = row.cells['id'].status
        self.assertFalse(cell_status)
        self.assertEqual('status_down',
                         row.cells['id'].get_status_class(cell_status))
        # Ensure data is not cached on the column across table instances
        self.table = MyTable(self.request, TEST_DATA_6)
        row = self.table.get_rows()[0]
        self.assertIn("down", row.cells['status'].value)

    def test_broken_filter(self):
        class MyTableBrokenFilter(MyTable):
            value = tables.Column('value',
                                  filters=(defaultfilters.timesince,))

        value = "not_a_date"
        data = TEST_DATA[0]
        data.value = value

        table = MyTableBrokenFilter(self.request, [data])
        resp = http.HttpResponse(table.render())
        self.assertContains(resp, value)


class SingleTableView(table_views.DataTableView):
    table_class = MyTable
    name = "Single Table"
    slug = "single"
    template_name = "horizon/common/_detail_table.html"

    def get_data(self):
        return TEST_DATA


class APIFilterTableView(SingleTableView):
    table_class = MyServerFilterTable


class TableWithPermissions(tables.DataTable):
    id = tables.Column('id')

    class Meta(object):
        name = "table_with_permissions"
        permissions = ('horizon.test',)


class SingleTableViewWithPermissions(SingleTableView):
    table_class = TableWithPermissions


class MultiTableView(tables.MultiTableView):
    table_classes = (TableWithPermissions, MyTable)

    def get_table_with_permissions_data(self):
        return TEST_DATA

    def get_my_table_data(self):
        return TEST_DATA


class DataTableViewTests(test.TestCase):
    def _prepare_view(self, cls, *args, **kwargs):
        req = self.factory.get('/my_url/')
        req.user = self.user
        view = cls()
        view.request = req
        view.args = args
        view.kwargs = kwargs
        return view

    def test_data_table_view(self):
        view = self._prepare_view(SingleTableView)
        context = view.get_context_data()
        self.assertEqual(SingleTableView.table_class,
                         context['table'].__class__)

    def test_data_table_view_not_authorized(self):
        view = self._prepare_view(SingleTableViewWithPermissions)
        context = view.get_context_data()
        self.assertNotIn('table', context)

    def test_data_table_view_authorized(self):
        view = self._prepare_view(SingleTableViewWithPermissions)
        self.set_permissions(permissions=['test'])
        context = view.get_context_data()
        self.assertIn('table', context)
        self.assertEqual(SingleTableViewWithPermissions.table_class,
                         context['table'].__class__)

    def test_multi_table_view_not_authorized(self):
        view = self._prepare_view(MultiTableView)
        context = view.get_context_data()
        self.assertEqual(MyTable, context['my_table_table'].__class__)
        self.assertNotIn('table_with_permissions_table', context)

    def test_multi_table_view_authorized(self):
        view = self._prepare_view(MultiTableView)
        self.set_permissions(permissions=['test'])
        context = view.get_context_data()
        self.assertEqual(MyTable, context['my_table_table'].__class__)
        self.assertEqual(TableWithPermissions,
                         context['table_with_permissions_table'].__class__)

    fil_value_param = "my_table__filter__q"
    fil_field_param = '%s_field' % fil_value_param

    def _test_filter_setup_view(self, request):
        view = APIFilterTableView()
        view.request = request
        view.kwargs = {}
        view.handle_server_filter(request)
        return view

    def test_api_filter_table_view(self):
        req = self.factory.post('/my_url/', {self.fil_value_param: 'up',
                                             self.fil_field_param: 'status'})
        req.user = self.user
        view = self._test_filter_setup_view(req)
        data = view.get_data()
        context = view.get_context_data()
        self.assertEqual(context['table'].__class__, MyServerFilterTable)
        self.assertQuerysetEqual(data,
                                 ['FakeObject: object_1',
                                  'FakeObject: object_2',
                                  'FakeObject: object_3',
                                  u'FakeObject: öbject_4'],
                                 transform=str)
        self.assertEqual(req.session.get(self.fil_value_param), 'up')
        self.assertEqual(req.session.get(self.fil_field_param), 'status')

    def test_filter_changed_deleted(self):
        req = self.factory.post('/my_url/', {self.fil_value_param: '',
                                             self.fil_field_param: 'status'})
        req.session[self.fil_value_param] = 'up'
        req.session[self.fil_field_param] = 'status'
        req.user = self.user
        view = self._test_filter_setup_view(req)
        context = view.get_context_data()
        self.assertEqual(context['table'].__class__, MyServerFilterTable)
        self.assertEqual(req.session.get(self.fil_value_param), '')
        self.assertEqual(req.session.get(self.fil_field_param), 'status')

    def test_filter_changed_nothing_sent(self):
        req = self.factory.post('/my_url/', {})
        req.session[self.fil_value_param] = 'up'
        req.session[self.fil_field_param] = 'status'
        req.user = self.user
        view = self._test_filter_setup_view(req)
        context = view.get_context_data()
        self.assertEqual(context['table'].__class__, MyServerFilterTable)
        self.assertEqual(req.session.get(self.fil_value_param), 'up')
        self.assertEqual(req.session.get(self.fil_field_param), 'status')

    def test_filter_changed_new_filter_sent(self):
        req = self.factory.post('/my_url/', {self.fil_value_param: 'down',
                                             self.fil_field_param: 'status'})
        req.session[self.fil_value_param] = 'up'
        req.session[self.fil_field_param] = 'status'
        req.user = self.user
        view = self._test_filter_setup_view(req)
        context = view.get_context_data()
        self.assertEqual(context['table'].__class__, MyServerFilterTable)
        self.assertEqual(req.session.get(self.fil_value_param), 'down')
        self.assertEqual(req.session.get(self.fil_field_param), 'status')


class FormsetTableTests(test.TestCase):

    def test_populate(self):
        """Create a FormsetDataTable and populate it with data."""

        class TableForm(forms.Form):
            name = forms.CharField()
            value = forms.IntegerField()

        TableFormset = forms.formsets.formset_factory(TableForm, extra=0)

        class Table(table_formset.FormsetDataTable):
            formset_class = TableFormset

            name = tables.Column('name')
            value = tables.Column('value')

            class Meta(object):
                name = 'table'

        table = Table(self.request)
        table.data = TEST_DATA_4
        formset = table.get_formset()
        self.assertEqual(2, len(formset))
        form = formset[0]
        form_data = form.initial
        self.assertEqual('object_1', form_data['name'])
        self.assertEqual(2, form_data['value'])


class MyException(Exception):
    pass


class OtherException(Exception):
    pass


class BatchActionDecoratorTests(unittest.TestCase):

    def setUp(self):
        obj = uuid.uuid4()
        self.obj_id = 'id-%s' % str(obj)
        self.obj_name = 'name-%s' % str(obj)
        table = mock.Mock()
        table.get_object_by_id.return_value = obj
        table.get_object_display.return_value = self.obj_name

        # getLogger is called insdie handle_exception_with_detail_message
        # decorator, so this needs to be mocked before using the decorator.
        self.logger = mock.Mock()
        with mock.patch('logging.getLogger',
                        return_value=self.logger) as mock_getlogger:
            class MyAction(object):
                def __init__(self, table):
                    self.table = table

                @actions.handle_exception_with_detail_message(
                    'normal log message %(id)s %(exc)s',
                    MyException,
                    'target log message %(id)s %(exc)s',
                    'target user message %(name)s %(exc)s',
                    'mylogger')
                def action(self, request, obj_id):
                    self._to_be_mocked()

                # This is required because if mock.patch replaces
                # a decorated method. We are testing a decorated method
                # so we need a separate method to mock,
                def _to_be_mocked(self):
                    pass

        self.action = MyAction(table)
        self.mock_getlogger = mock_getlogger

    def test_normal_exception(self):
        myexc = OtherException()
        with mock.patch.object(self.action, '_to_be_mocked',
                               side_effect=myexc):
            self.assertRaises(OtherException,
                              self.action.action,
                              mock.sentinel.request, self.obj_id)
        self.mock_getlogger.assert_called_once_with('mylogger')
        self.logger.info.assert_called_once_with(
            'normal log message %(id)s %(exc)s',
            {'id': self.obj_id, 'exc': myexc})

    def test_target_exception(self):
        myexc = MyException()
        handled = exceptions.HandledException(myexc)
        with mock.patch.object(self.action, '_to_be_mocked',
                               side_effect=myexc), \
                mock.patch.object(exceptions, 'handle',
                                  side_effect=handled) as mocked_handle:
            self.assertRaises(exceptions.HandledException,
                              self.action.action,
                              mock.sentinel.request, self.obj_id)
        self.mock_getlogger.assert_called_once_with('mylogger')
        self.logger.info.assert_called_once_with(
            'target log message %(id)s %(exc)s',
            {'id': self.obj_id, 'exc': myexc})
        mocked_handle.assert_called_once_with(
            mock.sentinel.request,
            'target user message %(name)s %(exc)s' %
            {'name': self.obj_name, 'exc': myexc},
            escalate=True)
