Display Metadata
Display Metadata lets you define frontend display information directly in your Django backend. This allows you to build generic frontend components (forms, CRUD tables) that automatically adapt to your models and actions without duplicating display logic across your frontend.
Overview
Display metadata consists of three main classes:
DisplayMetadata
: Top-level container for display customizationFieldGroup
: Logical grouping of related fieldsFieldDisplayConfig
: Per-field display customization
DisplayMetadata
python
@dataclass
class DisplayMetadata:
display_title: Optional[str] = None
display_description: Optional[str] = None
field_groups: Optional[List[FieldGroup]] = None
field_display_configs: Optional[List[FieldDisplayConfig]] = None
extra: Optional[Dict[str, Any]] = None
FieldGroup
python
@dataclass
class FieldGroup:
display_title: str
display_description: Optional[str] = None
field_names: Optional[List[str]] = None
FieldDisplayConfig
python
@dataclass
class FieldDisplayConfig:
field_name: str
display_component: Optional[str] = None
filter_queryset: Optional[Dict[str, Any]] = None
display_help_text: Optional[str] = None
extra: Optional[Dict[str, Any]] = None
Usage with Models
python
from statezero.adaptors.django.config import registry
from statezero.core.classes import DisplayMetadata, FieldGroup, FieldDisplayConfig
from .models import Product
registry.register(
Product,
ModelConfig(
display=DisplayMetadata(
display_title="Product Management",
display_description="Create and manage products in your catalog",
field_groups=[
FieldGroup(
display_title="Basic Information",
display_description="Essential product details",
field_names=["name", "description", "category"]
),
FieldGroup(
display_title="Pricing & Availability",
field_names=["price", "in_stock"]
),
],
field_display_configs=[
FieldDisplayConfig(
field_name="description",
display_component="RichTextEditor",
display_help_text="Provide a detailed description of the product"
),
FieldDisplayConfig(
field_name="category",
display_component="CategorySelector",
filter_queryset={"is_active": True},
display_help_text="Select the product category"
),
FieldDisplayConfig(
field_name="price",
display_component="CurrencyInput",
display_help_text="Price with tax calculated automatically"
),
]
)
)
)
Usage with Actions
python
from statezero.core.actions import action
from statezero.core.classes import DisplayMetadata, FieldGroup, FieldDisplayConfig
@action(
serializer=SendNotificationInputSerializer,
display=DisplayMetadata(
display_title="Send Notifications",
display_description="Send notifications to multiple recipients with priority control",
field_groups=[
FieldGroup(
display_title="Message Content",
field_names=["message", "priority"]
),
FieldGroup(
display_title="Recipients",
field_names=["recipients"]
)
],
field_display_configs=[
FieldDisplayConfig(
field_name="message",
display_component="TextArea",
display_help_text="Enter your notification message (max 500 characters)"
),
FieldDisplayConfig(
field_name="priority",
display_component="RadioGroup",
display_help_text="High priority notifications are processed first"
),
FieldDisplayConfig(
field_name="recipients",
display_component="EmailListInput",
display_help_text="Add one or more email addresses"
)
]
)
)
def send_notification(message: str, recipients: list, priority: str = "low", *, request=None):
# Implementation
pass
Frontend Integration
Display metadata is included in the schema API response:
javascript
import { Product } from './generated/models';
const schema = Product.schema;
console.log(schema.display.display_title); // "Product Management"
console.log(schema.display.field_groups); // Array of field groups
console.log(schema.display.field_display_configs); // Array of field configs
Generic Form Example
vue
<template>
<form @submit.prevent="handleSubmit">
<h2>{{ schema.display?.display_title || schema.title }}</h2>
<p v-if="schema.display?.display_description">
{{ schema.display.display_description }}
</p>
<div v-for="group in schema.display?.field_groups" :key="group.display_title">
<h3>{{ group.display_title }}</h3>
<p v-if="group.display_description">{{ group.display_description }}</p>
<div v-for="fieldName in group.field_names" :key="fieldName">
<component
:is="getFieldComponent(fieldName)"
:field="schema.fields[fieldName]"
:config="getFieldConfig(fieldName)"
v-model="formData[fieldName]"
/>
</div>
</div>
</form>
</template>
<script setup>
const getFieldConfig = (fieldName) => {
return schema.display?.field_display_configs?.find(
config => config.field_name === fieldName
);
};
const getFieldComponent = (fieldName) => {
const config = getFieldConfig(fieldName);
return componentMap[config?.display_component] || 'DefaultInput';
};
</script>
Component Mapping
Map display component names to your actual components:
javascript
export const componentMap = {
'RichTextEditor': RichTextEditor,
'CategorySelector': CategorySelector,
'CurrencyInput': CurrencyInput,
'EmailListInput': EmailListInput,
'TextArea': TextArea,
'RadioGroup': RadioGroup,
'DatePicker': DatePicker,
'DefaultInput': DefaultInput,
};
Filter Queryset
Use filter_queryset
to pre-filter options for foreign key fields:
python
FieldDisplayConfig(
field_name="category",
filter_queryset={
"is_active": True,
"parent__isnull": True # Only top-level categories
}
)
Frontend usage:
javascript
const config = getFieldConfig('category');
const options = await Category.objects.filter(config.filter_queryset).fetch();
Schema Response Format
json
{
"model_name": "Product",
"display": {
"display_title": "Product Management",
"display_description": "Create and manage products in your catalog",
"field_groups": [
{
"display_title": "Basic Information",
"field_names": ["name", "description", "category"]
}
],
"field_display_configs": [
{
"field_name": "description",
"display_component": "RichTextEditor",
"display_help_text": "Provide a detailed description"
}
]
}
}