Skip to main content

Form Schema Generators

CreateViewSchemaGenerator, EditViewSchemaGenerator and ShowViewSchemaGenerator are used to generate schema for the form views. You can specify the fields that will be rendered in the form:

from habarico import ModelAdmin
from habarico.schema.generators import (
CreateViewSchemaGenerator,
EditViewSchemaGenerator,
ListViewSchemaGenerator,
ShowViewSchemaGenerator,
)

from .models import Product


class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "sku", "description", "short_description", "status", "category", "brand", "tags", "created_at", "updated_at"]
view_schema_generators = [
ListViewSchemaGenerator(boxes=["name", "status", "category", "created_at"]),
CreateViewSchemaGenerator(boxes=["name", "sku", "description", "category", "brand"]),
EditViewSchemaGenerator(boxes=["name", "sku", "description", "short_description", "status", "category", "tags"]),
ShowViewSchemaGenerator(boxes=["name", "description", "short_description", "status", "category", "brand", "created_at", "updated_at"]),
]

Boxes​

The structure of form view generators can be more complex. You can define boxes that define the structure of the form. For example, you can define two boxes with fields in the edit view. React admin will generate two labeled areas with fields.

from habarico import ModelAdmin
from habarico.schema.generators import EditViewSchemaGenerator
from habarico.schema.boxes import Box

from .models import Product


class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "sku", "description", "short_description", "status", "category", "brand", "tags", "created_at", "updated_at"]
view_schema_generators = [
EditViewSchemaGenerator(box=Box(
boxes=[
Box(
label="Content",
boxes=["name", "sku", "description", "short_description"], # Field descriptors can also be used here
),
Box(
label="Metadata",
is_readonly=True, # Box is read only
boxes=["status", "category", "brand", "tags", "created_at", "updated_at"],
),
]
))
]

Boxes can be used to show form fields in tabs with TabBox:

from habarico import ModelAdmin
from habarico.schema.generators import EditViewSchemaGenerator
from habarico.schema.boxes import TabBox, Box

from .models import Product


class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "sku", "description", "short_description", "status", "category", "brand", "tags", "stock", "price", "created_at", "updated_at", "published_at"]
view_schema_generators = [
EditViewSchemaGenerator(box=TabBox(
show_toolbar_navigation=True, # Show previous/next navigation buttons in the box toolbar (default: False for tabs)
boxes=[
Box(
label="Content",
boxes=["name", "sku", "description", "short_description", "status"],
),
Box(
label="Relationships",
boxes=["category", "brand", "tags"],
),
Box(
label="Statistics",
boxes=["stock", "price", "created_at", "updated_at"],
show_query="status === 'published'", # Show this box only if status is published
),
Box(
label="Publishing",
boxes=["published_at"],
show_query="status === 'published'",
required_query="status === 'published'", # This box is required only if status is published
),
]
))
]

or WizardBox. Each step is a labelled inner box, typically a TabItemBox (its label is required). WizardBox shows previous/next navigation buttons by default (show_toolbar_navigation=True) and validates the current step before advancing (validate_steps=True); both can be turned off.

from habarico import ModelAdmin
from habarico.schema.generators import EditViewSchemaGenerator
from habarico.schema.boxes import WizardBox, TabItemBox

from .models import Product


class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "sku", "description", "short_description", "status", "category", "brand", "tags"]
view_schema_generators = [
EditViewSchemaGenerator(box=WizardBox(
show_toolbar_navigation=True, # Show previous/next navigation buttons in the box toolbar (default: True for wizards)
validate_steps=True, # Validate the current step before moving to the next one (default: True)
boxes=[
TabItemBox(
label="Basic Info", # label is required for each step
boxes=["name", "sku", "category"],
),
TabItemBox(
label="Content",
boxes=["description", "short_description"],
),
TabItemBox(
label="Categorization",
boxes=["status", "category", "tags"],
),
]
))
]

Both TabBox and WizardBox accept show_toolbar_navigation (default False for tabs, True for wizards); WizardBox additionally accepts validate_steps (default True). In the generated schema these are nested under a tab / wizard object, e.g. {"type": "wizard", "wizard": {"show_toolbar_navigation": true, "validate_steps": true}}.

Box actions​

Every box supports two action slots that position buttons relative to the box itself rather than the whole view:

ParameterWhere rendered
toolbar_actionsToolbar in the box header / card header area. On a plain Box always visible; on TabBox / WizardBox appears on the last step.
bottom_actionsBelow the box fields, similar to form submit buttons at the view level.

Both accept a list of action descriptors or plain action name strings.

To move the Save button next to a specific block (e.g. the right column of a grid), add SaveActionDescriptor() to that box's toolbar_actions and drop the default view-level Save by passing bottom_actions=[] on the detail view:

from habarico.schema.actions import SaveActionDescriptor
from habarico.schema.boxes import GridBox, GridItemBox, Box, WizardBox, TabItemBox

EditViewSchemaGenerator(
bottom_actions=[], # drop the default view-level Save button
box=GridBox(boxes=[
GridItemBox(md=6, boxes=[Box(label="Preview", boxes=["name"])]),
GridItemBox(md=6, boxes=[
WizardBox(
show_toolbar_navigation=True,
toolbar_actions=[SaveActionDescriptor()], # shown on the last step
boxes=[
TabItemBox(label="Step 1", boxes=["name"]),
TabItemBox(label="Step 2", boxes=["description"]),
],
),
]),
]),
)

