CRUD Base Views
Developer guide for the Enezaframework. Part of the Developer documentation.
Table of Contents
- Overview
- View Inheritance
BaseListViewBaseDetailViewBaseCreateViewBaseDeleteViewBaseParameterBaseAutocompleteView- Request Lifecycle and HTMX
- Change History
- 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 pageView 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 --|> AutocompleteModelViewConcrete 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 partialcore/partials/list_partial.html); children usually do not settemplate_name. - Requires:
model, aset_list_fieldsmethod, and aset_search_fieldsmethod. - Search:
set_search_fieldsis also required by the base class. Return an empty list when no search is needed. - Entity scoping:
get_querysetfilters byrequest.current_entityautomatically when the model has anentityFK, and orders by-created_at.
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_fieldsandset_search_fieldsraiseNotImplementedErrorin the base view if not overridden (enezaframework/views/base.py). Implement both; return[]fromset_search_fieldsto 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
editquery flag is set it renderscore/partials/main_object_form.html. - Requires:
modeland adetail_ui_viewmethod describing the tab/column layout. - Inlines: implement
get_inline_related_objectsto show related objects as inline tabs. - One-to-one resolution: override
get_related_object_idwhen a one-to-one inline must be resolved. - Actions: define
action_list(custom actions) andprint_actions(print reports) to expose buttons.
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
partyforeign key,get_inline_related_objectsalso 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:
modeland adetail_ui_viewmethod. - Fields listed in
disable_fieldsare typically server-generated or read-only during creation.
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.
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:
modeland adetail_ui_viewmethod. get_object()returns the first record for the current entity, orNone(create mode).- You typically do not create separate list/detail/create views for such a model; the parameter view both displays and updates it.
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_requiredondispatch. - 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, andSaveQueryDetailViewinheritFullPageFallbackMixin(enezaframework/views/mixins.py), which wraps the partial incore/full_page_template.html(extendingcomponents/layouts/default.html) for non-HTMX requests.BaseCreateViewredirects 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-Requestheader is present. - Same URL, two representations requires
Vary: HX-Request.HtmxVaryMiddleware(enezaframework/middleware/htmx_vary.py) appendsHX-Requestto every response'sVaryheader and, in DEBUG, setsCache-Control: no-storeon 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) treatHX-Requestorpartial=1as a partial request. - Do not register a partial template as the
template_nameof 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. |