# 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
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

import mock
import six

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", six.text_type(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", six.text_type(id_col))
        self.assertEqual("Verbose Name", six.text_type(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 ...',
                         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=six.text_type)

        # 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=six.text_type)

        # 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",
                         six.text_type(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",
                         six.text_type(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",
                         six.text_type(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", six.text_type(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=six.text_type)

        # 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=six.text_type)

        # 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",
                         six.text_type(table_actions[0].verbose_name))
        self.assertEqual("Delete Me",
                         six.text_type(table_actions[1].verbose_name))

        row_actions = self.table.get_row_actions(TEST_DATA[0])
        self.assertEqual("Delete Me",
                         six.text_type(row_actions[0].verbose_name))
        self.assertEqual("Log In",
                         six.text_type(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=six.text_type)

        # 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=six.text_type)

    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", six.text_type(id_col))
        self.assertEqual("Verbose Name", six.text_type(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=six.text_type)
        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)
