Metadata-Version: 2.5
Name: polyadmin
Version: 0.1.0b3
Summary: PolyAdmin: cross-language server-rendered admin framework (Python core)
Project-URL: Documentation, https://magicrodri.github.io/polyadmin-docs/
Project-URL: Source, https://github.com/MagicRodri/polyadmin
Project-URL: Issues, https://github.com/MagicRodri/polyadmin/issues
License: MIT
License-File: LICENSE
Keywords: admin,fastapi,htmx,server-rendered
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 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
Requires-Python: >=3.10
Requires-Dist: jinja2>=3.1
Provides-Extra: export-xlsx
Requires-Dist: openpyxl>=3.1; extra == 'export-xlsx'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.110; extra == 'fastapi'
Requires-Dist: python-multipart>=0.0.9; extra == 'fastapi'
Description-Content-Type: text/markdown

# PolyAdmin

[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue?logo=python&logoColor=white)](https://www.python.org/)
[![CI](https://github.com/MagicRodri/polyadmin/actions/workflows/publish.yml/badge.svg)](https://github.com/MagicRodri/polyadmin/actions/workflows/publish.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![GitHub Repo stars](https://img.shields.io/github/stars/MagicRodri/polyadmin?style=flat-square)](https://github.com/MagicRodri/polyadmin)

A server-rendered operations workspace for Python applications, with a
FastAPI adapter. See [`docs/`](docs/) for reference documentation, or visit
the [PolyAdmin documentation site](https://magicrodri.github.io/polyadmin-docs/)
for the shared Python and Go guides.

## What you get from declaring a `ModelAdmin`

Point one at your model (fields, `list_display`, search/filter config,
permissions) and you get:

- Full CRUD with search, filter, sort, and pagination, all swapped in
  via HTMX partials — no full-page reloads
- A dashboard of pluggable widgets: metric, stat (value + trend),
  progress bar, bar chart, donut/pie breakdown, table, activity feed,
  timeline, and tabs (several widgets in one card)
- Sidebar grouping — resources sharing a `category` collapse into one
  collapsible accordion section, e.g. everything CRUD-shaped under
  one "Directory" group
- Custom admin pages (`admin.route`) for functionality that isn't
  resource CRUD — reports, wizards, internal tools — rendered inside
  the same layout, auth, and sidebar grouping as everything else
- Relation fields rendered as links, or as a searchable shadcn/ui
  Command-style autocomplete backed by a server-side `/lookup` route
  (never dumps the target model's full queryset into the page)
- Image fields with upload handling and preview rendering
- Inline related records (`StackedInline`/`TabularInline`) — a
  parent's create/detail/edit pages can show and manage a child's
  records that point back at it, directly within the parent workflow
- Record and bulk Actions, with a shadcn/ui Dialog confirmation step for
  destructive ones
- Delete previews: the confirmation page says what else a delete takes
  with it, and protected records block it
- List niceties: a date drill-down, restricted sorting, chosen link
  columns, and filters that survive the trip to a record and back
- Authentication/authorization hooks gating every route *and* every
  control the templates render
- CSV and XLSX export
- Internationalisation: per-request locale, language switcher, French
  and Russian
- Built-in favicon support for the admin shell
- Toast notifications for every create/update/delete/action
- Per-resource (and per-widget) template overrides, so an application
  can replace one view's markup without forking the framework

Styling is [shadcn/ui](https://ui.shadcn.com), hand-ported to
Alpine.js + Tailwind — its CSS-variable token system and component
markup, without React or Radix. That gives the admin **dark mode and
themability**: every color resolves through a CSS variable, so restyling
the whole thing is a change to one template. The layout is mobile-first
with a collapsible sidebar (a Sheet below `md`), a right-hand filter panel
for focused workflows on wider screens, and breadcrumbs as the page
title. Tailwind/Alpine/HTMX are all CDN-loaded — no frontend build step.
See [`docs/components.md`](docs/components.md).

## Quickstart

The first beta release is available on PyPI:

```bash
pip install "polyadmin[fastapi]==0.1.0b3"
```

For the latest unreleased code, install from Git:

```bash
pip install "polyadmin[fastapi] @ git+https://github.com/MagicRodri/polyadmin.git"
```

Declare a `ModelAdmin` against your own storage and mount it on a
FastAPI app:

```python
from dataclasses import dataclass

from fastapi import FastAPI
from polyadmin import BooleanField, EmailField, ModelAdmin
from polyadmin.core.admin import Admin
from polyadmin.fastapi.router import create_router


@dataclass
class User:
    id: int
    email: str
    is_active: bool = True


_users: list[User] = []


class UserAdmin(ModelAdmin):
    model = User
    list_display = ("id", "email", "is_active")
    form_fields = ("email", "is_active")
    search_fields = ("email",)
    fields = (
        EmailField("email", required=True),
        BooleanField("is_active", default=True),
    )

    def get_queryset(self):
        return _users

    def get_object(self, pk):
        return next((u for u in _users if u.id == int(pk)), None)

    def create(self, data):
        user = User(id=len(_users) + 1, **data)
        _users.append(user)
        return user

    def update(self, obj, data):
        obj.email = data["email"]
        obj.is_active = data["is_active"]
        return obj

    def delete(self, obj):
        _users.remove(obj)


admin = Admin(model_admins=[UserAdmin()])

app = FastAPI()
app.include_router(create_router(admin, base_path="/admin"), prefix="/admin")
```

```bash
uv run uvicorn main:app --reload
# open http://127.0.0.1:8000/admin
```

That's a full CRUD admin for `User` — search, sort, create, edit,
delete, CSV/XLSX export, all with zero routes or templates of your
own. With no `authenticator`/`authorizer` set, every request is
allowed by default (fine for exploring locally, not for anything
real) — see [`docs/authentication.md`](docs/authentication.md)
and [`docs/permissions.md`](docs/permissions.md) before
deploying. For everything else a `ModelAdmin` supports (relations,
filters, actions, a dashboard, exports, delete previews), see
[`docs/model-admin.md`](docs/model-admin.md); for the UI components and
theming, [`docs/components.md`](docs/components.md); and for the rest,
[`docs/`](docs/).

## Languages

French and Russian are **on by default** next to English: a visitor
whose browser asks for either gets the admin's own text in it, and the
language switcher appears in the header. Their catalogs are **drafts,
awaiting review by native speakers** — corrections are welcome. To keep
an admin English-only, restrict the supported set:

```python
admin = Admin(model_admins=[...], locales=["en"])
```

See [`docs/i18n.md`](docs/i18n.md) for the rest: translating your own
strings, the switcher, and how a request's language is chosen.

## Upgrading: CSRF protection

Every mutating route now requires a CSRF token, on by default. The
framework's own pages carry it automatically; a **custom `AdminPage` that
renders its own `<form>`** must add the hidden field, or its posts will be
rejected with `403`:

```html
{% from "admin/components/csrf-field.html" import csrf_field %}
{{ csrf_field(csrf_token) }}
```

Forms that only submit via `hx-post` need no change. Every admin response
also sends `X-Frame-Options: DENY`. See
[`docs/authentication.md`](docs/authentication.md#csrf-protection) for the
cookie/header/field names, the proxy caveat (`X-Forwarded-Proto`), and how
to opt out.

## Status

All 9 implementation phases are done:

- **Core** (`polyadmin/core/`): `Admin`, `ModelAdmin`, `Field` (incl. relation
  field types), `Relation`, `Filter`, the list query pipeline (`query.py`),
  `Paginator`, `Authenticator`/`Principal`, `Authorizer`, `Dashboard`/`Widget`,
  `Exporter` (CSV + XLSX).
- **Rendering** (`polyadmin/templating.py`, `polyadmin/templates/`): Jinja2
  templates with framework/app/resource override resolution and
  `TemplateContext` builders. Tailwind/Alpine/HTMX are CDN-loaded
  — no frontend build step required.
- **FastAPI adapter** (`polyadmin/fastapi/`): `create_router` mounts full CRUD +
  list/detail/create/edit/delete + relation lookup + CSV/XLSX export routes,
  with HTMX partial-swap for list search/filter/sort/pagination and for
  forms, flash messages surviving a redirect, delete previews (see what a
  delete takes with it; protected records block it), and auth/permission
  enforcement on every route (also gates which controls the templates show).

See [`examples/fastapi`](examples/fastapi) for a full runnable
reference app exercising all of this.

## Development

```bash
uv sync
uv run pytest   # 205 tests
```
