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"
column_help_text = {
"title": "The book title shown throughout the app.",
"isbn_empty": "Shows whether this row currently has an ISBN value.",
}
list_cell_tooltip_fields = ["title", "isbn_empty"]
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', 'genres']
dropdown_sort_options = {"author": "name"}
inline_edit_fields = ['title', 'author', 'genres', 'published_date', 'bestseller', 'isbn', 'description']
extra_buttons = [...] # Includes a selection-aware "Selected Summary" demo
extra_actions = [...] # Includes a conditional "Description Preview" demo
def get_list_cell_tooltip(self, obj, field_name, *, is_property, request=None):
if field_name == "title":
return f"{obj.author}\n{obj.pages} pages"
if field_name == "isbn_empty":
return (
"This book does not currently have an ISBN."
if obj.isbn_empty
else f"ISBN: {obj.isbn}"
)
return None
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 column_help_text for one field and one property so the sample list shows the header-help tooltip pattern, and list_cell_tooltip_fields plus get_list_cell_tooltip(...) so the list also demonstrates semantic field-level tooltips on both a normal text cell 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, header help text, and semantic cell tooltip text are all rendered as plain escaped text rather than HTML.
The sample frontend now also demonstrates the downstream tooltip-styling path. In src/config/static/css/app.custom.css, the sample app overrides --pc-tooltip-bg and --pc-tooltip-fg to use daisyUI's primary semantic tokens. 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 - semantic field-level list-cell tooltips on the
titlefield andisbn_emptyproperty - app-level tooltip theme overrides 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.
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, inline editing, bulk operations, merged nullable relation filtering on
favorite_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, and visible row-levelextra_actionsin the default button mode - BookCRUDView: Async bulk editing, dependent
author -> genresqueryset scoping,view_title/view_instructionsheading-area overrides,column_help_textheader tooltips, semantic field-level list-cell tooltips, selection-awareextra_buttons, 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.
Static queryset demo
The sample app also includes a static queryset example on ProfileCRUDView:
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, while BookCRUDView remains the reference for dynamic parent/child dependencies.
Example BookCRUDView action config:
extra_actions_mode = "dropdown"
extra_actions_dropdown_open_upward_bottom_rows = 5
extra_buttons = [
{
"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_if": "is_description_preview_disabled",
"disabled_reason": "get_description_preview_disabled_reason",
},
]
That lets the sample app demonstrate both selection-aware header actions and conditionally disabled row actions in the same CRUD surface.
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
- 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
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.