Inline & Specialized Views

Developer guide for the Enezaframework. Part of the architecture series.

Table of Contents

  1. Overview
  2. Inline Views
  3. One-to-Many Inline Views (List + Detail)
  4. BaseInlineOneToOneDetailView
  5. Specialized Views
  6. Journals Views
  7. Setup Views
  8. Document Views
  9. Inline Template Context
  10. Critical Rules
  11. Related Guides

Overview

Beyond the core CRUD views, Enezaframework provides two families of views:

  • Inline views manage records that belong to a parent record (one-to-many or one-to-one) and are rendered inside the parent's detail page.
  • Specialized views behave like the core views but are tailored to a class of models (journals, setups, documents).
classDiagram
    class BaseDetailView
    class BaseListView
    class BaseCreateView
    class BaseDeleteView
    class BaseInlineListView
    class BaseInlineDetailView
    class BaseInlineCreateView
    class BaseInlineDeleteView
    class BaseInlineOneToOneDetailView
    class BaseJournalsListView
    class BaseSetupsListView
    class BaseSourceDocumentListView

    BaseInlineListView --|> BaseListView
    BaseInlineDetailView --|> BaseDetailView
    BaseInlineCreateView --|> BaseCreateView
    BaseInlineDeleteView --|> BaseDeleteView
    BaseInlineOneToOneDetailView --|> BaseInlineDetailView
    BaseJournalsListView --|> BaseListView
    BaseSetupsListView --|> BaseListView
    BaseSourceDocumentListView --|> BaseListView

Inline Views

View Purpose
BaseInlineListView Lists child records for a parent.
BaseInlineDetailView Shows/edits one child record.
BaseInlineCreateView Creates a child record under a parent.
BaseInlineDeleteView Deletes a child record.
BaseInlineOneToOneDetailView Shows/edits a strictly one-to-one related object.

Inline views are usually invoked by the parent detail view using HTMX, which swaps their content into an inline tab. The parent declares them in get_inline_related_objects; see Field & Inline UI Configuration.


One-to-Many Inline Views (List + Detail)

A child record that belongs to a parent (one-to-many) is edited inside the parent's detail page with two views: a BaseInlineListView that lists the children, and a BaseInlineDetailView that shows/edits one child. The parent declares the pair in get_inline_related_objects (see Field & Inline UI Configuration); the framework loads each view over HTMX into an inline tab.

python
from enezaframework.views import BaseInlineListView, BaseInlineDetailView
from enezaframework.utils import auto_url_register

from appname.models import ChildModel, ParentModel


@auto_url_register
class ChildModelInlineListView(BaseInlineListView):
    model = ChildModel
    parent_model = ParentModel
    related_name = "children"
    partial_template_name = "appname/partials/child_inline_list.html"
    url_name = "childmodel_inline"

    def set_list_fields(self):
        return ["field1", "field2"]

    def set_search_fields(self):
        return ["field1", "parent__name"]


@auto_url_register
class ChildModelInlineDetailView(BaseInlineDetailView):
    model = ChildModel
    parent_model = ParentModel
    related_name = "children"
    template_name = "appname/partials/child_inline_detail.html"
    url_name = "childmodel_inline"

    def detail_ui_view(self):
        return [
            {
                "name": "Report",
                "icon": "Home",
                "Fieldcolumns": [{"fields": ["parent"]}],
            },
        ]

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # super() already sets main_object_pk and the related_object_* URL names.
        # Do not override main_object_pk here.
        return context

Required attributes:

Attribute View Description
model list, detail The inline model class.
parent_model list, detail The parent model class (not a string).
related_name list, detail The reverse FK/O2O relation name from the child back to the parent.
set_list_fields() list Return the columns to show. Raises NotImplementedError if missing.
set_search_fields() list Return the searchable fields. Raises NotImplementedError if missing.
detail_ui_view() detail Form layout (same schema as core detail views).

Optional attributes:

Attribute View Default Description
partial_template_name list core/partials/inline_list_partial.html Custom list template.
template_name detail core/partials/inline_update_partial.html Custom detail template.
url_name list, detail (model name) URL-prefix override. Must match the parent's inline url_name.

set_search_fields() must be implemented, but the base list view does not currently apply it in get_queryset() — no search filtering happens out of the box.

The list view's get_queryset() filters children by the parent (main_object_pk) through find_field_by_related_name() (handles ForeignKey, OneToOne, ManyToMany, and GenericForeignKey) and by the current entity when the model has an entity field; results are ordered by -created_at.


BaseInlineOneToOneDetailView

Specialized for a related object in a strict one-to-one relationship with the parent.

Required attributes:

Attribute Description
model The inline model class.
parent_model The parent model class.
related_name The related_name on the FK/O2O from the inline model back to the parent.
detail_ui_view Form layout (same schema as core detail views).
python
from enezaframework.views import BaseInlineOneToOneDetailView
from enezaframework.utils import auto_url_register
from accountpayable.models import VendorTable, VendorTableDetails


@auto_url_register
class VendorTableDetailsInlineView(BaseInlineOneToOneDetailView):
    model = VendorTableDetails
    parent_model = VendorTable
    related_name = "details"

    def detail_ui_view(self):
        Fieldtabs = [
            {
                "name": "Purchase demographics",
                "icon": "Home",
                "Fieldcolumns": [
                    {"fields": ["segment"]},
                ],
            },
        ]
        return Fieldtabs

