# 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
from mox3.mox import IsA
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', 'evil ', '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'),
)
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 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
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
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
class MyUpdateAction(tables.UpdateAction):
def allowed(self, *args):
return True
def update_cell(self, *args):
pass
class MyUpdateActionNotAllowed(MyUpdateAction):
def allowed(self, *args):
return False
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(required=True),
form_field_attributes={'class': 'test'},
update_action=MyUpdateAction)
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 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(required=True),
form_field_attributes={'class': 'test'},
update_action=MyUpdateActionNotAllowed)
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(required=True),
form_field_attributes={'class': 'test'},
update_action=MyUpdateActionNotAllowed,
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):
use_mox = True
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(),
['',
'',
'',
'',
'',
'',
''])
# Actions (these also test ordering)
self.assertQuerysetEqual(self.table.base_actions.values(),
['',
'',
'',
'',
'',
''])
self.assertQuerysetEqual(self.table.get_table_actions(),
['',
'',
'',
''])
self.assertQuerysetEqual(self.table.get_row_actions(TEST_DATA[0]),
['',
'',
'',
'',
''])
# 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(MyUpdateAction, name_column.update_action)
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 = ['']
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 = ['', '']
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(),
['',
''])
def test_table_force_no_actions_column(self):
class TempTable(MyTable):
class Meta(object):
columns = ('id',)
table_actions = (MyFilterAction, MyAction,)
row_actions = (MyAction, MyLinkAction,)
actions_column = False
self.table = TempTable(self.request, TEST_DATA)
self.assertQuerysetEqual(self.table.columns.values(),
['',
''])
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,)
self.table = TempTable(self.request, TEST_DATA)
self.assertQuerysetEqual(self.table.columns.values(),
['',
''])
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(),
['',
''])
def test_table_column_inheritance(self):
class TempTable(MyTable):
extra = tables.Column('extra')
class Meta(object):
name = "temp_table"
table_actions = (MyFilterAction, MyAction,)
row_actions = (MyAction, MyLinkAction,)
self.table = TempTable(self.request, TEST_DATA)
self.assertQuerysetEqual(self.table.columns.values(),
['',
'',
'',
'',
'',
'',
'',
'',
''])
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, ['',
'',
'',
'',
'',
'',
''])
# Verify we retrieve the right rows from our data
rows = self.table.get_rows()
self.assertQuerysetEqual(rows, ['',
'',
'',
''])
# Verify each row contains the right cells
self.assertQuerysetEqual(rows[0].get_cells(),
['',
'',
'',
'',
'',
'',
''])
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, "", 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, '', 1)
self.assertContains(resp_value, '', 1)
self.assertContains(resp_optional, 'not wrapped optional', 1)
self.assertNotContains(resp_optional, '')
self.assertNotContains(resp_optional, ' ')
def test_inline_edit_available_cell_rendering(self):
self.table = MyTable(self.request, TEST_DATA_2)
row = self.table.get_rows()[0]
name_cell = row.cells['name']
# Check if in-line edit is available in the cell,
# but is not in inline_edit_mod.
self.assertTrue(name_cell.inline_edit_available)
self.assertFalse(name_cell.inline_edit_mod)
# Check if is cell is rendered correctly.
name_cell_rendered = name_cell.render()
resp = http.HttpResponse(name_cell_rendered)
self.assertContains(resp, ' ',
count=1, html=True)
self.assertContains(resp, ' ',
count=1, html=True)
self.assertContains(resp,
''
'Verbose Name ',
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(),
[''])
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'},
update_action=MyUpdateAction)
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,
'',
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(),
['',
'',
'',
''])
# 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(),
['',
''])
# 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_inline_edit_update_action_get_non_ajax(self):
# Non ajax inline edit request should return None.
url = ('/my_url/?action=cell_update'
'&table=my_table&cell_name=name&obj_id=1')
req = self.factory.get(url, {})
self.table = MyTable(req, TEST_DATA_2)
handled = self.table.maybe_preempt()
# Checking the response header.
self.assertIsNone(handled)
def test_inline_edit_update_action_get(self):
# Get request should return td field with data.
url = ('/my_url/?action=cell_update'
'&table=my_table&cell_name=name&obj_id=1')
req = self.factory.get(url, {},
HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.table = MyTable(req, TEST_DATA_2)
handled = self.table.maybe_preempt()
# Checking the response header.
self.assertEqual(200, handled.status_code)
# Checking the response content.
resp = handled
self.assertContains(resp, ' ',
count=1, html=True)
self.assertContains(resp, 'Summary ', 1)
self.assertContains(res, '3.0 ', 1)
# Test again with the "sum" method.
table.columns['value'].summation = "sum"
res = http.HttpResponse(table.render())
self.assertContains(res, 'Summary', 1)
self.assertContains(res, '6 ', 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, ' 3.0')
self.assertNotContains(res, '6 ')
# 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, ' 3.0')
self.assertNotContains(res, '6 ')
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, "