Skip to content

Hawk-API/hawkapi-admin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

hawkapi-admin

Auto-generated admin UI for HawkAPI + hawkapi-sqlalchemy models. Drop your model classes in and get list / detail / create / edit / delete views mounted under /admin — no boilerplate, no React, no JSON-schema duplication.

Install

pip install hawkapi-admin

Quickstart

from hawkapi import HawkAPI
from hawkapi_sqlalchemy import Base, TimestampMixin, init_database
from sqlalchemy.orm import Mapped, mapped_column

from hawkapi_admin import Admin, ModelResource, init_admin


class User(Base, TimestampMixin):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
    email: Mapped[str] = mapped_column(unique=True)
    name: Mapped[str] = mapped_column(default="")


app = HawkAPI()
init_database(app, url="postgresql+asyncpg://…/myapp")

admin = init_admin(app, title="My App")
admin.register(User)                            # simplest form

Visit /admin — you get the index page, /admin/user (list), /admin/user/new (create form), /admin/user/{id} (detail), /admin/user/{id}/edit, and POST /admin/user/{id}/delete. All with sane widgets picked from each column's SQLAlchemy type.

Customizing a resource

from hawkapi_admin import ModelResource

admin.register(ModelResource(
    model=User,
    label="Account",
    label_plural="Accounts",
    icon="👤",
    list_display=("id", "email", "created_at"),
    list_search=("email", "name"),
    form_fields=("email", "name"),
    readonly_fields=("created_at",),
    page_size=25,
    can_delete=False,
))
Option Default Effect
name lowercased class name URL slug (/admin/<name>)
label class name Heading on detail / form
label_plural label + "s" Nav label & list-page heading
icon "" Prepended to nav label
list_display every column Columns shown on the list page
list_search () Columns searched by ?q=... (LIKE)
form_fields every non-PK column Columns shown in the form
readonly_fields () Columns rendered but not editable
page_size 50 Rows per list page
can_create / can_update / can_delete True Toggles the corresponding routes off

Widgets

hawkapi-admin picks an input widget per column type, automatically:

  • bool → checkbox
  • int / float<input type="number">
  • date / datetime → matching native input
  • String(length > 500) → textarea
  • Enum<select> with the declared choices
  • everything else → <input type="text">

Authentication

The admin panel is fail-closed. If you do not pass an auth callable to init_admin(...) (or Admin(...)), every admin request is denied with HTTP 401 — the panel no longer silently allows access. A UserWarning is emitted at construction and a logger.warning at attach() to flag the misconfiguration.

You must pass an auth callable to enable access. It is an async function that receives the request and raises HTTPException(401)/HTTPException(403) to reject it (return None to allow). It runs at the top of every admin route. Combine with hawkapi-auth:

from hawkapi import HTTPException
from hawkapi_auth import requires_scopes

from hawkapi_admin import init_admin


async def admin_auth(request):
    # raises 401/403 if the JWT is missing / lacks the scope
    await requires_scopes("admin:read")(request)


admin = init_admin(app, title="My App", auth=admin_auth)
admin.register(User)

Or hand-roll the check:

async def admin_auth(request):
    if request.headers.get("x-admin-token") != "…":
        raise HTTPException(401)


admin = init_admin(app, auth=admin_auth)

Other safeguards

  • Detail views render only detail_fields() — derived from list_display when you configure it — so columns hidden from the list page are not exposed on the detail page either. With no list_display, the detail view falls back to every mapped column.
  • Mass-assignment protection. Field names that look sensitive or authorization-bearing (password, secret, token, key, hash, role, admin, superuser, permission, privilege, staff, scope) are forced read-only unless you explicitly opt them into form_fields. This prevents privilege escalation via the edit form (e.g. setting is_admin or role).
  • CSRF cookie hardening. The hawkapi_admin_csrf cookie is now HttpOnly with a Max-Age (tokens rotate rather than living indefinitely), in addition to Secure and SameSite=Lax.
  • Security headers. Every admin HTML response carries X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: same-origin, and a restrictive Content-Security-Policy.

Theming

The bundled CSS is roughly 60 lines, prefers system colors (light + dark mode), and lives inline in _base.html — copy that template into your own templates/ directory if you want to restyle. Jinja extends "_base.html" keeps working as long as the same blocks (title, content) are defined.

Development

git clone https://github.com/Hawk-API/hawkapi-admin.git
cd hawkapi-admin
uv sync --extra dev
uv run pytest -q
uv run ruff check . && uv run ruff format --check .
uv run pyright src/

License

MIT.

About

Auto-generated admin UI for HawkAPI + SQLAlchemy models — list, detail, create, edit, delete with no boilerplate

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors