Computed Fields Guide - djadmin-formset¶
Plugin: djadmin-formset
Version: 0.1.0 (Alpha)
Last Updated: October 21, 2025
Overview¶
Computed fields allow you to automatically calculate field values based on other fields in the form. Calculations happen instantly in the browser as users type, with no page refresh required.
Prerequisites¶
djadmin-formsetplugin installed- Layout API with
Fieldcomponents
Basic Usage¶
Simple Calculation¶
Use the calculate attribute to define a calculation expression:
from djadmin import ModelAdmin, register, Layout, Field, Row
@register(OrderItem)
class OrderItemAdmin(ModelAdmin):
layout = Layout(
Row(
Field('quantity', css_classes=['flex-1']),
Field('price', css_classes=['flex-1']),
# Auto-calculate total
Field('total',
calculate='.quantity * .price',
css_classes=['flex-1']
),
),
)
How it works:
- When quantity or price changes, total is recalculated instantly
- No JavaScript code needed - django-formset handles it
- Field updates in real-time as user types
Expression Syntax¶
Calculation expressions use django-formset's expression language (similar to JavaScript):
Accessing Field Values¶
Arithmetic Operators¶
# Addition
calculate=".price + .tax"
# Subtraction
calculate=".total - .discount"
# Multiplication
calculate=".price * .quantity"
# Division
calculate=".total / .quantity"
# Modulo
calculate=".total % 10"
Multiple Operations¶
# Compound expressions with parentheses
calculate="(.price * .quantity) - .discount"
# Multi-step calculation
calculate="(.base_price + .shipping) * (1 + .tax_rate)"
Common Use Cases¶
Example 1: Order Line Item Total¶
@register(OrderItem)
class OrderItemAdmin(ModelAdmin):
layout = Layout(
Field('product'),
Row(
Field('quantity', css_classes=['flex-1']),
Field('unit_price', css_classes=['flex-1']),
Field('discount_percent', css_classes=['flex-1']),
),
Row(
Field('subtotal',
calculate='.quantity * .unit_price',
css_classes=['flex-1']
),
Field('discount_amount',
calculate='(.quantity * .unit_price) * (.discount_percent / 100)',
css_classes=['flex-1']
),
Field('total',
calculate='(.quantity * .unit_price) - ((.quantity * .unit_price) * (.discount_percent / 100))',
css_classes=['flex-1']
),
),
)
Example 2: Tax Calculation¶
@register(Invoice)
class InvoiceAdmin(ModelAdmin):
layout = Layout(
Field('subtotal'),
Field('tax_rate', help_text='Enter as decimal (e.g., 0.08 for 8%)'),
# Auto-calculate tax amount
Field('tax_amount',
calculate='.subtotal * .tax_rate'
),
# Auto-calculate total
Field('total',
calculate='.subtotal + (.subtotal * .tax_rate)'
),
)
Example 3: Percentage Calculation¶
@register(SalesReport)
class SalesReportAdmin(ModelAdmin):
layout = Layout(
Row(
Field('target_sales', css_classes=['flex-1']),
Field('actual_sales', css_classes=['flex-1']),
),
# Calculate achievement percentage
Field('achievement_percent',
calculate='(.actual_sales / .target_sales) * 100',
help_text='Automatically calculated'
),
)
Example 4: Date-Based Calculations¶
@register(Subscription)
class SubscriptionAdmin(ModelAdmin):
layout = Layout(
Field('start_date'),
Field('duration_months'),
# Calculate end date (approximation - 30 days per month)
Field('estimated_end_date',
calculate='.start_date + (.duration_months * 30)'
),
)
Note: For complex date calculations, consider using server-side logic in clean() methods instead.
Example 5: Conditional Calculation¶
You can combine computed fields with conditional fields:
@register(Discount)
class DiscountAdmin(ModelAdmin):
layout = Layout(
Field('original_price'),
Field('discount_type'), # 'percentage' or 'fixed'
# Show/calculate based on discount type
Field('discount_percentage',
show_if=".discount_type === 'percentage'"
),
Field('discount_fixed',
show_if=".discount_type === 'fixed'"
),
# Calculate final price based on type
Field('final_price',
calculate=(
".discount_type === 'percentage' ? "
".original_price * (1 - .discount_percentage / 100) : "
".original_price - .discount_fixed"
)
),
)
Advanced Features¶
Ternary Operator¶
Use conditional (ternary) expressions for conditional calculations:
# Syntax: condition ? value_if_true : value_if_false
Field('shipping_cost',
calculate=(
".total > 100 ? 0 : " # Free shipping over $100
".total > 50 ? 5 : " # $5 shipping $50-$100
"10" # $10 shipping under $50
)
)
Math Functions¶
django-formset supports common math functions:
# Round to 2 decimal places
calculate="round(.price * .quantity, 2)"
# Absolute value
calculate="abs(.value)"
# Min/Max
calculate="max(.price1, .price2)"
calculate="min(.discount1, .discount2)"
# Power
calculate="pow(.base, 2)" # Square
Null/Default Handling¶
Handle null or empty values:
# Provide default value if field is null
calculate="(.quantity || 0) * (.price || 0)"
# Only calculate if both fields have values
calculate=".quantity && .price ? .quantity * .price : 0"
Read-Only Computed Fields¶
Computed fields are typically read-only to prevent user edits from being overwritten. Configure in your Django model:
# models.py
class OrderItem(models.Model):
quantity = models.IntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
# Computed field - editable=False makes it read-only
total = models.DecimalField(
max_digits=10,
decimal_places=2,
editable=False, # Read-only in forms
)
Or make it read-only in the layout:
from django import forms
@register(OrderItem)
class OrderItemAdmin(ModelAdmin):
layout = Layout(
Field('quantity'),
Field('price'),
Field('total',
calculate='.quantity * .price',
widget=forms.TextInput(attrs={'readonly': True}), # Read-only
),
)
Server-Side Validation¶
IMPORTANT: Computed fields are calculated client-side only. Always validate and recalculate on the server:
# forms.py
class OrderItemForm(forms.ModelForm):
class Meta:
model = OrderItem
fields = ['quantity', 'price', 'total']
def clean(self):
"""Validate and recalculate on server-side."""
data = super().clean()
quantity = data.get('quantity', 0)
price = data.get('price', 0)
# Recalculate server-side
calculated_total = quantity * price
# Allow for rounding differences
submitted_total = data.get('total', 0)
if abs(calculated_total - submitted_total) > 0.01:
raise forms.ValidationError(
f"Total mismatch. Expected {calculated_total}, got {submitted_total}"
)
# Always use server-calculated value
data['total'] = calculated_total
return data
Or use a model save() override:
# models.py
class OrderItem(models.Model):
# ...
def save(self, *args, **kwargs):
"""Always recalculate before saving."""
self.total = self.quantity * self.price
super().save(*args, **kwargs)
Decimal Precision¶
For financial calculations, ensure proper decimal precision:
# Use round() for display precision
Field('total',
calculate='round(.quantity * .price, 2)', # 2 decimal places
)
# Or in the model
class OrderItem(models.Model):
total = models.DecimalField(
max_digits=10,
decimal_places=2, # Enforced at database level
)
Performance Considerations¶
Client-Side Benefits¶
- ✅ Instant feedback (no server request)
- ✅ Works offline after page load
- ✅ Reduces server load
- ✅ Better user experience
Limitations¶
- ❌ Cannot access database values
- ❌ Cannot call Django/Python functions
- ❌ Limited to current form fields
- ❌ Complex calculations may be slow in browser
When to Use Server-Side¶
Use server-side calculations when:
- Calculation requires database lookup
- Formula is extremely complex
- Need to enforce business rules
- Calculation involves external APIs
# Server-side example
class OrderForm(forms.ModelForm):
def clean(self):
data = super().clean()
# Complex calculation requiring database
product = data.get('product')
if product:
# Lookup current price from database
data['price'] = product.get_current_price()
data['total'] = data['quantity'] * data['price']
return data
Troubleshooting¶
Calculation Not Working¶
Symptom: Field doesn't update when dependencies change
Checklist:
1. ✅ Plugin installed? 'djadmin_formset' in INSTALLED_APPS
2. ✅ JavaScript loaded? Check browser console
3. ✅ Syntax correct? Use .fieldname notation
4. ✅ Field name matches model field exactly
Debug:
// Browser console
const field = document.querySelector('[name="total"]');
console.log(field.getAttribute('df-calculate'));
Wrong Calculation Result¶
Common issues:
# WRONG - incorrect operator precedence
calculate=".price + .shipping * .quantity"
# Result: price + (shipping * quantity)
# CORRECT - use parentheses
calculate="(.price + .shipping) * .quantity"
Null/Undefined Errors¶
Symptom: Calculation results in NaN or errors
Solution: Handle null values:
# Provide defaults
calculate="(.quantity || 0) * (.price || 0)"
# Or check before calculating
calculate=".quantity && .price ? .quantity * .price : 0"
Rounding Errors¶
Symptom: Decimal precision issues (e.g., 10.00000001)
Solution: Use round():
Best Practices¶
1. Keep Calculations Simple¶
# Good - simple, clear
calculate=".quantity * .price"
# Avoid - overly complex
calculate="(((.base_price * .quantity) + .shipping) * (1 + .tax_rate)) - (.discount_percent / 100 * ((.base_price * .quantity) + .shipping))"
For complex calculations, break into multiple steps:
Field('subtotal', calculate='.quantity * .price'),
Field('tax', calculate='.subtotal * .tax_rate'),
Field('total', calculate='.subtotal + .tax'),
2. Always Validate Server-Side¶
Never trust client-side calculations for data integrity:
def clean(self):
# Always recalculate on server
data = super().clean()
data['total'] = data['quantity'] * data['price']
return data
3. Provide Visual Feedback¶
Make it clear that a field is computed:
Field('total',
calculate='.quantity * .price',
help_text='Automatically calculated',
widget=forms.TextInput(attrs={
'readonly': True,
'class': 'bg-gray-100', # Visual indicator
}),
)
4. Document Formulas¶
For complex calculations, document the formula:
Field('commission',
# Commission = 10% base + 2% bonus if sales > $10,000
calculate=(
".sales > 10000 ? "
".sales * 0.12 : " # 10% + 2% bonus
".sales * 0.10" # 10% base only
),
help_text='10% base rate, +2% bonus over $10k',
)
5. Test Edge Cases¶
Test calculations with edge values:
def test_computed_total():
"""Test computed field calculations."""
# Test normal values
# Test zero values
# Test negative values
# Test large values
# Test decimal precision
Expression Reference¶
Arithmetic Operators¶
| Operator | Example | Description |
|---|---|---|
+ |
.a + .b |
Addition |
- |
.a - .b |
Subtraction |
* |
.a * .b |
Multiplication |
/ |
.a / .b |
Division |
% |
.a % .b |
Modulo (remainder) |
Comparison Operators¶
| Operator | Example | Description |
|---|---|---|
=== |
.a === .b |
Equality |
!== |
.a !== .b |
Inequality |
> |
.a > .b |
Greater than |
< |
.a < .b |
Less than |
>= |
.a >= .b |
Greater or equal |
<= |
.a <= .b |
Less or equal |
Logical Operators¶
| Operator | Example | Description |
|---|---|---|
&& |
.a && .b |
Logical AND |
|| |
.a || .b |
Logical OR |
! |
!.a |
Logical NOT |
?: |
.x ? .a : .b |
Ternary (conditional) |
Math Functions¶
| Function | Example | Description |
|---|---|---|
round(x, d) |
round(.price, 2) |
Round to d decimals |
abs(x) |
abs(.value) |
Absolute value |
min(a, b) |
min(.x, .y) |
Minimum value |
max(a, b) |
max(.x, .y) |
Maximum value |
pow(x, y) |
pow(.base, 2) |
Power (x^y) |
Default Values¶
| Expression | Example | Description |
|---|---|---|
|| |
.quantity || 0 |
Use 0 if null |
?: |
.x ? .x : 0 |
Explicit default |
See Also¶
- Conditional Fields Guide - Show/hide fields dynamically
- Inline Editing Guide - Collections with computed fields
- Layout Integration Guide - Overall plugin architecture
- django-formset Expressions