Skip to content

add ability to customize how detail fields are rendered #88

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/django_twc_toolbox/crud/templatetags/neapolitan.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does overriding the neapolitan.py template make it work better with default templates, or does it create confusion when people start looking for what is going on, or why a new project without this template tag doesn't work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I kept it this way to ease the transition, but don't necessarily care about keeping the templatetag module named the same. I'd rather go for clarity.

Original file line number Diff line number Diff line change
Expand Up @@ -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()}

Expand Down
3 changes: 3 additions & 0 deletions src/django_twc_toolbox/crud/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
20 changes: 20 additions & 0 deletions tests/test_crud/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down