Skip to content

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-formset plugin installed
  • Layout API with Field components

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

# Reference fields with dot notation
".field_name"

# Example
calculate=".price * .quantity"

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 - missing dot prefix
calculate="quantity * price"

# CORRECT
calculate=".quantity * .price"
# 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():

calculate="round(.quantity * .price, 2)"

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