Metadata-Version: 2.4
Name: fastapi-mongo-admin
Version: 2.0.1
Summary: Django-inspired server-rendered admin for FastAPI and MongoDB
Project-URL: Homepage, https://github.com/tushortz/fastapi-mongo-admin
Project-URL: Documentation, https://fastapi-mongo-admin.readthedocs.io/
Project-URL: Repository, https://github.com/tushortz/fastapi-mongo-admin
Project-URL: Issues, https://github.com/tushortz/fastapi-mongo-admin/issues
Author-email: Taiwo Kareem <taiwo.kareem36@gmail.com>
License: MIT
License-File: LICENSE
Keywords: admin,django-admin,fastapi,mongodb,motor,pymongo
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Database
Requires-Python: >=3.10
Requires-Dist: fastapi>=0.136.3
Requires-Dist: jinja2>=3.1.6
Requires-Dist: motor>=3.7.1
Requires-Dist: openpyxl>=3.1.5
Requires-Dist: pydantic<3.0.0,>=2.13.4
Requires-Dist: pymongo>=4.17.0
Requires-Dist: python-multipart>=0.0.32
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: tomli-w>=1.2.0
Requires-Dist: tomli>=2.2.1; python_version < '3.11'
Provides-Extra: dev
Requires-Dist: bandit>=1.9.4; extra == 'dev'
Requires-Dist: black>=26.1.0; extra == 'dev'
Requires-Dist: email-validator>=2.3.0; extra == 'dev'
Requires-Dist: httpx>=0.28.1; extra == 'dev'
Requires-Dist: mongomock>=4.3.0; extra == 'dev'
Requires-Dist: mypy>=2.1.0; extra == 'dev'
Requires-Dist: pysentry-rs>=0.4.6; extra == 'dev'
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
Requires-Dist: pytest>=9.0.3; extra == 'dev'
Requires-Dist: ruff>=0.15.16; extra == 'dev'
Requires-Dist: uvicorn[standard]>=0.49.0; extra == 'dev'
Provides-Extra: docs
Requires-Dist: sphinx-rtd-theme<4,>=2.0; extra == 'docs'
Requires-Dist: sphinx<9,>=7.4; extra == 'docs'
Description-Content-Type: text/markdown

# FastAPI Mongo Admin

A Django-inspired, server-rendered admin framework for FastAPI and MongoDB.

**v2.0.0** replaces the legacy React SPA with a Jinja2 + HTMX admin interface, a full `ModelAdmin` configuration API, pluggable authentication, and support for both async (Motor) and sync (PyMongo) MongoDB backends.

## Screenshots

![Customer changelist](images/list.png)

![Customer change form](images/detail.png)

## Key Features

- **Django-like registry** — `site.register(Model, AdminClass)`
- **Server-rendered admin UI** — changelist, add/change forms, delete confirmation, bulk actions
- **List filters** — choice, boolean, date, related, and custom `ListFilter` classes
- **Pydantic-driven forms** — validation and schema inference from your models
- **Field mapping** — map model fields to different MongoDB keys
- **Pluggable auth** — wire any FastAPI `Depends` authentication/authorization
- **Sync + async MongoDB** — `mode="async"` (Motor) or `mode="sync"` (PyMongo)
- **Customization** — template overrides, `ModelAdmin` hooks, custom admin views
- **JSON API** — `/admin/api/{collection}/` for programmatic access (read-only by default; optional write methods for `/docs`)


## Breaking Changes (v0.x → v2)

- React UI and `/admin-ui` mount removed; admin lives at `/admin`
- `MongoAdmin` alias removed; use `AdminSite` or `site`
- Built-in demo token auth removed; provide `auth_dependency`
- Legacy `/admin/collections/.../documents` routes removed

## Installation

```bash
# Using uv (recommended)
uv add fastapi-mongo-admin

# Or pip
pip install fastapi-mongo-admin
```

## Quick Start

### 1. Define models and admin classes

```python
from pydantic import BaseModel
from fastapi_mongo_admin import ModelAdmin, site, display, action
from fastapi_mongo_admin.admin.filters import ChoiceListFilter, DateFieldListFilter


class Product(BaseModel):
    name: str
    price: float
    category: str
    active: bool = True


class ProductAdmin(ModelAdmin):
    model = Product
    collection_name = "products"
    list_display = ["name", "category", "price", "active"]
    list_filter = ["category", "active"]
    search_fields = ["name", "category"]
    list_per_page = 25
    choices = {
        "category": [("books", "Books"), ("electronics", "Electronics")],
    }

    @display(description="Name")
    def name_upper(self, obj: dict) -> str:
        return str(obj.get("name", "")).upper()

    @action("Deactivate selected")
    async def deactivate_selected(self, request, queryset: list[dict]) -> None:
        pass


site.register(Product, ProductAdmin)
```

### 2. Mount in FastAPI (async)

