---
title: Admin Integration
description: MoneyFieldDisplay, MoneyFieldWidget, and Django Admin integration for currency fields.
sidebar_label: Admin Integration
sidebar_position: 4
keywords:
  - django admin currency
  - money field admin
  - currency widget
  - moneyfield display
---

import { Callout } from 'nextra/components';

# Admin Integration

Django-CFG provides seamless admin integration for `MoneyField` with automatic widget detection and a dedicated display field.

## Overview

<Callout type="info">
**No extra mixins needed!** `PydanticAdmin` automatically includes `MoneyFieldAdminMixin`.
Just inherit from `PydanticAdmin` and the currency features work out of the box.
</Callout>

| Component | Purpose |
|-----------|---------|
| `PydanticAdmin` | Base admin class (includes MoneyFieldAdminMixin) |
| `MoneyFieldDisplay` | Display field for admin lists |
| `MoneyFieldWidget` | Form widget for editing (auto-applied) |

---

## MoneyFieldDisplay

Compact display field for admin lists. Auto-detects related fields from the base name.

### Import

```python
from django_cfg.modules.django_admin import MoneyFieldDisplay
```

### Basic Usage

```python
from django_cfg.modules.django_admin import AdminConfig, MoneyFieldDisplay

config = AdminConfig(
    model=Vehicle,
    list_display=["id", "name", "price"],

    display_fields=[
        # Auto-detects: price_currency, price_target, price_rate, price_rate_at
        MoneyFieldDisplay(
            name="price",
            title="Price",
        ),
    ],
)
```

**What it displays:**
```
₩15,700,000 → $10,645.05
Rate: 0.000678 • 2h ago
```

### Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | Required | Base field name (e.g., "price") |
| `title` | `str` | `None` | Column header title |
| `currency_field` | `str` | `{name}_currency` | Field with currency code |
| `target_field` | `str` | `{name}_target` | Field with converted amount |
| `target_currency` | `str` | `"USD"` | Target currency code |
| `rate_field` | `str` | `{name}_rate` | Field with exchange rate |
| `rate_at_field` | `str` | `{name}_rate_at` | Field with rate timestamp |
| `show_rate` | `bool` | `True` | Show rate info line |
| `precision` | `int` | `2` | Decimal places |
| `compact` | `bool` | `True` | Single-line display |

### Explicit Field Names

Override auto-detected field names if needed:

```python
MoneyFieldDisplay(
    name="price",
    title="Price",
    currency_field="price_currency",
    target_field="price_target",
    target_currency="USD",
    rate_field="price_rate",
    rate_at_field="price_rate_at",
)
```

### Display Options

```python
MoneyFieldDisplay(
    name="price",
    title="Price",
    show_rate=False,     # Hide rate info line
    precision=0,         # No decimals
    compact=False,       # Multi-line display
)
```

---

## MoneyFieldWidget

Auto-applied to `MoneyField` in admin forms. No configuration needed.

### Visual Layout

```
┌─────────────────────────────────────────────────────────────┐
│  Amount                    │  Currency                      │
│  ┌──────────────────────┐  │  ┌──────────────────────────┐  │
│  │ 50,000,000           │  │  │ KRW ▼                    │  │
│  └──────────────────────┘  │  └──────────────────────────┘  │
│  → $33,900 | 1 KRW = 0.000678 USD • 2h ago   [rates ↗]      │
└─────────────────────────────────────────────────────────────┘
```

### Features

- **Amount input** (2/3 width) + **Currency dropdown** (1/3 width)
- **Live conversion** updates as you type (Alpine.js)
- **Rate info** with timestamp ("2h ago")
- **Link to CurrencyRate admin** for rate management
- **Dark mode support**

### Live Recalculation

When you change currency or amount:

1. Widget reads rates from embedded JSON (passed at render)
2. Alpine.js recalculates conversion
3. Display updates instantly (no server roundtrip)

---

## Complete Example

