CRUD Base Views

Developer guide for the Enezaframework. Part of the Developer documentation.

Table of Contents

  1. Overview
  2. View Inheritance
  3. BaseListView
  4. BaseDetailView
  5. BaseCreateView
  6. BaseDeleteView
  7. BaseParameter
  8. BaseAutocompleteView
  9. Request Lifecycle and HTMX
  10. Change History
  11. Related Guides

Overview

Enezaframework ships class-based views that implement common CRUD behavior and reuse generic templates. A child view normally only sets model and a few configuration methods. Every view should be decorated with @auto_url_register so its URLs are generated automatically.

sequenceDiagram
    participant Browser
    participant URL as urls.py
    participant View as BaseListView
    participant Model as Model
    participant Tmpl as Generic template

    Browser->>URL: GET /accountpayable/vendortable/
    URL->>View: dispatch()
    View->>View: set_list_fields()
    View->>View: set_search_fields()
    View->>Model: get_queryset()
    Model-->>View: queryset
    View->>Tmpl: render(context with fields + rows)
    Tmpl-->>Browser: HTML list page

View Inheritance

classDiagram
    class BaseListView
    class BaseDetailView
    class BaseCreateView
    class BaseDeleteView
    class BaseParameter
    class BaseAutocompleteView

    BaseListView --|> ListView
    BaseDetailView --|> DetailView
    BaseCreateView --|> CreateView
    BaseDeleteView --|> DeleteView
    BaseParameter --|> DetailView
    BaseAutocompleteView --|> AutocompleteModelView

Concrete framework subclasses (BaseSetupsDetailView, BaseSourceDocumentDetailView, BasejournalsDetailView, and so on) extend these core views; see Inline & Specialized Views. Custom actions use BaseAction; see Base Actions (Custom Actions).


BaseListView

Displays a list of records.

  • Template: generic list template supplied by Enezaframework (core/list_page.html, with the HTMX partial core/partials/list_partial.html); children usually do not set template_name.
  • Requires: model, a set_list_fields method, and a set_search_fields method.
  • Search: set_search_fields is also required by the base class. Return an empty list when no search is needed.
  • Entity scoping: get_queryset filters by request.current_entity automatically when the model has an entity FK, and orders by -created_at.
python
from enezaframework.views import BaseListView
from enezaframework.utils import auto_url_register
from accountpayable.models import VendorTable


@auto_url_register
class VendorTableListView(BaseListView):
    model = VendorTable

    def set_list_fields(self):
        return ["vendorno", "searchname", "vendortype", "vendorgroup", "blocked"]

    def set_search_fields(self):
        return ["vendorno", "searchname"]

set_list_fields and set_search_fields raise NotImplementedError in the base view if not overridden (enezaframework/views/base.py). Implement both; return [] from set_search_fields to disable searching.


BaseDetailView

Displays a single record and handles editing (GET and POST). It is built from UrlMixin, UiMixin, ActionMixin, InlineMixin, WorkflowMixin, DetailView.

  • Template: generic detail/form template supplied by Enezaframework; when the edit query flag is set it renders core/partials/main_object_form.html.
  • Requires: model and a detail_ui_view method describing the tab/column layout.
  • Inlines: implement get_inline_related_objects to show related objects as inline tabs.
  • One-to-one resolution: override get_related_object_id when a one-to-one inline must be resolved.
  • Actions: define action_list (custom actions) and print_actions (print reports) to expose buttons.
python
from enezaframework.views import BaseDetailView
from enezaframework.utils import auto_url_register
from accountpayable.models import VendorTable


@auto_url_register
class VendorTableDetailView(BaseDetailView):
    model = VendorTable

    action_list = [
        {
            "name": "Print Name",
            "icon": "DocumentPlus",
            "url": "printname",
            "confirm_message": "Are you sure you want to print this name?",
        },
    ]

    def detail_ui_view(self):
        return [
            {
                "name": "Basic Information",
                "icon": "Home",
                "disable_fields": ["vendorno", "blocked", "vendortype"],
                "Fieldcolumns": [
                    {"fields": ["vendorno", "searchname", "vendorgroup"]},
                    {"fields": ["vendortype", "blocked"]},
                ],
            },
        ]

The full layout schema — disable_fields, disable_if, hide_if, headerpostion, and extended ManyToMany configuration — is documented in Field & Inline UI Configuration.

When the model has a party foreign key, get_inline_related_objects also auto-injects the party inlines (Address, Contact Information, and the matching Person/Business profile).


BaseCreateView

Creates a new record and reuses the same generic form templates as BaseDetailView.

  • Requires: model and a detail_ui_view method.
  • Fields listed in disable_fields are typically server-generated or read-only during creation.
python
from enezaframework.views import BaseCreateView
from enezaframework.utils import auto_url_register
from accountpayable.models import VendorTable