```python
from fastapi import FastAPI
from motor.motor_asyncio import AsyncIOMotorClient
from fastapi_mongo_admin import mount_admin_app

app = FastAPI()
client = AsyncIOMotorClient("mongodb://localhost:27017")
database = client["my_db"]


async def get_database():
    return database


mount_admin_app(app, get_database, admin_site=site, mode="async")
```

### 3. Sync MongoDB

```python
from pymongo import MongoClient
from fastapi_mongo_admin import mount_admin_app

client = MongoClient("mongodb://localhost:27017")
db = client["my_db"]

mount_admin_app(app, lambda: db, admin_site=site, mode="sync")
```

Visit `http://localhost:8000/admin/` for the admin index.

## Authentication

Pass any FastAPI-compatible dependency:

```python
from fastapi import Depends, HTTPException


async def get_admin_user():
  # Your JWT/session validation here
  return {"id": "user-1", "is_staff": True}


mount_admin_app(
    app,
    get_database,
    admin_site=site,
    auth_dependency=get_admin_user,
)
```

## JSON API

Read endpoints are always available at `/admin/api/{collection}/`. By default only `GET` operations appear in OpenAPI (`/docs`).

Enable `POST`, `PUT`, `PATCH`, and `DELETE` routes (and Swagger documentation):

```python
mount_admin_app(
    app,
    get_database,
    admin_site=site,
    auth_dependency=get_admin_user,
    api_write_methods=True,
)
```

| Method | URL | Description |
|--------|-----|-------------|
| `GET` | `/admin/api/{collection}/` | Paginated list (`page`, `q`) |
| `GET` | `/admin/api/{collection}/{id}` | Single document |
| `POST` | `/admin/api/{collection}/` | Create (when `api_write_methods=True`) |
| `PUT` | `/admin/api/{collection}/{id}` | Update |
| `PATCH` | `/admin/api/{collection}/{id}` | Partial update |
| `DELETE` | `/admin/api/{collection}/{id}` | Delete |

Write requests use the same Pydantic validation and `ModelAdmin` permission hooks as the HTML admin.

Override per-model permissions on `ModelAdmin`:

```python
class ProductAdmin(ModelAdmin):
    def has_add_permission(self, request, user=None) -> bool:
        return bool(user and user.get("is_staff"))
```

## ModelAdmin Options

| Option | Description |
|--------|-------------|
| `model` | Pydantic model for validation |
| `collection_name` | MongoDB collection (required) |
| `list_display` | Changelist columns (fields or `@display` methods) |
| `list_display_links` | Clickable columns |
| `list_filter` | Field names or `ListFilter` subclasses |
| `search_fields` | Text search fields |
| `list_per_page` | Pagination size (default 25) |
| `ordering` | Default sort, e.g. `["-created_at"]` |
| `date_hierarchy` | Date drill-down field |
| `list_select_related` | `{"field": "collection"}` for related lookups |
| `fieldsets` | Grouped change form layout |
| `readonly_fields` | Non-editable fields |
| `field_mapping` | Model field → DB field mapping |
| `actions` | Bulk action method names |
| `choices` | Choice lookups for filters/forms |
| `date_format` | Display format for `date` fields (default: `8 Apr 2026`) |
| `datetime_format` | Display format for `datetime` fields (default: `8 Apr 2026, 7:32pm`) |

## Date and Time Display

`date` and `datetime` fields are formatted automatically on changelists and readonly form fields. Form inputs still use ISO values for HTML date/datetime pickers.

Default formats:

- **Date:** `8 Apr 2026`
- **Datetime:** `8 Apr 2026, 7:32pm`

Customize per model:

```python
class OrderAdmin(ModelAdmin):
    date_format = "j M Y"                  # Django-style tokens
    datetime_format = "%d/%m/%Y %H:%M"     # or standard strftime
```

Override completely:

```python
def format_datetime_value(self, value) -> str:
    return my_formatter(value)
```


## Template Customization

```python
from pathlib import Path

site = AdminSite(template_dirs=[Path("myapp/templates")])

class ProductAdmin(ModelAdmin):
    change_list_template = "myapp/admin/product_change_list.html"
```

Register custom views:

```python
async def reports(request):
    return {"report": "data"}

site.register_view("Reports", "/reports/", reports)
```

## URL Scheme

| URL | View |
|-----|------|
| `GET /admin/` | Admin index |
| `GET /admin/{collection}/` | Changelist |
| `GET /admin/{collection}/add/` | Add form |
| `GET/POST /admin/{collection}/{id}/change/` | Change form |
| `POST /admin/{collection}/{id}/delete/` | Delete |
| `POST /admin/{collection}/action/` | Bulk actions |
| `GET /admin/api/{collection}/` | JSON list API |
| `GET /admin/api/{collection}/{id}` | JSON detail API |
| `POST/PUT/PATCH/DELETE /admin/api/...` | JSON write API (`api_write_methods=True`) |


## Development

```bash
make install   # uv sync --group dev
make test      # pytest
make lint      # ruff
make secure    # bandit + pysentry-rs
make docs      # build Sphinx HTML docs
make help      # list all targets
```

## License

MIT
