Admin Models¶
Admin models define how your SQLModel classes appear in the fp-admin interface. They provide a simple way to register models and configure their display in the admin panel.
Overview¶
Admin models use the AdminModel
class to register your models with the admin interface. This is separate from view configuration and provides basic model registration with minimal configuration.
Basic Admin Model Registration¶
Simple Registration¶
from fp_admin.registry import AdminModel
from .models import User, Post, Category
class UserAdmin(AdminModel):
model = User
label = "Users"
display_field = "username" # Field to display in lists
class PostAdmin(AdminModel):
model = Post
label = "Posts"
display_field = "title"
class CategoryAdmin(AdminModel):
model = Category
label = "Categories"
display_field = "name"
Admin Model Features¶
- Simple Setup: Just specify the model and label
- Automatic Discovery: Models are automatically discovered by the admin interface
- Clean Separation: Keeps model registration separate from view configuration
- Minimal Code: Requires only the essential information
- Display Fields: Configure which field to show in list views
Admin Model Configuration¶
Required Attributes¶
class UserAdmin(AdminModel):
model = User # Required: Your SQLModel class
label = "Users" # Required: Display name in admin
display_field = "username" # Required: Field to show in lists
Optional Attributes¶
class UserAdmin(AdminModel):
model = User
label = "Users"
display_field = "username"
# Optional: Custom ordering
ordering = ["username"]
# Optional: Fields to include in list view
list_fields = ["id", "username", "email", "is_active"]
# Optional: Fields to exclude from list view
exclude_fields = ["password_hash", "created_at", "updated_at"]
View Configuration (views.py)¶
Views define the detailed configuration of how your models appear in the admin interface using ViewBuilder
:
from fp_admin.registry import ViewBuilder
from fp_admin.models.field import FieldFactory
from .models import User, Group, Permission
class UserFormView(ViewBuilder):
model = User
view_type = "form"
name = "UserForm"
fields = [
FieldFactory.primary_key_field("id"),
FieldFactory.string_field("username", required=True, min_length=3),
FieldFactory.string_field("first_name", required=True),
FieldFactory.string_field("last_name", required=True),
FieldFactory.email_field("email", required=True),
FieldFactory.password_field("password", required=True, min_length=8),
FieldFactory.boolean_field("is_active", required=True),
FieldFactory.boolean_field("is_superuser", required=True),
FieldFactory.foreignkey_field(
"group_id",
model_class=Group,
display_field="name",
required=False
),
]
creation_fields = ["username", "first_name", "last_name", "email", "password", "is_active", "is_superuser"]
allowed_update_fields = ["first_name", "last_name", "email", "is_active", "is_superuser", "group_id"]
View Features¶
- Field Configuration: Detailed control over form fields
- Validation Rules: Custom validation and error messages
- Permission Control: Field-level access control
- Widget Selection: Choose appropriate input widgets
- Relationship Display: Handle foreign key and many-to-many fields
Complete Example¶
Here's a complete example based on the auth app:
from fp_admin.registry import AdminModel, ViewBuilder
from fp_admin.models.field import FieldFactory
from .models import User, Group, Permission
# Admin Model Registration
class UserAdmin(AdminModel):
model = User
label = "Users"
display_field = "username"
class GroupAdmin(AdminModel):
model = Group
label = "Groups"
display_field = "name"
class PermissionAdmin(AdminModel):
model = Permission
label = "Permissions"
display_field = "name"
# User Form View
class UserFormView(ViewBuilder):
model = User
view_type = "form"
name = "UserForm"
fields = [
FieldFactory.primary_key_field("id"),
FieldFactory.string_field("username", required=True, min_length=3),
FieldFactory.email_field("email", required=True),
FieldFactory.password_field("password", required=True, min_length=8),
FieldFactory.boolean_field("is_active", required=True),
FieldFactory.boolean_field("is_superuser", required=True),
FieldFactory.many_to_many_field("groups", model_class=Group, display_field="name"),
]
creation_fields = ["username", "email", "password", "is_active", "is_superuser"]
allowed_update_fields = ["email", "is_active", "is_superuser"]
# User List View
class UserListView(ViewBuilder):
model = User
view_type = "list"
name = "UserList"
fields = [
FieldFactory.primary_key_field("id"),
FieldFactory.string_field("username"),
FieldFactory.email_field("email"),
FieldFactory.boolean_field("is_active"),
FieldFactory.boolean_field("is_superuser"),
]
# Group Form View
class GroupFormView(ViewBuilder):
model = Group
view_type = "form"
name = "GroupForm"
fields = [
FieldFactory.primary_key_field("id"),
FieldFactory.string_field("name", required=True, min_length=1),
FieldFactory.string_field("description", required=True, min_length=1, max_length=200),
FieldFactory.many_to_many_field("permissions", model_class=Permission, display_field="name"),
FieldFactory.many_to_many_field("users", model_class=User, display_field="username"),
]
creation_fields = ["name", "description"]
allowed_update_fields = ["name", "description", "permissions"]
Field Configuration Options¶
String Fields¶
# String field with validation
FieldFactory.string_field(
"title",
required=True,
min_length=1,
max_length=200
)
# Email field with pattern validation
FieldFactory.email_field(
"email",
required=True
)
# Password field with strength requirements
FieldFactory.password_field(
"password",
required=True,
min_length=8
)
Numeric Fields¶
# Number field with range validation
FieldFactory.number_field(
"age",
required=True,
min_value=13,
max_value=120
)
# Float field with precision
FieldFactory.float_field(
"price",
required=True,
min_value=0.0,
step=0.01
)
Boolean Fields¶
# Standard boolean field
FieldFactory.boolean_field("is_active", required=True)
# Toggle field (switch widget)
FieldFactory.toggle_field("is_verified", required=True)
# Radio field for multiple options
FieldFactory.radio_field("status", choices=["active", "inactive", "pending"])
Choice Fields¶
# Single choice field
FieldFactory.choice_field("category", required=True, choices=["tech", "lifestyle", "news"])
# Multiple choice field
FieldFactory.multichoice_field("tags", required=True, choices=["python", "fastapi", "admin"])
# Chips field for multiple selections
FieldFactory.chips_field("skills", required=True, choices=["python", "javascript", "sql"])
Relationship Fields¶
# Foreign key field
FieldFactory.foreignkey_field(
"category_id",
model_class=Category,
display_field="name",
required=True
)
# Many-to-many field
FieldFactory.many_to_many_field(
"tags",
model_class=Tag,
display_field="name",
required=False
)
# One-to-one field
FieldFactory.one_to_one_field(
"profile",
model_class=UserProfile,
display_field="username",
required=False
)
File Fields¶
# File upload field
FieldFactory.file_field("document", required=True)
# Image field with preview
FieldFactory.image_field("avatar", required=True)
Special Fields¶
# JSON field
FieldFactory.json_field("metadata", required=True)
# Autocomplete field
FieldFactory.autocomplete_field("search", required=True, placeholder="Start typing...")
Best Practices¶
- Use descriptive names: Choose clear, descriptive names for your views
- Separate concerns: Use different views for different operations (form vs list)
- Validate fields: Always specify required fields and validation rules
- Control updates: Use
allowed_update_fields
to restrict what can be changed - Use relationships: Leverage
many_to_many_field
andforeignkey_field
for relationships - Follow naming conventions: Use consistent naming patterns across your views
Field Validation¶
Built-in Validation¶
Each field type includes appropriate validation:
- String fields: Length constraints, pattern matching
- Email fields: Email format validation
- Numeric fields: Range validation, type checking
- Date fields: Date format validation
- Required fields: Non-empty value validation
Custom Validation¶
You can add custom validation to any field:
from fp_admin.models.field import FpFieldValidator, FpFieldError
# Custom validator
def validate_username_unique(value: str) -> FpFieldError | None:
if value == "admin":
return FpFieldError(
code="reserved_username",
message="Username 'admin' is reserved"
)
return None
# Apply custom validator
FieldFactory.string_field(
"username",
required=True,
custom_validator=validate_username_unique
)
Performance Optimization¶
Field Selection¶
Only include fields that are necessary for your use case:
# Minimal fields for list view
list_fields = [
FieldFactory.primary_key_field("id"),
FieldFactory.string_field("username"),
FieldFactory.email_field("email"),
FieldFactory.boolean_field("is_active"),
]
# Full fields for form view
form_fields = [
# ... all fields including relationships
]
Relationship Loading¶
Configure relationship fields with appropriate display fields:
# Efficient relationship field
FieldFactory.many_to_many_field(
"groups",
model_class=Group,
display_field="name", # Only load the name field
required=False
)
Testing Admin Models¶
Unit Testing¶
import pytest
from fp_admin.registry import AdminModel, ViewBuilder
from fp_admin.models.field import FieldFactory
def test_user_admin_model():
"""Test user admin model configuration."""
admin = UserAdmin()
assert admin.model == User
assert admin.label == "Users"
assert admin.display_field == "username"
def test_user_form_view():
"""Test user form view configuration."""
view = UserFormView()
assert view.model == User
assert view.view_type == "form"
assert len(view.fields) > 0
# Check required fields
username_field = next(f for f in view.fields if f.name == "username")
assert username_field.required is True
Integration Testing¶
@pytest.mark.asyncio
async def test_admin_model_registration():
"""Test that admin models are properly registered."""
from fp_admin.registry import model_registry
# Check if user model is registered
user_config = model_registry.get("user")
assert user_config is not None
assert user_config.model == User
assert user_config.label == "Users"
Next Steps¶
- Field Types - Learn about all available field types
- Widgets - Discover available widgets and their configuration
- Services Layer - Learn about the services architecture
- API Reference - Explore the REST API endpoints