@auto_url_register
class VendorTableCreateView(BaseCreateView):
    model = VendorTable

    def detail_ui_view(self):
        return [
            {
                "name": "Basic Information",
                "icon": "Home",
                "disable_fields": ["vendorno"],
                "Fieldcolumns": [
                    {"fields": ["searchname", "vendorgroup", "vendortype"]},
                    {"fields": ["blocked"]},
                ],
            },
        ]

Inlines are usually not shown until the parent record exists. Either create the parent first, or load inlines via HTMX after the initial save.


BaseDeleteView

Deletes a record after a confirmation page.

python
from enezaframework.views import BaseDeleteView
from enezaframework.utils import auto_url_register
from accountpayable.models import VendorTable


@auto_url_register
class VendorTableDeleteView(BaseDeleteView):
    model = VendorTable

The framework handles the success redirect: it reverses {prefix}_list for the current app (using url_name when set, otherwise the model name).


BaseParameter

A specialized view for a model that has a single instance (or one instance per entity), such as application parameters.

  • class BaseParameter(UiMixin, InlineMixin, DetailView).
  • Expected attributes: model and a detail_ui_view method.
  • get_object() returns the first record for the current entity, or None (create mode).
  • You typically do not create separate list/detail/create views for such a model; the parameter view both displays and updates it.
python
from enezaframework.views import BaseParameter
from enezaframework.utils import auto_url_register
from accountpayable.models import PayablesSetup


@auto_url_register
class PayablesSetupView(BaseParameter):
    model = PayablesSetup

    def detail_ui_view(self):
        return [
            {
                "name": "General",
                "icon": "Home",
                "Fieldcolumns": [
                    {"fields": ["default_vendor_group", "payment_terms"]},
                ],
            },
        ]

BaseAutocompleteView

A thin subclass of django_tomselect's AutocompleteModelView used for ManyToMany autocomplete endpoints. It sets view_type = "autocomplete", so get_registered_urls generates {prefix}_autocomplete/ named {prefix}_autocomplete.


Request Lifecycle and HTMX

  • All base views are wrapped with login_required on dispatch.
  • Non-HTMX fallbacks are full pages. A URL that normally answers HTMX requests must never render a bare partial to a real browser navigation (duplicated tab, Back/Forward, refresh, native form submit). BaseDetailView, BaseParameter, BaseInlineDetailView, BasejournalsDetailView, and SaveQueryDetailView inherit FullPageFallbackMixin (enezaframework/views/mixins.py), which wraps the partial in core/full_page_template.html (extending components/layouts/default.html) for non-HTMX requests. BaseCreateView redirects non-HTMX GET/POST to the parent list URL.
  • Native (non-HTMX) POST to a detail/parameter view redirects back to the same URL as a GET (POST/Redirect/GET) instead of returning HttpResponseBadRequest.
  • List/detail GET requests render a full page for normal requests and a partial template when the HX-Request header is present.
  • Same URL, two representations requires Vary: HX-Request. HtmxVaryMiddleware (enezaframework/middleware/htmx_vary.py) appends HX-Request to every response's Vary header and, in DEBUG, sets Cache-Control: no-store on HTML responses. This stops a browser or proxy from caching a fragment under a page URL and later serving it to a navigation.
  • List shells must not fetch their own page URL. Where a full-page list template auto-loads its partial, it requests the list URL with ?partial=1 (e.g. core/list_page.html, journal/listpage.html, saved_query/list.html, task_history/list.html). BaseListView.get_template_names() (and the equivalent list views) treat HX-Request or partial=1 as a partial request.
  • Do not register a partial template as the template_name of a list/detail/create view reachable by real navigation. If a view only renders a fragment, redirect non-HTMX requests to its parent page.

The user is solely responsible for testing and validating view behavior; the developer/agent does not run the application.


Change History

Dated changes that affect this guide, taken from docs/Release note/CHANGELOG.md and the release notes. For the complete history, see the changelog.

Date Version What changed
2026-07-02 0.2.12 Fieldtab default_open added so accordion tabs can start collapsed.
2026-07-05 0.2.13 get_tab_groups() added to BaseDetailView, replacing hard-coded header/lines tabs; unmatched fieldtabs auto-placed in the first group.
2026-07-06 0.2.14 Saving a tabbed detail form now preserves the tab structure (detail_form_tabs_partial.html); get_template_names() resolves the tabbed template on edit=1 when tab groups exist.
2026-07-10 0.2.16 Optional url_name added to UrlMixin and to BaseDeleteView, BaseParameter, BaseAction, and the inline/journals delete views.
2026-08-23 0.2.19 All generated model forms use BaseEnezaModelForm, rendering Boolean fields as a Yes/No select; TomSelect auto-initializes after HTMX swaps.
2026-09-17 0.2.20 Non-HTMX navigations no longer render bare partials: FullPageFallbackMixin wraps detail/parameter views in the layout, native POST redirects (PRG), HtmxVaryMiddleware adds Vary: HX-Request, and list shells load their partial from ?partial=1.