mirror of
https://github.com/wagtail/wagtail.git
synced 2024-11-29 01:22:07 +01:00
Move to a select field for determining the way TableBlock will use headers
- Make the new table_header_choice select backwards compatible with tables stored before Wagtail 6.0 - Built on previous PRs #9673 & #6763 - Fixes #5989
This commit is contained in:
parent
a4c18b4957
commit
fe1a306285
@ -38,6 +38,7 @@ Changelog
|
|||||||
* Move locale selector in generic IndexView to a filter (Sage Abdullah)
|
* Move locale selector in generic IndexView to a filter (Sage Abdullah)
|
||||||
* Add ability to customise a page's copy form (Neeraj Yetheendran)
|
* Add ability to customise a page's copy form (Neeraj Yetheendran)
|
||||||
* Add optional caption field to `TypedTableBlock` (Tommaso Amici, Cynthia Kiser)
|
* Add optional caption field to `TypedTableBlock` (Tommaso Amici, Cynthia Kiser)
|
||||||
|
* Switch the `TableBlock` header controls to a field that requires user input (Bhuvnesh Sharma, Aman Pandey, Cynthia Kiser)
|
||||||
* Fix: Update system check for overwriting storage backends to recognise the `STORAGES` setting introduced in Django 4.2 (phijma-leukeleu)
|
* Fix: Update system check for overwriting storage backends to recognise the `STORAGES` setting introduced in Django 4.2 (phijma-leukeleu)
|
||||||
* Fix: Prevent password change form from raising a validation error when browser autocomplete fills in the "Old password" field (Chiemezuo Akujobi)
|
* Fix: Prevent password change form from raising a validation error when browser autocomplete fills in the "Old password" field (Chiemezuo Akujobi)
|
||||||
* Fix: Ensure that the legacy dropdown options, when closed, do not get accidentally clicked by other interactions wide viewports (CheesyPhoenix, Christer Jensen)
|
* Fix: Ensure that the legacy dropdown options, when closed, do not get accidentally clicked by other interactions wide viewports (CheesyPhoenix, Christer Jensen)
|
||||||
|
@ -3,26 +3,23 @@
|
|||||||
exports[`telepath: wagtail.widgets.TableInput it renders correctly 1`] = `
|
exports[`telepath: wagtail.widgets.TableInput it renders correctly 1`] = `
|
||||||
"<div>
|
"<div>
|
||||||
<div class="w-field__wrapper" data-field-wrapper="">
|
<div class="w-field__wrapper" data-field-wrapper="">
|
||||||
<label class="w-field__label" for="the-id-handsontable-header">Row header</label>
|
<label class="w-field__label" for="the-id-table-header-choice">Table headers</label>
|
||||||
<div class="w-field w-field--boolean_field w-field--checkbox_input" data-field="">
|
<select id="the-id-table-header-choice" name="table-header-choice">
|
||||||
<div class="w-field__help" id="the-id-handsontable-header-helptext" data-field-help="">
|
<option value="">Select a header option</option>
|
||||||
<div class="help">Display the first row as a header.</div>
|
<option value="row">
|
||||||
</div>
|
Display the first row as a header
|
||||||
<div class="w-field__input" data-field-input="">
|
</option>
|
||||||
<input type="checkbox" id="the-id-handsontable-header" name="handsontable-header" aria-describedby="the-id-handsontable-header-helptext">
|
<option value="column">
|
||||||
</div>
|
Display the first column as a header
|
||||||
</div>
|
</option>
|
||||||
</div>
|
<option value="both">
|
||||||
<div class="w-field__wrapper" data-field-wrapper="">
|
Display the first row AND first column as headers
|
||||||
<label class="w-field__label" for="the-id-handsontable-col-header">Column header</label>
|
</option>
|
||||||
<div class="w-field w-field--boolean_field w-field--checkbox_input" data-field="">
|
<option value="neither">
|
||||||
<div class="w-field__help" id="the-id-handsontable-col-header-helptext" data-field-help="">
|
No headers
|
||||||
<div class="help">Display the first column as a header.</div>
|
</option>
|
||||||
</div>
|
</select>
|
||||||
<div class="w-field__input" data-field-input="">
|
<p class="help">Which cells should be displayed as headers?</p>
|
||||||
<input type="checkbox" id="the-id-handsontable-col-header" name="handsontable-col-header" aria-describedby="the-id-handsontable-col-header-helptext">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="w-field__wrapper" data-field-wrapper="">
|
<div class="w-field__wrapper" data-field-wrapper="">
|
||||||
<label class="w-field__label" for="the-id-handsontable-col-caption">Table caption</label>
|
<label class="w-field__label" for="the-id-handsontable-col-caption">Table caption</label>
|
||||||
@ -43,26 +40,23 @@ exports[`telepath: wagtail.widgets.TableInput it renders correctly 1`] = `
|
|||||||
exports[`telepath: wagtail.widgets.TableInput translation 1`] = `
|
exports[`telepath: wagtail.widgets.TableInput translation 1`] = `
|
||||||
"<div>
|
"<div>
|
||||||
<div class="w-field__wrapper" data-field-wrapper="">
|
<div class="w-field__wrapper" data-field-wrapper="">
|
||||||
<label class="w-field__label" for="the-id-handsontable-header">En-tête de ligne</label>
|
<label class="w-field__label" for="the-id-table-header-choice">En-têtes de tableau</label>
|
||||||
<div class="w-field w-field--boolean_field w-field--checkbox_input" data-field="">
|
<select id="the-id-table-header-choice" name="table-header-choice">
|
||||||
<div class="w-field__help" id="the-id-handsontable-header-helptext" data-field-help="">
|
<option value="">Select a header option</option>
|
||||||
<div class="help">Affichez la première ligne sous forme d'en-tête.</div>
|
<option value="row">
|
||||||
</div>
|
Afficher la première ligne sous forme d'en-tête
|
||||||
<div class="w-field__input" data-field-input="">
|
</option>
|
||||||
<input type="checkbox" id="the-id-handsontable-header" name="handsontable-header" aria-describedby="the-id-handsontable-header-helptext">
|
<option value="column">
|
||||||
</div>
|
Afficher la première colonne sous forme d'en-tête
|
||||||
</div>
|
</option>
|
||||||
</div>
|
<option value="both">
|
||||||
<div class="w-field__wrapper" data-field-wrapper="">
|
Afficher la première ligne ET la première colonne sous forme d'en-têtes
|
||||||
<label class="w-field__label" for="the-id-handsontable-col-header">En-tête de colonne</label>
|
</option>
|
||||||
<div class="w-field w-field--boolean_field w-field--checkbox_input" data-field="">
|
<option value="neither">
|
||||||
<div class="w-field__help" id="the-id-handsontable-col-header-helptext" data-field-help="">
|
Pas d'en-têtes
|
||||||
<div class="help">Affichez la première colonne sous forme d'en-tête.</div>
|
</option>
|
||||||
</div>
|
</select>
|
||||||
<div class="w-field__input" data-field-input="">
|
<p class="help">Quelles cellules doivent être affichées en tant qu'en-têtes?</p>
|
||||||
<input type="checkbox" id="the-id-handsontable-col-header" name="handsontable-col-header" aria-describedby="the-id-handsontable-col-header-helptext">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="w-field__wrapper" data-field-wrapper="">
|
<div class="w-field__wrapper" data-field-wrapper="">
|
||||||
<label class="w-field__label" for="the-id-handsontable-col-caption">Légende du tableau</label>
|
<label class="w-field__label" for="the-id-handsontable-col-caption">Légende du tableau</label>
|
||||||
|
@ -7,12 +7,14 @@ import { hasOwn } from '../../../utils/hasOwn';
|
|||||||
|
|
||||||
function initTable(id, tableOptions) {
|
function initTable(id, tableOptions) {
|
||||||
const containerId = id + '-handsontable-container';
|
const containerId = id + '-handsontable-container';
|
||||||
const tableHeaderCheckboxId = id + '-handsontable-header';
|
var tableHeaderId = id + '-handsontable-header';
|
||||||
const colHeaderCheckboxId = id + '-handsontable-col-header';
|
var colHeaderId = id + '-handsontable-col-header';
|
||||||
|
var headerChoiceId = id + '-table-header-choice';
|
||||||
const tableCaptionId = id + '-handsontable-col-caption';
|
const tableCaptionId = id + '-handsontable-col-caption';
|
||||||
const hiddenStreamInput = $('#' + id);
|
const hiddenStreamInput = $('#' + id);
|
||||||
const tableHeaderCheckbox = $('#' + tableHeaderCheckboxId);
|
var tableHeader = $('#' + tableHeaderId);
|
||||||
const colHeaderCheckbox = $('#' + colHeaderCheckboxId);
|
var colHeader = $('#' + colHeaderId);
|
||||||
|
var headerChoice = $('#' + headerChoiceId);
|
||||||
const tableCaption = $('#' + tableCaptionId);
|
const tableCaption = $('#' + tableCaptionId);
|
||||||
const finalOptions = {};
|
const finalOptions = {};
|
||||||
let hot = null;
|
let hot = null;
|
||||||
@ -52,18 +54,12 @@ function initTable(id, tableOptions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dataForForm !== null) {
|
if (dataForForm !== null) {
|
||||||
if (hasOwn(dataForForm, 'first_row_is_table_header')) {
|
|
||||||
tableHeaderCheckbox.prop(
|
|
||||||
'checked',
|
|
||||||
dataForForm.first_row_is_table_header,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (hasOwn(dataForForm, 'first_col_is_header')) {
|
|
||||||
colHeaderCheckbox.prop('checked', dataForForm.first_col_is_header);
|
|
||||||
}
|
|
||||||
if (hasOwn(dataForForm, 'table_caption')) {
|
if (hasOwn(dataForForm, 'table_caption')) {
|
||||||
tableCaption.prop('value', dataForForm.table_caption);
|
tableCaption.prop('value', dataForForm.table_caption);
|
||||||
}
|
}
|
||||||
|
if (hasOwn(dataForForm, 'table_header_choice')) {
|
||||||
|
headerChoice.prop('value', dataForForm.table_header_choice);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasOwn(tableOptions, 'width') || !hasOwn(tableOptions, 'height')) {
|
if (!hasOwn(tableOptions, 'width') || !hasOwn(tableOptions, 'height')) {
|
||||||
@ -123,8 +119,9 @@ function initTable(id, tableOptions) {
|
|||||||
data: hot.getData(),
|
data: hot.getData(),
|
||||||
cell: cell,
|
cell: cell,
|
||||||
mergeCells: mergeCells,
|
mergeCells: mergeCells,
|
||||||
first_row_is_table_header: tableHeaderCheckbox.prop('checked'),
|
first_row_is_table_header: tableHeader.val(),
|
||||||
first_col_is_header: colHeaderCheckbox.prop('checked'),
|
first_col_is_header: colHeader.val(),
|
||||||
|
table_header_choice: headerChoice.val(),
|
||||||
table_caption: tableCaption.val(),
|
table_caption: tableCaption.val(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -169,11 +166,7 @@ function initTable(id, tableOptions) {
|
|||||||
persist();
|
persist();
|
||||||
};
|
};
|
||||||
|
|
||||||
tableHeaderCheckbox.on('change', () => {
|
headerChoice.on('change', () => {
|
||||||
persist();
|
|
||||||
});
|
|
||||||
|
|
||||||
colHeaderCheckbox.on('change', () => {
|
|
||||||
persist();
|
persist();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -238,26 +231,23 @@ class TableInput {
|
|||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="w-field__wrapper" data-field-wrapper>
|
<div class="w-field__wrapper" data-field-wrapper>
|
||||||
<label class="w-field__label" for="${id}-handsontable-header">${this.strings['Row header']}</label>
|
<label class="w-field__label" for="${id}-table-header-choice">${this.strings['Table headers']}</label>
|
||||||
<div class="w-field w-field--boolean_field w-field--checkbox_input" data-field>
|
<select id="${id}-table-header-choice" name="table-header-choice">
|
||||||
<div class="w-field__help" id="${id}-handsontable-header-helptext" data-field-help>
|
<option value="">Select a header option</option>
|
||||||
<div class="help">${this.strings['Display the first row as a header.']}</div>
|
<option value="row">
|
||||||
</div>
|
${this.strings['Display the first row as a header']}
|
||||||
<div class="w-field__input" data-field-input>
|
</option>
|
||||||
<input type="checkbox" id="${id}-handsontable-header" name="handsontable-header" aria-describedby="${id}-handsontable-header-helptext" />
|
<option value="column">
|
||||||
</div>
|
${this.strings['Display the first column as a header']}
|
||||||
</div>
|
</option>
|
||||||
</div>
|
<option value="both">
|
||||||
<div class="w-field__wrapper" data-field-wrapper>
|
${this.strings['Display the first row AND first column as headers']}
|
||||||
<label class="w-field__label" for="${id}-handsontable-col-header">${this.strings['Column header']}</label>
|
</option>
|
||||||
<div class="w-field w-field--boolean_field w-field--checkbox_input" data-field>
|
<option value="neither">
|
||||||
<div class="w-field__help" id="${id}-handsontable-col-header-helptext" data-field-help>
|
${this.strings['No headers']}
|
||||||
<div class="help">${this.strings['Display the first column as a header.']}</div>
|
</option>
|
||||||
</div>
|
</select>
|
||||||
<div class="w-field__input" data-field-input>
|
<p class="help">${this.strings['Which cells should be displayed as headers?']}</p>
|
||||||
<input type="checkbox" id="${id}-handsontable-col-header" name="handsontable-col-header" aria-describedby="${id}-handsontable-col-header-helptext" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="w-field__wrapper" data-field-wrapper>
|
<div class="w-field__wrapper" data-field-wrapper>
|
||||||
<label class="w-field__label" for="${id}-handsontable-col-caption">${this.strings['Table caption']}</label>
|
<label class="w-field__label" for="${id}-handsontable-col-caption">${this.strings['Table caption']}</label>
|
||||||
|
@ -33,11 +33,15 @@ const TEST_OPTIONS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TEST_STRINGS = {
|
const TEST_STRINGS = {
|
||||||
'Row header': 'Row header',
|
'Table headers': 'Table headers',
|
||||||
'Display the first row as a header.': 'Display the first row as a header.',
|
'Display the first row as a header': 'Display the first row as a header',
|
||||||
'Column header': 'Column header',
|
'Display the first column as a header':
|
||||||
'Display the first column as a header.':
|
'Display the first column as a header',
|
||||||
'Display the first column as a header.',
|
'Display the first row AND first column as headers':
|
||||||
|
'Display the first row AND first column as headers',
|
||||||
|
'No headers': 'No headers',
|
||||||
|
'Which cells should be displayed as headers?':
|
||||||
|
'Which cells should be displayed as headers?',
|
||||||
'Table caption': 'Table caption',
|
'Table caption': 'Table caption',
|
||||||
|
|
||||||
'A heading that identifies the overall topic of the table, and is useful for screen reader users.':
|
'A heading that identifies the overall topic of the table, and is useful for screen reader users.':
|
||||||
@ -155,12 +159,16 @@ describe('telepath: wagtail.widgets.TableInput', () => {
|
|||||||
|
|
||||||
test('translation', () => {
|
test('translation', () => {
|
||||||
testStrings = {
|
testStrings = {
|
||||||
'Row header': 'En-tête de ligne',
|
'Table headers': 'En-têtes de tableau',
|
||||||
'Display the first row as a header.':
|
'Display the first row as a header':
|
||||||
"Affichez la première ligne sous forme d'en-tête.",
|
"Afficher la première ligne sous forme d'en-tête",
|
||||||
'Column header': 'En-tête de colonne',
|
'Display the first column as a header':
|
||||||
'Display the first column as a header.':
|
"Afficher la première colonne sous forme d'en-tête",
|
||||||
"Affichez la première colonne sous forme d'en-tête.",
|
'Display the first row AND first column as headers':
|
||||||
|
"Afficher la première ligne ET la première colonne sous forme d'en-têtes",
|
||||||
|
'No headers': "Pas d'en-têtes",
|
||||||
|
'Which cells should be displayed as headers?':
|
||||||
|
"Quelles cellules doivent être affichées en tant qu'en-têtes?",
|
||||||
'Table caption': 'Légende du tableau',
|
'Table caption': 'Légende du tableau',
|
||||||
|
|
||||||
'A heading that identifies the overall topic of the table, and is useful for screen reader users.':
|
'A heading that identifies the overall topic of the table, and is useful for screen reader users.':
|
||||||
|
@ -60,6 +60,7 @@ Thank you to Thibaud Colas and Badr Fourane for their work on this feature.
|
|||||||
* Show character counts on RichTextBlock with `max_length` (Elhussein Almasri)
|
* Show character counts on RichTextBlock with `max_length` (Elhussein Almasri)
|
||||||
* Move locale selector in generic IndexView to a filter (Sage Abdullah)
|
* Move locale selector in generic IndexView to a filter (Sage Abdullah)
|
||||||
* Add optional caption field to `TypedTableBlock` (Tommaso Amici, Cynthia Kiser)
|
* Add optional caption field to `TypedTableBlock` (Tommaso Amici, Cynthia Kiser)
|
||||||
|
* Switch the `TableBlock` header controls to a field that requires user input (Bhuvnesh Sharma, Aman Pandey, Cynthia Kiser)
|
||||||
|
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.forms.fields import Field
|
||||||
|
from django.forms.utils import ErrorList
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
@ -68,12 +71,18 @@ class TableInputAdapter(WidgetAdapter):
|
|||||||
def js_args(self, widget):
|
def js_args(self, widget):
|
||||||
strings = {
|
strings = {
|
||||||
"Row header": _("Row header"),
|
"Row header": _("Row header"),
|
||||||
"Display the first row as a header.": _(
|
"Table headers": _("Table headers"),
|
||||||
"Display the first row as a header."
|
"Display the first row as a header": _("Display the first row as a header"),
|
||||||
|
"Display the first column as a header": _(
|
||||||
|
"Display the first column as a header"
|
||||||
),
|
),
|
||||||
"Column header": _("Column header"),
|
"Column header": _("Column header"),
|
||||||
"Display the first column as a header.": _(
|
"Display the first row AND first column as headers": _(
|
||||||
"Display the first column as a header."
|
"Display the first row AND first column as headers"
|
||||||
|
),
|
||||||
|
"No headers": _("No headers"),
|
||||||
|
"Which cells should be displayed as headers?": _(
|
||||||
|
"Which cells should be displayed as headers?"
|
||||||
),
|
),
|
||||||
"Table caption": _("Table caption"),
|
"Table caption": _("Table caption"),
|
||||||
"A heading that identifies the overall topic of the table, and is useful for screen reader users.": _(
|
"A heading that identifies the overall topic of the table, and is useful for screen reader users.": _(
|
||||||
@ -117,6 +126,44 @@ class TableBlock(FieldBlock):
|
|||||||
def value_for_form(self, value):
|
def value_for_form(self, value):
|
||||||
return json.dumps(value)
|
return json.dumps(value)
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
"""
|
||||||
|
If value came from a table block stored before Wagtail 6.0, we need to set an appropriate
|
||||||
|
value for the header choice. I would really like to have this default to "" and force the
|
||||||
|
editor to reaffirm they don't want any headers, but that woud be a breaking change.
|
||||||
|
"""
|
||||||
|
if not value.get("table_header_choice", ""):
|
||||||
|
if value.get("first_row_is_table_header", False) and value.get(
|
||||||
|
"first_col_is_header", False
|
||||||
|
):
|
||||||
|
value["table_header_choice"] = "both"
|
||||||
|
elif value.get("first_row_is_table_header", False):
|
||||||
|
value["table_header_choice"] = "row"
|
||||||
|
elif value.get("first_col_is_header", False):
|
||||||
|
value["table_header_choice"] = "col"
|
||||||
|
else:
|
||||||
|
value["table_header_choice"] = "neither"
|
||||||
|
return value
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
if not value:
|
||||||
|
return value
|
||||||
|
|
||||||
|
if value.get("table_header_choice", ""):
|
||||||
|
value["first_row_is_table_header"] = value["table_header_choice"] in [
|
||||||
|
"row",
|
||||||
|
"both",
|
||||||
|
]
|
||||||
|
value["first_col_is_header"] = value["table_header_choice"] in [
|
||||||
|
"column",
|
||||||
|
"both",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# Ensure we have a choice for the table_header_choice
|
||||||
|
errors = ErrorList(Field.default_error_messages["required"])
|
||||||
|
raise ValidationError("Validation error in TableBlock", params=errors)
|
||||||
|
return self.value_from_form(self.field.clean(self.value_for_form(value)))
|
||||||
|
|
||||||
def get_form_state(self, value):
|
def get_form_state(self, value):
|
||||||
# pass state to frontend as a JSON-ish dict - do not serialise to a JSON string
|
# pass state to frontend as a JSON-ish dict - do not serialise to a JSON string
|
||||||
return value
|
return value
|
||||||
|
@ -116,7 +116,7 @@ class TestTableBlock(TestCase):
|
|||||||
"""
|
"""
|
||||||
self.assertHTMLEqual(result, expected)
|
self.assertHTMLEqual(result, expected)
|
||||||
|
|
||||||
def test_do_not_render_html(self):
|
def test_do_not_render_html_by_default(self):
|
||||||
"""
|
"""
|
||||||
Ensure that raw html doesn't render
|
Ensure that raw html doesn't render
|
||||||
by default.
|
by default.
|
||||||
@ -145,6 +145,36 @@ class TestTableBlock(TestCase):
|
|||||||
result = block.render(value)
|
result = block.render(value)
|
||||||
self.assertHTMLEqual(result, expected)
|
self.assertHTMLEqual(result, expected)
|
||||||
|
|
||||||
|
def test_does_render_html_if_allowed(self):
|
||||||
|
"""
|
||||||
|
Ensure html renders if table_options set renderer to allow html
|
||||||
|
"""
|
||||||
|
value = {
|
||||||
|
"first_row_is_table_header": False,
|
||||||
|
"first_col_is_header": False,
|
||||||
|
"data": [
|
||||||
|
["<p><strong>Test</strong></p>", None, None],
|
||||||
|
[None, None, None],
|
||||||
|
[None, None, None],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = """
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><p><strong>Test</strong></p></td><td></td><td></td></tr>
|
||||||
|
<tr><td></td><td></td><td></td></tr>
|
||||||
|
<tr><td></td><td></td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_options = self.default_table_options.copy()
|
||||||
|
new_options["renderer"] = "html"
|
||||||
|
block = TableBlock(table_options=new_options)
|
||||||
|
result = block.render(value)
|
||||||
|
self.assertHTMLEqual(result, expected)
|
||||||
|
|
||||||
def test_row_headers(self):
|
def test_row_headers(self):
|
||||||
"""
|
"""
|
||||||
Ensure that row headers are properly rendered.
|
Ensure that row headers are properly rendered.
|
||||||
@ -226,6 +256,33 @@ class TestTableBlock(TestCase):
|
|||||||
result = block.render(value)
|
result = block.render(value)
|
||||||
self.assertHTMLEqual(result, expected)
|
self.assertHTMLEqual(result, expected)
|
||||||
|
|
||||||
|
def test_no_headers(self):
|
||||||
|
"""
|
||||||
|
Test table without headers.
|
||||||
|
"""
|
||||||
|
value = {
|
||||||
|
"first_row_is_table_header": False,
|
||||||
|
"first_col_is_header": False,
|
||||||
|
"data": [
|
||||||
|
["Foo", "Bar", "Baz"],
|
||||||
|
["one", "two", "three"],
|
||||||
|
["four", "five", "six"],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = """
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Foo</td><td>Bar</td><td>Baz</td></tr>
|
||||||
|
<tr><td>one</td><td>two</td><td>three</td></tr>
|
||||||
|
<tr><td>four</td><td>five</td><td>six</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
"""
|
||||||
|
block = TableBlock()
|
||||||
|
result = block.render(value)
|
||||||
|
self.assertHTMLEqual(result, expected)
|
||||||
|
|
||||||
def test_value_for_and_from_form(self):
|
def test_value_for_and_from_form(self):
|
||||||
"""
|
"""
|
||||||
Make sure we get back good json and make
|
Make sure we get back good json and make
|
||||||
|
Loading…
Reference in New Issue
Block a user