For conditional or generic one-to-one inlines, the parent view supplies get_related_object_id to resolve the object being edited. Match the related_name used here with the relation_name declared in the parent's get_inline_related_objects.


Specialized Views

General pattern

Specialized views subclass the core views and add behavior (default filters, read-only enforcement, document layout). The child view still sets model and detail_ui_view, and is still decorated with @auto_url_register.


Journals Views

For models that inherit from a journal header/line base (for example JournalBatch and its lines).

View Purpose
BaseJournalsListView Lists journal headers.
BaseJournalsDetailView Shows a journal header with lines.
BaseJournalsCreateView Creates a journal header.
BaseJournalsDeleteView Deletes a journal header.
BaseJournalsLinesListView Lists journal lines.
BaseJournalsLinesDetailView Shows/edits a journal line.
BaseJournalsLinesCreateView Creates a journal line.
BaseJournalsLinesDeleteView Deletes a journal line.

Line views are typically used as inlines under BaseJournalsDetailView. Note that BaseJournalsDetailView and BaseJournalsLinesDetailView define action_list = [] and load inlines through get_inline_related_objects, so override those the same way you would on a core detail view.


Setup Views

For models that inherit from EnezaSetup (see Base Models & Model Types).

View Purpose
BaseSetupsListview Lists setup records.
BaseSetupsDetailView Shows/edits a setup record.
BaseSetupsCreateView Creates a setup record.
BaseSetupsDeleteView Deletes a setup record.

Read-only variants

View Purpose
BaseSetupsReadOnlyListView Lists setup records without edit affordances.
BaseSetupsReadOnlyDetailView Shows setup records without allowing modification.

Use the read-only variants when setup data must be visible but not editable in a given context or for a given role.

python
from enezaframework.views import BaseSetupsListview
from enezaframework.utils import auto_url_register
from accountpayable.models import VendorPostingGroup


@auto_url_register
class VendorPostingGroupListView(BaseSetupsListview):
    model = VendorPostingGroup

    def set_list_fields(self):
        list_fields = ["code", "description"]
        return list_fields

    def set_search_fields(self):
        search_fields = ["code", "description"]
        return search_fields

The class name is BaseSetupsListview (lowercase v). Import it from enezaframework.views.


Document Views

For "source document" models (sales orders, purchase invoices, and similar) that need a header/lines layout.

View Purpose
BaseSourceDocumentListView Lists source documents.
BaseSourceDocumentDetailView Shows a document with header and lines.
BaseSourceDocumentCreateView Creates a source document.
BaseSourceDocumentDeleteView Deletes a source document.

These views support the headerpostion tab key ("header" or "lines") so a tab's content renders in the document header region or alongside the lines.

python
def detail_ui_view(self):
    Fieldtabs = [
        {
            "name": "General",
            "icon": "Home",
            "headerpostion": "header",
            "Fieldcolumns": [
                {"fields": ["document_no", "customer"]},
            ],
        },
        {
            "name": "Lines",
            "icon": "Home",
            "headerpostion": "lines",
            "Fieldcolumns": [
                {"fields": ["total_amount", "currency"]},
            ],
        },
    ]
    return Fieldtabs

Inline Template Context

The base inline views populate the template context with routing and identity variables. Use these in custom inline templates instead of hard-coding URL names.

Variable List Detail Description
main_object_pk ✓ ✓ The parent's primary key (from the main_object_pk GET parameter).
related_object_list_url_name ✓ ✓ List URL name (from UrlMixin.get_list_url_name()).
related_object_detail_url_name ✓ ✓ Detail URL name.
related_object_create_url_name ✓ ✓ Create URL name.
related_object_delete_url_name ✓ ✓ Delete URL name.
related_object_container_name ✓ ✓ Container id used in hx-target (see note below).
related_object_verbose_name ✓ ✓ Model verbose name.
related_object_verbose_name_plural ✓ ✓ Model verbose name plural.
related_object_list ✓ — The child list (object_list).
related_object_current_object — ✓ The child object being viewed/edited.
current_app ✓ ✓ The current app namespace.
party_object ✓ ✓ The Party instance for party inlines (else None).

related_object_container_name differs between the two views: the list view sets it to model._meta.model_name, while the detail view sets it to model._meta.verbose_name.lower().replace(" ", ""). Keep this in mind if your list and detail templates share an hx-target id.

The inline detail template's back button uses these variables:

html
<button type="button"
        hx-get="{% url current_app|add:':'|add:related_object_list_url_name %}?main_object_pk={{ main_object_pk }}"
        hx-target="#{{ related_object_container_name }}-content">
    Back to List
</button>

Critical Rules

  1. Decorate both inline views with @auto_url_register; do not add manual URL patterns for them in urls.py — the registry generates the routes (see URL Auto-Registration).
  2. parent_model must be the model class (for example Risk), not a string like "riskmanagement.Risk".
  3. Do not override get_list_url_name(), get_detail_url_name(), get_create_url_name(), or get_delete_url_name() — UrlMixin derives them from the model name / url_name.
  4. Do not override main_object_pk in get_context_data() — the base view sets it from the request GET parameter.
  5. url_name must match between the view class attribute and the url_name key in the parent's get_inline_related_objects() entry.
  6. related_name on the view must match relation_name in the parent's inline config.
  7. Import from enezaframework.views, not enezaframework.views.baseinline.