A table of another admin resource can be displayed with ListBox:

from habarico import ModelAdmin
from habarico.schema.generators import EditViewSchemaGenerator
from habarico.schema.boxes import TabBox, Box, ListBox

from .models import Category, Product


class CategoryAdmin(ModelAdmin):
model = Category
fields = ["id", "name", "email", "description", "is_active"]
view_schema_generators = [
EditViewSchemaGenerator(box=TabBox(
boxes=[
Box(
label="Category Info",
boxes=["name", "email", "description", "is_active"],
),
ListBox(
label="Products",
boxes=["name", "status", "stock", "created_at"], # Fields that will be rendered in the table
toolbar_actions=["refresh"], # Actions in the box's own toolbar (box header, box root)
list_toolbar_actions=["create"], # Actions in the table toolbar (top of the resource list)
row_actions=["edit", "show", "delete"], # Actions on each table row
list_actions=["export"], # Utility actions next to the filter bar
default_sorting="-created_at", # Default sorting field
reference="product", # Reference to the related admin resource (name of the admin)
target="category", # Name of the field that is used to filter the related items
),
]
))
]

Finally, there are two types of boxes that can be used with nested DRF serializers, ObjectBox and ArrayBox:

from habarico import ModelAdmin
from habarico.schema.generators import EditViewSchemaGenerator
from habarico.schema.boxes import TabBox, Box, ArrayBox, ObjectBox

from .models import Product


class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "sku", "description", "category", "brand", "tags"]
view_schema_generators = [
EditViewSchemaGenerator(box=TabBox(
boxes=[
Box(
label="Content",
boxes=["name", "sku", "description"],
),
ObjectBox(
label="Category Details",
boxes=["name", "email", "description"], # Fields that will be rendered in the subform
target="category", # Name of the nested serializer field
),
ArrayBox(
label="Tags",
boxes=["name"], # Fields that will be rendered in array form
target="tags", # Name of the nested array field
),
]
))
]

Save Action Behaviour​

By default, after a successful save the frontend decides what to do (typically redirecting to the show or list view). You can override this using the after_save parameter on SaveActionDescriptor or ActionSchema (with type=ActionType.SAVE).

Possible values (AfterSaveType):

ValueBehaviour
stayStay on the current form (do not navigate away)
prevNavigate back (browser history back / close dialog)
showOpen the show view for the saved record
listNavigate to the list view of the resource
from habarico import ModelAdmin
from habarico.schema import AfterSaveType, SaveActionDescriptor
from habarico.schema.generators import CreateViewSchemaGenerator, EditViewSchemaGenerator

from .models import Product


class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "sku", "description", "status", "category"]
view_schema_generators = [
CreateViewSchemaGenerator(
actions=[
SaveActionDescriptor(after_save=AfterSaveType.SHOW), # Go to show view after create
]
),
EditViewSchemaGenerator(
actions=[
SaveActionDescriptor(after_save=AfterSaveType.STAY), # Stay on the form after save
]
),
]

You can also set after_save directly on ActionSchema when building a custom save action:

from habarico.schema import Action, ActionSchema, ActionType, AfterSaveType

Action(
name="save",
label="Save and go to list",
action=ActionSchema(
type=ActionType.SAVE,
after_save=AfterSaveType.LIST,
),
)

Upload Images Action​

UploadImagesActionDescriptor renders an "Upload images" button that lets the user bulk-upload image files. By default it uploads to the current resource context, but it can be pointed at another resource and linked back to the current record.

Parameters:

ParameterTypeDescription
namestrAction name. Defaults to upload-images.
labelstrButton label. Defaults to Upload images.
iconstrButton icon. Defaults to AddPhotoAlternate.
colorColorButton color. When not set, the button inherits its color from the toolbar context.
referencestrResource where images will be created. When set, the button uploads to this resource instead of the current resource context.
targetstrField name on the image model linking to the parent record. When set together with reference, the current record's ID is used as the target value.
acceptlist[str]List of accepted file types (e.g. [".png", ".jpg"]). When not set, the frontend uses its own default (.png,.jpg,.jpeg,.bmp,.gif).
datadictAdditional data that will be added to each uploaded image record.
image_fieldstrField name on the image model that holds the uploaded image file. When not set, the frontend uses its own default.
from habarico import ModelAdmin
from habarico.schema.actions import UploadImagesActionDescriptor
from habarico.schema.generators import ShowViewSchemaGenerator

from .models import Product


class ProductAdmin(ModelAdmin):
model = Product
fields = ["name", "sku", "description"]
view_schema_generators = [
ShowViewSchemaGenerator(
actions=[
UploadImagesActionDescriptor(
reference="photos", # create images in the `photos` resource
target="product", # link each image to the current product
accept=[".png", ".jpg"], # restrict accepted file types
data={"public": True}, # extra data added to every created image
image_field="photo", # field on the image model that holds the file
),
]
),
]

In the generated schema these are nested under an upload_images object on the action, e.g. {"type": "upload-images", "upload_images": {"reference": "photos", "target": "product", "accept": [".png", ".jpg"], "data": {"public": true}, "image_field": "photo"}}.

See also​