```python
from django.contrib import admin
from django_cfg.modules.django_admin import (
    AdminConfig,
    MoneyFieldDisplay,
    FieldsetConfig,
    ShortUUIDField,
    BadgeField,
)
from django_cfg.modules.django_admin.base import PydanticAdmin

from .models import Vehicle

config = AdminConfig(
    model=Vehicle,

    # Performance
    select_related=["source", "brand"],

    # List view columns
    list_display=["id", "name", "price", "status"],

    # Display field configurations
    display_fields=[
        ShortUUIDField(name="id", title="ID"),

        # MoneyFieldDisplay - auto-detects all price-related fields
        MoneyFieldDisplay(
            name="price",
            title="Price",
        ),

        BadgeField(
            name="status",
            title="Status",
            label_map={
                "active": "success",
                "sold": "danger",
            },
        ),
    ],

    # Form fieldsets
    fieldsets=[
        FieldsetConfig(
            title="Basic Info",
            fields=["name", "status"],
        ),
        FieldsetConfig(
            title="Pricing",
            fields=["price"],  # MoneyFieldWidget auto-applied
        ),
    ],
)


@admin.register(Vehicle)
class VehicleAdmin(PydanticAdmin):
    """PydanticAdmin includes MoneyFieldAdminMixin automatically."""
    config = config
```

---

## How MoneyFieldAdminMixin Works

`MoneyFieldAdminMixin` is automatically included in `PydanticAdmin`. Here's what it provides:

### 1. Auto-Detection of MoneyField

On admin init, it scans the model for `MoneyField` instances and registers related fields.

### 2. Readonly Fields

Automatically adds `_target`, `_rate`, `_rate_at` as readonly fields:

```python
def get_readonly_fields(self, request, obj=None):
    readonly = list(super().get_readonly_fields(request, obj))
    for field_name in self._get_money_field_names():
        for suffix in ["_target", "_rate", "_rate_at"]:
            aux_field = f"{field_name}{suffix}"
            if self._field_exists(aux_field):
                readonly.append(aux_field)
    return readonly
```

### 3. Widget Assignment

Replaces default widget with `MoneyFieldWidget`:

```python
def formfield_for_dbfield(self, db_field, request, **kwargs):
    if db_field.__class__.__name__ == "MoneyField":
        kwargs["widget"] = MoneyFieldWidget(
            default_currency=current_currency,
            target_currency=target_currency,
        )
    return super().formfield_for_dbfield(db_field, request, **kwargs)
```

### 4. Currency Saving

Handles currency field saving from POST data:

```python
def save_model(self, request, obj, form, change):
    for field_name in self._get_money_field_names():
        currency_key = f"{field_name}_currency"
        if currency_key in request.POST:
            setattr(obj, currency_key, request.POST[currency_key])
    super().save_model(request, obj, form, change)
```

---

## Currency Symbols

`MoneyFieldDisplay` includes built-in currency symbols:

| Code | Symbol |
|------|--------|
| USD | $ |
| EUR | € |
| GBP | £ |
| JPY | ¥ |
| KRW | ₩ |
| CNY | ¥ |
| RUB | ₽ |
| BTC | ₿ |
| ETH | Ξ |

Unknown currencies display as `"CODE "` (code + space).

---

## Troubleshooting

### Widget Not Appearing

Make sure you're using `PydanticAdmin`:

```python
from django_cfg.modules.django_admin.base import PydanticAdmin

@admin.register(Vehicle)
class VehicleAdmin(PydanticAdmin):  # ✅ Correct
    config = config
```

### Currency Not Saving

Check that the form includes the currency field in POST data. The widget automatically handles this.

### Conversion Shows "N/A"

1. Check that rates exist in `CurrencyRate` table
2. Click "Update All Rates" in CurrencyRate admin
3. Verify `price_currency` has a value

---

## See Also

- [Quick Start](./quick-start) - Get started in 5 minutes
- [MoneyField](./money-field) - Model field deep dive
- [API Reference](./api-reference) - Complete API documentation
