Initial commit
This commit is contained in:
266
env/lib/python3.10/site-packages/wagtail/contrib/table_block/blocks.py
vendored
Normal file
266
env/lib/python3.10/site-packages/wagtail/contrib/table_block/blocks.py
vendored
Normal file
@@ -0,0 +1,266 @@
|
||||
import json
|
||||
|
||||
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.utils import translation
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from wagtail.admin.staticfiles import versioned_static
|
||||
from wagtail.blocks import FieldBlock
|
||||
from wagtail.telepath import register
|
||||
from wagtail.widget_adapters import WidgetAdapter
|
||||
|
||||
DEFAULT_TABLE_OPTIONS = {
|
||||
"minSpareRows": 0,
|
||||
"startRows": 3,
|
||||
"startCols": 3,
|
||||
"colHeaders": False,
|
||||
"rowHeaders": False,
|
||||
"contextMenu": [
|
||||
"row_above",
|
||||
"row_below",
|
||||
"---------",
|
||||
"col_left",
|
||||
"col_right",
|
||||
"---------",
|
||||
"remove_row",
|
||||
"remove_col",
|
||||
"---------",
|
||||
"undo",
|
||||
"redo",
|
||||
],
|
||||
"editor": "text",
|
||||
"stretchH": "all",
|
||||
"height": 108,
|
||||
"renderer": "text",
|
||||
"autoColumnSize": False,
|
||||
}
|
||||
|
||||
|
||||
class TableInput(forms.HiddenInput):
|
||||
def __init__(self, table_options=None, attrs=None):
|
||||
self.table_options = table_options
|
||||
super().__init__(attrs=attrs)
|
||||
|
||||
@cached_property
|
||||
def media(self):
|
||||
return forms.Media(
|
||||
css={
|
||||
"all": [
|
||||
versioned_static(
|
||||
"table_block/css/vendor/handsontable-6.2.2.full.min.css"
|
||||
),
|
||||
]
|
||||
},
|
||||
js=[
|
||||
versioned_static(
|
||||
"table_block/js/vendor/handsontable-6.2.2.full.min.js"
|
||||
),
|
||||
versioned_static("table_block/js/table.js"),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
class TableInputAdapter(WidgetAdapter):
|
||||
js_constructor = "wagtail.widgets.TableInput"
|
||||
|
||||
def js_args(self, widget):
|
||||
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 column as a header": _(
|
||||
"Display the first column as a header"
|
||||
),
|
||||
"Column header": _("Column 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"),
|
||||
"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."
|
||||
),
|
||||
"Table": _("Table"),
|
||||
}
|
||||
|
||||
return [
|
||||
widget.table_options,
|
||||
strings,
|
||||
]
|
||||
|
||||
|
||||
register(TableInputAdapter(), TableInput)
|
||||
|
||||
|
||||
class TableBlock(FieldBlock):
|
||||
def __init__(self, required=True, help_text=None, table_options=None, **kwargs):
|
||||
"""
|
||||
CharField's 'label' and 'initial' parameters are not exposed, as Block
|
||||
handles that functionality natively (via 'label' and 'default')
|
||||
|
||||
CharField's 'max_length' and 'min_length' parameters are not exposed as table
|
||||
data needs to have arbitrary length
|
||||
"""
|
||||
self.table_options = self.get_table_options(table_options=table_options)
|
||||
self.field_options = {"required": required, "help_text": help_text}
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@cached_property
|
||||
def field(self):
|
||||
return forms.CharField(
|
||||
widget=TableInput(table_options=self.table_options), **self.field_options
|
||||
)
|
||||
|
||||
def value_from_form(self, value):
|
||||
return json.loads(value)
|
||||
|
||||
def value_for_form(self, 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 would be a breaking change.
|
||||
"""
|
||||
if value and 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):
|
||||
# pass state to frontend as a JSON-ish dict - do not serialise to a JSON string
|
||||
return value
|
||||
|
||||
def is_html_renderer(self):
|
||||
return self.table_options["renderer"] == "html"
|
||||
|
||||
def get_searchable_content(self, value):
|
||||
content = []
|
||||
if value:
|
||||
for row in value.get("data", []):
|
||||
content.extend([v for v in row if v])
|
||||
return content
|
||||
|
||||
def render(self, value, context=None):
|
||||
template = getattr(self.meta, "template", None)
|
||||
if template and value:
|
||||
table_header = (
|
||||
value["data"][0]
|
||||
if value.get("data", None)
|
||||
and len(value["data"]) > 0
|
||||
and value.get("first_row_is_table_header", False)
|
||||
else None
|
||||
)
|
||||
first_col_is_header = value.get("first_col_is_header", False)
|
||||
|
||||
if context is None:
|
||||
new_context = {}
|
||||
else:
|
||||
new_context = dict(context)
|
||||
|
||||
new_context.update(
|
||||
{
|
||||
"self": value,
|
||||
self.TEMPLATE_VAR: value,
|
||||
"table_header": table_header,
|
||||
"first_col_is_header": first_col_is_header,
|
||||
"html_renderer": self.is_html_renderer(),
|
||||
"table_caption": value.get("table_caption"),
|
||||
"data": value["data"][1:]
|
||||
if table_header
|
||||
else value.get("data", []),
|
||||
}
|
||||
)
|
||||
|
||||
if value.get("cell"):
|
||||
new_context["classnames"] = {}
|
||||
new_context["hidden"] = {}
|
||||
for meta in value["cell"]:
|
||||
if "className" in meta:
|
||||
new_context["classnames"][(meta["row"], meta["col"])] = meta[
|
||||
"className"
|
||||
]
|
||||
if "hidden" in meta:
|
||||
new_context["hidden"][(meta["row"], meta["col"])] = meta[
|
||||
"hidden"
|
||||
]
|
||||
|
||||
if value.get("mergeCells"):
|
||||
new_context["spans"] = {}
|
||||
for merge in value["mergeCells"]:
|
||||
new_context["spans"][(merge["row"], merge["col"])] = {
|
||||
"rowspan": merge["rowspan"],
|
||||
"colspan": merge["colspan"],
|
||||
}
|
||||
|
||||
return render_to_string(template, new_context)
|
||||
else:
|
||||
return self.render_basic(value or "", context=context)
|
||||
|
||||
def get_table_options(self, table_options=None):
|
||||
"""
|
||||
Return a dict of table options using the defaults unless custom options provided
|
||||
|
||||
table_options can contain any valid handsontable options:
|
||||
https://handsontable.com/docs/6.2.2/Options.html
|
||||
contextMenu: if value from table_options is True, still use default
|
||||
language: if value is not in table_options, attempt to get from environment
|
||||
"""
|
||||
|
||||
collected_table_options = DEFAULT_TABLE_OPTIONS.copy()
|
||||
|
||||
if table_options is not None:
|
||||
if table_options.get("contextMenu", None) is True:
|
||||
# explicitly check for True, as value could also be array
|
||||
# delete to ensure the above default is kept for contextMenu
|
||||
del table_options["contextMenu"]
|
||||
collected_table_options.update(table_options)
|
||||
|
||||
if "language" not in collected_table_options:
|
||||
# attempt to gather the current set language of not provided
|
||||
language = translation.get_language()
|
||||
collected_table_options["language"] = language
|
||||
|
||||
return collected_table_options
|
||||
|
||||
class Meta:
|
||||
default = None
|
||||
template = "table_block/blocks/table.html"
|
||||
icon = "table"
|
||||
Reference in New Issue
Block a user