diff --git a/src/neapolitan/templatetags/neapolitan.py b/src/neapolitan/templatetags/neapolitan.py index 13f3559..f24c2ae 100644 --- a/src/neapolitan/templatetags/neapolitan.py +++ b/src/neapolitan/templatetags/neapolitan.py @@ -1,6 +1,4 @@ from django import template -from django.urls import reverse -from django.utils.safestring import mark_safe from neapolitan.views import Role @@ -8,13 +6,21 @@ def action_links(view, object): - actions = [ - (Role.DETAIL.reverse(view, object), "View"), - (Role.UPDATE.reverse(view, object), "Edit"), - (Role.DELETE.reverse(view, object), "Delete"), - ] - links = [f"{anchor_text}" for url, anchor_text in actions] - return mark_safe(" | ".join(links)) + actions = { + "detail": { + "url": Role.DETAIL.reverse(view, object), + "text": "View", + }, + "update": { + "url": Role.UPDATE.reverse(view, object), + "text": "Edit", + }, + "delete": { + "url": Role.DELETE.reverse(view, object), + "text": "Delete", + }, + } + return actions @register.inclusion_tag("neapolitan/partial/detail.html") @@ -51,7 +57,7 @@ def object_list(objects, view): with links to view, edit, and delete views. """ - fields = view.fields + fields = view.get_list_fields() headers = [objects[0]._meta.get_field(f).verbose_name for f in fields] object_list = [ { diff --git a/src/neapolitan/views.py b/src/neapolitan/views.py index 3fb6405..5b72df8 100644 --- a/src/neapolitan/views.py +++ b/src/neapolitan/views.py @@ -82,7 +82,7 @@ def get_url(self, view_cls): return path( self.url_pattern(view_cls), view_cls.as_view(role=self), - name=f"{view_cls.url_base}-{self.url_name_component}" + name=f"{view_cls.url_base}-{self.url_name_component}", ) def reverse(self, view, object=None): @@ -98,7 +98,6 @@ def reverse(self, view, object=None): ) - class CRUDView(View): """ CRUDView is Neapolitan's core. It provides the standard list, detail, @@ -142,9 +141,14 @@ def list(self, request, *args, **kwargs): """GET handler for the list view.""" queryset = self.get_queryset() - filterset = self.get_filterset(queryset) - if filterset is not None: - queryset = filterset.qs + + self.filterset = self.get_filterset(queryset) + if self.filterset and ( + not self.filterset.is_bound + or self.filterset.is_valid() + or not self.get_strict() + ): + queryset = self.filterset.qs if not self.allow_empty and not queryset.exists(): raise Http404 @@ -157,7 +161,7 @@ def list(self, request, *args, **kwargs): page_obj=None, is_paginated=False, paginator=None, - filterset=filterset, + filter=filterset, ) else: # Paginated response @@ -167,7 +171,7 @@ def list(self, request, *args, **kwargs): page_obj=page, is_paginated=page.has_other_pages(), paginator=page.paginator, - filterset=filterset, + filter=filterset, ) return self.render_to_response(context) @@ -422,6 +426,39 @@ def render_to_response(self, context): request=self.request, template=self.get_template_names(), context=context ) + # Fields + + def get_fields(self): + # Construct the method name based on the current role. + # For example, if self.role is Role.LIST, method_name will be 'list_fields'. + method_name = f"{self.role}_fields" + + if self.fields is not None: + # Check if the attribute (method or property) with the constructed name exists in the class. + if hasattr(self, method_name): + # If the attribute exists, check if it is callable (i.e., if it's a method). + if callable(getattr(self, method_name)): + # If it's a callable method, call the method and return its result. + return getattr(self, method_name)() + # If the attribute exists but is not callable (e.g., a property), directly return its value. + return getattr(self, method_name) + + # If the specific role-based method or property does not exist, fall back to the default 'fields'. + return self.fields + + msg = "'%s' must define 'fields' or override 'get_fields()'" + raise ImproperlyConfigured(msg % self.__class__.__name__) + + def get_detail_fields(self): + if self.detail_fields: + return self.detail_fields + return self.fields + + def get_list_fields(self): + if self.list_fields: + return self.list_fields + return self.fields + # URLs and view callables @classonlymethod