from __future__ import annotations
from copy import deepcopy
from typing import Any, cast
from django.core.exceptions import ImproperlyConfigured
from .base import Block
[docs]class DatagridItem(Block):
"""
Implements a `Tabler datagrid-item
<https://preview.tabler.io/docs/datagrid.html>`_ It should be used with
:py:class:`Datagrid`.
Note:
Unlike :py:class:`wildewidgets.widgets.base.Block`,
:py:class:`DatagridItem` requires either :py:attr:`contents` to be set,
or the block contents to be provided as positional arguments.
Examples:
Create a :py:class:`DatagridItem` with a title and content:
.. code-block:: python
from wildewidgets import DatagridItem, Datagrid
item = DatagridItem(
"This is the content of the item.",
title="My Item",
url="https://example.com"
)
grid = Datagrid(item)
# or
grid = Datagrid()
grid.add_item(item)
Args:
*blocks: strings or :py:class:`wildewidgets.widgets.base.Block` objects
to add to our ``datagrid-item``. If :py:attr:`contents` is not set,
these blocks will be used as the contents of the ``datagrid-item``.
Keyword Args:
title: the ``datagrid-title`` of the ``datagrid-item``
url: URL to use to turn content into a hyperlink
Raises:
ValueError: either the ``title`` was empty, or no contents were provided
ImproperlyConfigured: :py:attr:`url` was set, and there is more than one block
in :py:attr:`contents`
"""
block: str = "datagrid-item"
#: the ``datagrid-title`` of the ``datagrid-item``
title: str | None = None
#: a URL to use to turn our contents into a hyperlink
url: str | None = None
def __init__(
self,
*blocks: Any,
title: str | None = None,
url: str | None = None,
**kwargs: Any,
) -> None:
self.title = title or self.title
assert self.title is not None, "title is required" # noqa: S101
if not self.title:
msg = (
'"title" is required as either a keyword argument or as a class '
"attribute"
)
raise ValueError(msg)
self.url = url or self.url
super().__init__(*blocks, **kwargs)
[docs] def add_blocks(self) -> None:
"""
Add our content.
If :py:attr:`url` is set, and there is only one block in
:py:attr:`contents`, wrap that block in a :py:class:`wildewidgets.Link`.
Raises:
ImproperlyConfigured: :py:attr:`url` was set, and there is more than
one block in :py:attr:`contents`
"""
if self.url:
if len(self.contents) == 1:
wrapper: Block = Block(
self.contents[0],
tag="a",
attributes={"href": self.url},
name="datagrid-content",
)
else:
msg = (
f"{self.__class__.__name__}: url should only be set if contents "
"are a single block"
)
raise ImproperlyConfigured(msg)
elif len(self.contents) == 1:
if not isinstance(self.contents[0], Block):
# Wrap the text in a block to allow us to assign the datagrid-content
# class to it
wrapper = Block(self.contents[0], name="datagrid-content")
else:
wrapper = self.contents[0]
else:
wrapper = Block(name="datagrid-content")
for block in self.contents:
wrapper.add_block(block)
self.add_block(Block(self.title, name="datagrid-title")) # type: ignore[arg-type]
self.add_block(wrapper)
DatagridItemDef = DatagridItem | tuple[str, str] | tuple[str, str, dict[str, Any]]
[docs]class Datagrid(Block):
"""
Implements a `Tabler Data grid <https://preview.tabler.io/docs/datagrid.html>`_
It contains :py:class:`DatagridItem` objects.
Examples:
Add :py:class:`DatagridItem` objects to this in one of these ways:
As constructor arguments::
>>> item1 = DatagridItem(title='foo', content='bar', url="https://example.com")
>>> item2 = DatagridItem(title='baz', content='barney')
>>> item3 = ['foo', 'bar']
>>> grid = Datagrid(item1, item2, item3)
By using :py:meth:`add_block` with a :py:class:`DatagridItem`:
>>> grid = Datagrid(item1, item2, item3)
>>> grid.add_block(DatagridItem(title='foo', content='bar'))
By using :py:meth:`add_item`::
>>> grid = Datagrid(item1, item2, item3)
>>> grid.add_item('foo', 'bar')
Args:
*items: a list of ``datagrid-item`` definitions or
*:py:class:`DatagridItem` objects.
"""
block: str = "datagrid"
#: a list of ``datagrid-items`` to add to our content
items: list[DatagridItemDef] = [] # noqa: RUF012
def __init__(self, *items: DatagridItemDef, **kwargs: Any) -> None:
self.items = list(items) if items else deepcopy(self.items)
super().__init__(**kwargs)
for item in items:
if isinstance(item, DatagridItem):
self.add_block(item)
elif isinstance(item, tuple):
if len(item) == 2: # noqa: PLR2004
item = cast("tuple[str, str]", item)
self.add_item(item[0], item[1])
else:
item = cast("tuple[str, str, dict[str, Any]]", item)
self.add_item(item[0], item[1], **item[2])
[docs] def add_item(
self,
title: str,
content: str | Block | list[str | Block],
url: str | None = None,
**kwargs: Any,
) -> None:
"""
Add a :py:class:`DatagridItem` to our block contents, with
``datagrid-title`` of ``title`` and datagrid.
Examples:
Start with our grid::
>>> dg = DataGrid()
Add a simple key/value::
>>> dg.add_item('Version', '1.2.3')
Add a block as the value, and wrap it in a :py:class:`wildewdigets.Link`::
>>> dg.add_item(
'Gravatar',
Image(src=static('myapp/images/gravatar.png'), alt='MyGravatar'),
url='https://www.google.com'
)
Add a list of blocks as the value::
>>> dg.add_item(
'Contributors',
[
ImageLink(
src=static('myapp/images/fred-gravatar.png',
alt='Fred'
url='https://www.fred.com'
),
ImageLink(
src=static('myapp/images/barney-gravatar.png',
alt='Barney'
url='https://www.barney.com'
)
],
css_class='d-flex flex-row'
)
Note:
To add a :py:class:`DatagridItem` directly, use :py:meth:`add_block`.
Keyword Args:
title: the ``datagrid-title`` of the ``datagrid-item``
content: the ``datagrid-content`` of the ``datagrid-item``
url: URL to use to turn content into a hyperlink
**kwargs: additional keyword arguments passed to the
:py:class:`DatagridItem` constructor
"""
if isinstance(content, (str, Block)):
content = [content]
self.add_block(DatagridItem(*content, title=title, url=url, **kwargs))