diff --git a/src/django_twc_toolbox/crud/templatetags/neapolitan.py b/src/django_twc_toolbox/crud/templatetags/neapolitan.py index 177dd69..dc5deaf 100644 --- a/src/django_twc_toolbox/crud/templatetags/neapolitan.py +++ b/src/django_twc_toolbox/crud/templatetags/neapolitan.py @@ -49,7 +49,16 @@ def object_detail(object: models.Model, view: CRUDView): def iter() -> Generator[tuple[str, str], None, None]: for f in fields: mf = object._meta.get_field(f) - yield (cast(str, mf.verbose_name), str(getattr(object, f))) # type: ignore[union-attr] + rendered = str(getattr(object, f)) + + if renderer := view.detail_field_renderers.get(f): + if callable(renderer): + rendered = renderer(field=mf, object=object) + else: + msg = f"Field renderer for `{f}` should be a callable that returns a string" + raise template.TemplateSyntaxError(msg) + + yield (cast(str, mf.verbose_name), rendered) # type: ignore[union-attr] return {"object": iter()} diff --git a/src/django_twc_toolbox/crud/views.py b/src/django_twc_toolbox/crud/views.py index bbc110b..0081dc1 100644 --- a/src/django_twc_toolbox/crud/views.py +++ b/src/django_twc_toolbox/crud/views.py @@ -12,6 +12,7 @@ from django.http import HttpRequest from django.http import HttpResponse from django.template.response import TemplateResponse +from django.utils.safestring import SafeString from django_htmx.middleware import HtmxDetails from django_tables2 import tables from django_tables2.views import SingleTableMixin @@ -34,6 +35,8 @@ class CRUDView(NeapolitanCRUDView): detail_fields: ClassVar[list[str] | None] = None list_fields: ClassVar[list[str] | None] = None + detail_field_renderers: ClassVar[dict[str, Callable[..., str | SafeString]]] = {} + table_class: ClassVar[type[tables.Table] | None] = None table_data: ClassVar[dict[str, object] | None] = None diff --git a/tests/test_crud/test_templatetags.py b/tests/test_crud/test_templatetags.py index 19ef8bf..3bc2a9d 100644 --- a/tests/test_crud/test_templatetags.py +++ b/tests/test_crud/test_templatetags.py @@ -35,6 +35,26 @@ def test_object_detail(db): assert detail_list[1][1] == getattr(object, view.detail_fields[1]) +def test_object_detail_with_renderer(db): + def render_url(**kwargs): + return "https://example.com" + + class BookmarkDetailRendererView(BookmarkView): + detail_field_renderers = { + "url": render_url, + } + + view = BookmarkDetailRendererView(role=Role.DETAIL) + object = baker.make(Bookmark) + + detail = object_detail(object, view) + detail_list = list(detail["object"]) + + assert detail_list[0][0] == view.detail_fields[0] + assert detail_list[0][1] == "https://example.com" + assert detail_list[0][1] != getattr(object, view.detail_fields[0]) + + def test_object_list(db): view = BookmarkView(role=Role.LIST) objects = baker.make(Bookmark, _quantity=5)