Sample Application
Overview
The sample app provides a comprehensive demonstration of django-powercrud features using a realistic book/author management system. It serves as both a testing environment during development and a reference implementation for developers learning the package.
Models
The sample app includes four interconnected models that showcase different relationship types and field configurations:
- Basic fields:
name,bio,birth_date - Many-to-many:
genresrelationship used to constrain inline Book genre choices - Properties:
has_bio,property_birth_date - Demonstrates property display in list/detail views
- Core fields:
title,author(ForeignKey),published_date,isbn,pages - Many-to-many:
genresrelationship - Advanced features:
isbn_emptyGeneratedField for complex database expressionsuneditable_fieldfor testing non-editable fields- Custom
clean()andsave()methods - Delayed
delete()method for async testing - Properties with custom display names
- Unique constraint on
title+author
- Simple model:
name,description,numeric_string - Custom validation in
clean()method - Guarded demo row for built-in Delete disable hooks
- Protected demo row for handled single-delete refusal UX
- Used for many-to-many relationships and filtering
- OneToOneField to Author (tests 1:1 relationships)
- ForeignKey to Genre (tests optional relationships)
- Demonstrates related field handling in forms/filters
CRUD Views
Each model has a dedicated CRUD view demonstrating different powercrud features:
BookCRUDView - Full Feature Demo
from powercrud.mixins import PowerCRUDAsyncMixin
class BookCRUDView(PowerCRUDAsyncMixin, CRUDView):
# Comprehensive configuration showing:
view_title = "My List of Books"
view_instructions = "Here you can edit books"
view_help = {
"summary": "About this feature demo",
"details": (
"This Books screen demonstrates many PowerCRUD features in one place."
"\n\n"
"Use it to inspect list options, inline editing, saved filter favourites, "
"bulk actions, async workflows, modal links, external links, selection-aware "
"toolbar actions, and guarded update behaviour."
),
"color": "info",
}
column_help_text = {
"title": "The book title shown throughout the app.",
"pages": "Demo link: opens this book detail in the current page.",
"isbn": "Demo link: opens an external ISBN reference in a new tab or window.",
"isbn_empty": "Shows whether this row currently has an ISBN value.",
"a_really_long_property_header_for_title": (
"Demo link: opens the related author detail in a larger PowerCRUD modal."
),
}
list_cell_tooltip_fields = {
"title": "get_title_tooltip",
"pages": "get_pages_tooltip",
"isbn_empty": "get_isbn_empty_tooltip",
}
list_cell_link_default_open_in = "modal"
list_options_enabled = True
default_list_fields = [
"title",
"author",
"published_date",
"pages",
"bestseller",
"isbn",
"genres",
"isbn_empty",
"a_really_long_property_header_for_title",
]
link_fields = {
"a_really_long_property_header_for_title": {
"view_name": "sample:author-detail",
"pk_attr": "author_id",
"modal_box_classes": (
"modal-box flex max-h-[calc(100dvh-2rem)] w-11/12 "
"max-w-6xl flex-col"
),
},
"pages": {
"view_name": "sample:bigbook-detail",
"open_in": "current",
},
"isbn": {
"url": "https://www.isbn-international.org/content/what-isbn",
"open_in": "new",
},
}
form_class = BookForm
form_display_fields = ["uneditable_field"]
form_disabled_fields = ["isbn"]
field_queryset_dependencies = {
"genres": {
"depends_on": ["author"],
"filter_by": {"authors": "author"},
"order_by": "name",
"empty_behavior": "all",
}
}
bulk_fields = ['title', 'published_date', 'bestseller', 'pages', 'author', 'genres']
bulk_delete = True
bulk_async = True
filterset_fields = ['author', 'title', 'published_date', 'isbn', 'pages', 'description', 'genres']
default_filterset_fields = ['author', 'title', 'published_date']
filter_favourites_enabled = True
dropdown_sort_options = {"author": "name"}
inline_edit_fields = ['title', 'author', 'genres', 'published_date', 'bestseller', 'description']
extra_buttons = [...] # Includes a selection-aware "Selected Summary" demo
extra_actions = [...] # Includes a conditional "Description Preview" demo
def get_title_tooltip(self, obj, request=None):
return f"{obj.author}\n{obj.pages} pages"
def get_pages_tooltip(self, obj, request=None):
return f"Page count: {obj.pages}"
def get_isbn_empty_tooltip(self, obj, request=None):
if obj.isbn_empty:
return "This book does not currently have an ISBN."
return f"ISBN: {obj.isbn}"
The sample BookCRUDView uses view_title = "My List of Books" plus view_instructions = "Here you can edit books" to demonstrate the narrow heading/helper-text overrides. It also sets view_help to demonstrate collapsed screen-level guidance with a one-line summary, escaped paragraph text, a subtle info colour tint, and table-aligned width. The column_help_text mapping covers one field and one property so the sample list shows the header-help tooltip pattern; on linked demo columns, the header help explicitly says whether the link opens in the current page, a new tab/window, or the PowerCRUD modal. list_cell_tooltip_fields maps selected fields/properties to row-specific tooltip hooks for the inline-editable title, the visible non-inline pages field, and the boolean-like isbn_empty property cell. The sample title tooltip intentionally uses a newline so the demo shows multiline semantic list-cell tooltip rendering, while header-help tooltips and other tooltip surfaces keep their normal single-line behavior. That changes only the list surface above and inside the table; other UI copy such as the create button still comes from the model verbose names, and the instructions text, collapsed screen help, header help text, and semantic cell tooltip text are all rendered as plain escaped text rather than HTML.
The same sample view now also demonstrates list-cell linking through the narrow declarative link_fields API. The live sample uses the non-inline property column a_really_long_property_header_for_title so the screen can keep its primary title and author columns reserved for inline-edit and dependency demos. That is deliberate: PowerCRUD never turns inline-editable cells into links. The sample sets list_cell_link_default_open_in = "modal" and uses the dict form with pk_attr = "author_id" plus modal_box_classes, so that existing non-inline link opens the related author detail through a noticeably larger PowerCRUD modal when the sample page is running with modal support. In views that omit list_cell_link_default_open_in, PowerCRUD assumes "new". The sample links pages to the current book detail with explicit open_in = "current", and keeps isbn out of inline_edit_fields so that visible field can link to a static external ISBN reference with explicit open_in = "new".
The same sample view now also demonstrates progressive filter visibility:
author,title, andpublished_dateare visible by default throughdefault_filterset_fieldsisbn,pages,description, andgenresremain allowed filters but start hidden- the Add filter control reveals those optional filters on demand without changing the underlying filterset contract
The same sample view now also demonstrates list options:
list_options_enabled = Trueenables Cols, whiledefault_list_fieldskeeps the initial book table narrower than the full allowed column set- users can open Cols and add allowed hidden columns such as
uneditable_field - the current column choice is scoped to the browser session and the
BookCRUDView - reset returns the table to the declared
default_list_fields
The sample BookCRUDView also demonstrates the optional saved-favourites contrib app:
filter_favourites_enabled = Trueturns on the toolbar for this list- saved favourites persist the current filters, optional filter visibility, sort, page size, and visible columns for the signed-in user, scoped to the list view's derived identity
- the sample project mounts
include("powercrud.urls", namespace="powercrud"), which is required for the optional favourites endpoints
See Filtering for the core filter behavior and Saved Favourites for the optional contrib add-on.
AnnotatedBookCRUDView - Queryset Annotation Columns
The sample app includes a focused list-only view at /sample/annotated-book/ for queryset-backed list/filter fields.
from django.db.models import BooleanField, Case, Value, When
class AnnotatedBookCRUDView(PowerCRUDAsyncMixin, CRUDView):
model = Book
url_base = "annotated-book"
queryset = Book.objects.select_related("author").annotate(
long_book=Case(
When(pages__gte=400, then=Value(True)),
default=Value(False),
output_field=BooleanField(),
)
)
fields = ["title", "author", "pages", "long_book", "published_date"]
list_options_enabled = True
default_list_fields = ["title", "author", "pages", "published_date"]
filterset_fields = ["author", "long_book", "pages"]
default_filterset_fields = ["author", "long_book"]
inline_edit_fields = ["pages"]
bulk_fields = []
The long_book column is not a model field. It is the public queryset annotation name, and PowerCRUD uses that same name in fields, generated filters, sorting, header help, cell tooltips, and list-column selection. The sample sets list_options_enabled = True and keeps long_book out of default_list_fields so it appears as an optional selectable column in the Cols control. The sample makes the real pages model field inline-editable while keeping long_book out of inline edit and bulk edit config because annotation fields are read-only. See Queryset Annotation Fields for the declaration details behind this sample.
The sample frontend now also shows the downstream tooltip-styling path. In src/config/static/css/app.custom.css, the sample app actively overrides --pc-tooltip-bg and --pc-tooltip-fg to use daisyUI's primary semantic tokens, while PowerCRUD itself keeps neutral tooltip defaults. The sample Vite entry imports that file after powercrud/css/powercrud.css, so readers can inspect the real app-level override pattern rather than only reading about it in the styling guide.
The sample form configuration now also demonstrates two contextual form-surface features:
form_display_fields = ["uneditable_field"]shows the model’s non-editable field in a separate read-onlyContextblock above the update form.form_disabled_fields = ["isbn"]keeps the ISBN visible on update forms but locks the input so users can see it without changing it.
BookForm remains the source of truth for editable inputs, while PowerCRUD layers the display-only context block and disabled-field behavior on top of that custom form.
The sample BookCRUDView now also demonstrates both custom action enhancements discussed in the docs:
- a selection-aware
extra_buttonthat opens a modal summary for the current persisted bulk selection - a row-level
extra_actionthat disables itself with a tooltip when the book has no description - an opt-in modal
extra_actionusingrefresh_list_on_modal_close=Trueto refresh the current list when its modal is closed - per-trigger modal sizing on a modal list-cell link, a modal
extra_button, and modalextra_actionsthroughmodal_box_classes - semantic field-level list-cell tooltips on the inline
title, non-inlinepages, andisbn_emptyproperty columns - session-backed list-column choices through Cols
- declarative list-cell linking on
pages(current), visibleisbn(new), and the non-inlineReally Long Titleproperty column (modal) - an active sample app-level tooltip theme override through
--pc-tooltip-bg/--pc-tooltip-fg - a guarded row (
Guarded Sample Book) that disables the built-in Edit action and inline editing before the user can start an update - a bulk-validation demo row (
Bulk Validation Sample Book) that re-renders the bulk edit modal for sync bulk updates and fails the async task for queued bulk updates when a sample bulk rule is violated
These examples are intentionally simple so package users can inspect both the view config and the matching sample endpoints/templates.
PowerFieldBookCRUDView - Field Intent Helper Variant
The sample app includes a sibling Book view at /sample/powerfield-book/ labelled PowerField Books.
This view uses power_fields instead of Base Configuration API Field Intent attributes. It is not a subclass of BookCRUDView, because PowerCRUD rejects mixing base Field Intent and PowerField declarations in one inheritance chain.
from powercrud.actions import PowerAction, PowerButton
from powercrud.powerfields import PowerField, PowerOverride
class PowerFieldBookCRUDView(PowerCRUDAsyncMixin, CRUDView):
model = Book
namespace = "sample"
url_base = "powerfield-book"
list_options_enabled = True
form_class = BookForm
power_fields = [
PowerOverride(detail="__all__"),
PowerField(
"title",
default_list=True,
tooltip_hook="get_title_tooltip",
form=True,
inline=True,
bulk=True,
),
PowerField(
"author",
default_list=True,
form=True,
inline=True,
bulk=True,
),
PowerField(
"published_date",
default_list=True,
form=True,
inline=True,
bulk=True,
),
PowerField(
"pages",
default_list=True,
tooltip_hook="get_pages_tooltip",
form=True,
bulk=True,
link={
"view_name": "sample:powerfield-book-detail",
"open_in": "current",
},
),
PowerField(
"isbn_empty",
property=True,
detail_property=True,
default_list=True,
tooltip_hook="get_isbn_empty_tooltip",
),
PowerField("description", form=True, inline=True),
PowerField("uneditable_field", form_display=True),
]
def get_title_tooltip(self, obj, request=None):
return f"{obj.author}\n{obj.pages} pages"
def get_pages_tooltip(self, obj, request=None):
return f"Page count: {obj.pages}"
def get_isbn_empty_tooltip(self, obj, request=None):
if obj.isbn_empty:
return "This book does not currently have an ISBN."
return f"ISBN: {obj.isbn}"
row_modal = PowerAction(
text="Normal Edit",
url_name="sample:bigbook-update",
display_modal=True,
)
extra_actions = [
row_modal,
row_modal.with_options(
text="Description Preview",
url_name="sample:bigbook-description-preview",
disabled_state="get_description_preview_disabled_state",
),
]
extra_buttons = [
PowerButton(
text="Selected Summary",
url_name="sample:bigbook-selected-summary",
display_modal=True,
uses_selection=True,
selection_min_count=1,
selection_min_behavior="disable",
),
]
The real sample view is more complete than this excerpt. It mirrors the base BookCRUDView Field Intent contract where that helps the demo, but it keeps the PowerField list allow-list clearer: default_list=True is enough for default visible model fields, and form-only fields do not need list exclusions. The PowerField variant links to its own sample:powerfield-book-detail route so the sample remains self-contained. It also mirrors the BookCRUDView toolbar buttons and row actions through PowerButton and PowerAction, including a with_options(...) row-action variant.
See Choosing an API Style, PowerField, and PowerField Reference for the constructor and validation contract.
The sample BookCRUDView also includes illustrative persistence-hook wiring:
persist_single_object(...)persist_bulk_update(...)bulk_update_persistence_backend_path = "sample.backends.BookBulkUpdateBackend"
The single-object hook stays intentionally thin. For bulk updates, the sync hook and the async backend both route through BookBulkUpdateService, so the same sample validation rule applies in either execution mode.
The sample app also now includes tutorial-oriented helper classes in sample.services and sample.backends:
BookWriteServiceBookBulkUpdateServiceBookBulkUpdateBackend
These are deliberately small examples used by the advanced persistence-hook guides. They are there to make the documentation more inspectable, and BookBulkUpdateBackend is now also wired into the sample BookCRUDView so the async bulk example is real rather than purely illustrative.
Inline dependency demo
The sample app now includes a concrete inline dependency example:
Book.authoris the parent field.Book.genresis the dependent field.- Allowed genre choices come from
Author.genres, not from historical book rows.
field_queryset_dependencies is the primary declaration for this rule, so the same queryset restriction applies to regular forms and inline editing. BookForm stays in place only for form-specific tweaks such as keeping genres optional when an author has no allowed genres.
Worked configuration:
field_queryset_dependencies = {
"genres": {
"depends_on": ["author"],
"filter_by": {"authors": "author"},
"order_by": "name",
"empty_behavior": "all",
}
}
How to read that:
genresis the child field being restrictedauthoris the parent form field the user changesauthorsis the queryset lookup onGenre
So the child queryset is effectively narrowed as if PowerCRUD were doing:
That same rule applies in two places:
- the normal Book create/edit form
- inline editing on the Books list
When the user changes author inline, PowerCRUD posts the current row data to the dependency endpoint, rebuilds the genres widget through the same form pipeline, and swaps the refreshed widget back into the row.
Other Views
- GenreCRUDView: Minimal configuration example plus two focused delete demos: a guarded row (
Guarded Sample Genre) that disables the built-in Delete action before click, and a protected row (Protected Sample Genre) that demonstrates handled single-deleteValidationErrorresponses after submit - ProfileCRUDView: OneToOneField, the sample app's column-alignment demo (
statuscentered,priority_bandright-aligned,favorite_genreleft-aligned), inline editing, bulk operations, merged nullable relation filtering onfavorite_genre, and a static queryset rule that limitsfavorite_genrechoices to genres whose names start withS - AuthorCRUDView: Properties, filtering, template debugging, companion nullable scalar filtering on
birth_date, the sample app's red inline-edit highlight accent demo, and visible row-levelextra_actionsin the default button mode - BookCRUDView: Async bulk editing, dependent
author -> genresqueryset scoping,view_title/view_instructions/view_helpheading-area overrides,column_help_textheader tooltips, list options through Cols, semantic field-level list-cell tooltips on inline and non-inline columns, declarative modal and external list-cell link demos, selection-awareextra_buttonsin the top toolbar overflow menu, dropdown row actions that open upward for the last five rendered rows, and a guarded sample row for built-in Edit and inline update guards
The Genre sample keeps these delete demos deliberately narrow:
- If a row is named
Guarded Sample Genre,GenreCRUDView.can_delete_object(...)returnsFalseand the built-in Delete action renders disabled with a tooltip reason before the modal opens. - If a row is named
Protected Sample Genre, itsdelete()method raisesValidationError("Protected Sample Genre exists to demonstrate handled delete refusals.").
That lets the sample app show both layers of the product story on the same lightweight CRUD surface:
- pre-click Delete disablement
- post-click handled delete refusal
The Book sample includes a separate update-guard demo on the busier inline-editing screen:
- If a row is titled
Guarded Sample Book,BookCRUDView.can_update_object(...)returnsFalse. - The built-in Edit action renders disabled with a tooltip reason.
- Inline-editable cells stay visible but render as disabled affordances with the same reason, so the sample shows both update surfaces together on a screen that already exercises inline editing.
The same Book screen now also includes a bulk-validation demo:
- If a selected row is titled
Bulk Validation Sample Book,BookBulkUpdateServicerejects a sample bulkbestseller=trueupdate. - If the selection stays below
bulk_min_async_records, PowerCRUD re-renders the bulk edit modal with the handled error payload instead of treating the result as a server failure. - If the selection reaches the async threshold, the queued task fails instead and the sample async dashboard shows the failure state.
- That makes
BookCRUDViewthe sample app reference for shared sync/async bulk persistence wiring plus the current difference between sync modal errors and async task-level failures.
ProfileCRUDView alignment and queryset demo
ProfileCRUDView is the sample app reference for two smaller-but-practical list customizations:
- mixed per-column list alignment through
column_alignments - static queryset rules shared across normal forms, inline editing, and bulk edit choices
The view uses three list columns to demonstrate the alignment feature in a way that is easy to inspect on screen:
statusis a short categorical value and is centeredpriority_bandis a short categorical value and is right-alignedfavorite_genreis ordinary text and is kept left-aligned
Current sample config:
That same screen also keeps those fields in inline editing and bulk editing, so the sample shows how alignment overrides behave across the main list display states without moving the feature onto the much busier Book screen.
Profile also carries the static queryset demo for favorite_genre:
field_queryset_dependencies = {
"favorite_genre": {
"static_filters": {"name__startswith": "S"},
"order_by": "name",
}
}
How to read that:
favorite_genreis the field being restrictedstatic_filtersapplies a fixed queryset rule with no parent field involvedorder_bykeeps the remaining choices sorted predictably
That same static rule is reused in three places:
- the normal Profile create/edit form
- inline editing on the Profiles list
- the bulk edit dropdown for
favorite_genre
This makes ProfileCRUDView the sample app reference for static queryset rules and mixed list alignment overrides, while BookCRUDView remains the reference for dynamic parent/child dependencies.
Example BookCRUDView action config:
extra_actions_mode = "dropdown"
extra_buttons_mode = "dropdown"
extra_actions_dropdown_open_upward_bottom_rows = 5
extra_buttons = [
{
"url_name": "home",
"text": "Home in Modal!",
"display_modal": True,
"modal_box_classes": "modal-box flex max-h-[calc(100dvh-2rem)] w-11/12 max-w-3xl flex-col",
},
{
"url_name": "sample:bigbook-selected-summary",
"text": "Selected Summary",
"display_modal": True,
"uses_selection": True,
"selection_min_count": 1,
"selection_min_behavior": "disable",
},
]
extra_actions = [
{
"url_name": "sample:bigbook-description-preview",
"text": "Description Preview",
"needs_pk": True,
"display_modal": True,
"disabled_state": "get_description_preview_disabled_state",
"modal_box_classes": "modal-box flex max-h-[calc(100dvh-2rem)] w-11/12 max-w-5xl flex-col",
},
]
That lets the sample app demonstrate selection-aware header actions, conditionally disabled row actions, and per-trigger modal sizing in the same CRUD surface. disabled_state is the single-hook disabled contract: return a non-empty string to disable the action and show that string as the reason. Selected Summary intentionally uses the view default modal width, while Home in Modal! shows a header-button override. The modal_box_classes entries are full replacement strings: they keep the default viewport-height classes and add per-trigger width classes for those specific modal calls.
Management Commands
# Create default authors (25) and books (50)
./manage.py create_sample_data
# Create 100 authors and 1000 books
./manage.py create_sample_data --authors 100 --books 1000
# Create 500 books with an average of 5 books per author
./manage.py create_sample_data --books 500 --books-per-author 5
Generates realistic sample data:
- Random author names and bios
- Book titles, descriptions, publication dates, and ISBNs
- Progress feedback during creation
- Allows control over the distribution of books per author using
--books-per-author.
./manage.py clear_sample_data --all # Delete everything
./manage.py clear_sample_data --books # Books only
./manage.py clear_sample_data --authors # Authors only (cascades to books)
Safety features:
- Only works when
DEBUG=True - Handles protected foreign key relationships
- Provides clear feedback on deletion counts
Forms & Filters
Custom Forms
- BookForm: Date widgets, field selection, crispy forms integration, and form-specific tweaks while
field_queryset_dependencieshandles the sharedauthor -> genresqueryset rule - AuthorForm: Demonstrates form customization patterns
Advanced Filtering
- BookFilterSet: HTMX integration, custom widget attributes
- AuthorFilterSet: Inherits from
HTMXFilterSetMixinfor reactive filtering - Shows crispy forms layout integration
Development Use Cases
Feature Testing
- Bulk Operations: Test edit/delete on multiple books with validation
- Async Processing: Book deletion includes artificial delay for async testing
- Complex Relationships: M2M genres, ForeignKey authors, OneToOne profiles
- Field Types: Generated fields, boolean displays, date formatting
UI/UX Testing
- Modal Interactions: All CRUD operations in modals
- HTMX Features: Reactive filtering, pagination, form updates
- Inline Dependencies: Changing a Book author inline immediately refreshes the allowed genre choices derived from the shared form dependency config
- Inline Validation Errors: Clear a Book title inline and save to see the row stay open with field-level error text and a field popover
- Static Queryset Rules: Editing a Profile only offers
favorite_genrechoices whose names start withS, and the same restriction carries through inline and bulk edit - CSS Frameworks: Easy switching between daisyUI and Bootstrap
- Responsive Design: Table layouts with column width controls
Manual Inline Error Repro
Use the Book list to inspect the inline validation UI:
- Open
/sample/bigbook/. - Click the inline edit affordance on a book title.
- Clear the title field.
- Click Save.
Expected result: the row remains in edit mode, the title field is marked invalid, and a forced-visible popover says This field is required.. The inline error text remains in the markup as an accessibility/fallback message but is visually hidden while the popover is active.
To check inline searchable selects on the same screen, click the author inline-edit affordance. The field should focus and open the dropdown.
Configuration Examples
- Property Display: Custom property names and formatting
- Field Exclusions: Hide sensitive/internal fields
- Custom Actions: Additional buttons and row-level actions, including dropdown-style overflow for
extra_actions - Sorting & Filtering: Advanced queryset manipulation
Getting Started
- Run migrations:
- Create sample data:
-
Access the views:
-
Books: http://localhost:8001/sample/bigbook/
- Authors: http://localhost:8001/sample/author/
- Genres: http://localhost:8001/sample/genre/
-
Profiles: http://localhost:8001/sample/profile/
-
Test features:
-
Try bulk edit operations on books
- Use filtering and sorting
- Test modal create/edit/delete
- Open a Book row inline, change
author, and confirmgenresrefreshes immediately without saving - Experiment with different page sizes
How to try the inline dependency demo
- Open the Books list at
/sample/bigbook/. - Edit an Author and assign one or more genres to that author.
- Open a Book edit form in a modal and confirm the genres dropdown only shows genres for that author.
- Open a Book row in inline mode.
- Change the Book author.
- Re-open the Book genres control before saving.
- Confirm the available genres now match the selected author’s
genresrelation.
How to adapt this pattern downstream
If your project used an older inline-only dependency pattern, the sample app demonstrates the preferred replacement:
field_queryset_dependencies = {
"cmms_asset": {
"depends_on": ["cmms_property_asset_type_override"],
"filter_by": {
"property_asset_type_override": "cmms_property_asset_type_override",
},
"empty_behavior": "none",
}
}
The key point is that filter_by maps:
- queryset lookup on the child field's queryset model
- to parent form field name
Inline refresh wiring is derived automatically from this declaration.
The browser regression for this flow lives in test_inline_dependencies.py.
Development Notes
The sample app is designed to be:
- Comprehensive: Covers all major powercrud features
- Realistic: Uses believable domain models and relationships
- Educational: Clear examples of configuration patterns
- Extensible: Easy to add new models or features for testing
When developing new powercrud features, add corresponding examples to the sample app to ensure comprehensive testing coverage.