Admin Interface#
A Django-style admin interface, auto-generated from your models. Zero config to start, deeply customizable when you need it.
Quick start#
Scaffold a project with admin enabled:
asok create myapp --admin
cd myapp
asok createsuperuser
asok dev
Open http://127.0.0.1:8000/admin and log in.
The --admin flag generates a User model with an is_admin field and registers Admin(app) in wsgi.py.
Adding to an existing project#
If you decided to add the Admin interface after your project was created, run:
asok admin --enable
This command will: 1. Update wsgi.py to register the Admin extension. 2. Create src/models/user.py with the default User model if it doesn't exist. 3. Guide you through the necessary migrations and superuser creation.
Customization#
Admin(app, site_name="codewithmpia", url_prefix="/gestion", favicon="images/logo.svg")
| Param | Default | Description |
|---|---|---|
site_name | "Asok Admin" | Brand shown in sidebar / page title |
url_prefix | "/admin" | URL prefix for all admin routes |
favicon | None | Path to your brand logo / tab icon |
Smart Asset Resolution#
The favicon path resolves intelligently based on its location: - Internal Assets: If you use a simple filename like logo.svg, the admin uses its built-in internal version. - Project Assets: If you provide a directory path (e.g. images/logo.svg or uploads/brand.png), the admin resolves it relative to your project's src/partials/ folder.
This logo is automatically applied as the browser favicon, the sidebar brand icon, and the login page header.
Authentication#
Admin uses request.login() and requires the user to have is_admin = True. Add the field to your User model:
from asok import Model, Field
class User(Model):
email = Field.String()
password = Field.Password()
is_admin = Field.Boolean(default=False)
Create the first superuser:
asok createsuperuser
# or non-interactive:
asok createsuperuser --email=admin@example.com --password=secret
Roles & permissions#
Asok Admin ships with a role-based permission system. Two models are auto-provisioned when you call Admin(app):
- User — the auth model (
email,password,name,is_admin,created_at) - Role — grants permissions to users (
name,label,permissions,created_at)
Linked by a role_user pivot table (a user can have any number of roles).
Permission format#
Permissions are comma-separated strings following <slug>.<verb>:
posts.view,posts.edit,users.delete,articles.exportposts.*— all verbs onposts*— superuser (bypass all checks)
The available verbs are: view, add, edit, delete, export.
Granting access#
- Log in as a superuser (
is_admin = True) or an existing admin. - Go to Roles → New.
- Give it a
name(e.g.editor) and alabel(e.g.Content Editor). - Tick boxes in the Permissions matrix (models × verbs). The "all" column ticks a whole row; the Superuser checkbox at the top grants
*. - Save.
- Go to Users, edit the target user, tick the role in the Roles section. Save.
The user now sees only the models they have view permission on. Each action (add / edit / delete / export) is gated by its corresponding verb.
Behaviour summary#
| User state | Access |
|---|---|
is_admin = True | Full access, bypass all permission checks |
| Has roles with perms | Filtered access based on permissions |
No roles, is_admin = False | Redirected to /admin/login |
user.can(perm)#
The User.can() helper is also available in your own code:
if request.user.can("posts.edit"):
...
Supports exact match, <slug>.* wildcards, and * superuser.
Self-protection#
The admin interface prevents foot-gunning:
- You cannot delete your own account (via row action or bulk delete).
- You cannot demote yourself (your own
is_adminfield is hidden on self-edit). - You cannot change your own role assignments.
createsuperuser#
asok createsuperuser
Creates a user with is_admin = True and an admin role (with * permissions) if the Role model exists. Attaches the role to the user automatically.
Per-model configuration#
Customize how a model appears in admin by adding a nested Admin class:
from asok import Model, Field, ModelAdmin
class Post(Model):
title = Field.String()
body = Field.Text()
slug = Field.Slug(source='title')
published = Field.Boolean(default=False)
author_id = Field.ForeignKey('users')
content = Field.Text(wysiwyg=True) # Rich Text Editor
author = BelongsTo('users')
tags = BelongsToMany('tags')
class Admin(ModelAdmin):
label = "Posts"
list_display = ['title', 'author', 'published', 'created_at']
search_fields = ['title', 'body']
list_filter = ['published', 'author_id']
readonly_fields = ['created_at']
form_exclude = ['slug'] # Hide from create/edit forms
fieldsets = [
('Content', ['title', 'slug', 'body']),
('Publishing', ['published', 'author_id']),
]
per_page = 25
can_delete = True
actions = ['publish_selected']
@classmethod
def publish_selected(cls, ids):
cls.where('id', 'in', ids).update(published=True)
[!TIP] Use
asok.ModelAdminas a base class for your innerAdminclass to get full IDE autocompletion for all configuration options.
from asok import Model, Field, ModelAdmin
class MyModel(Model):
# ...
class Admin(ModelAdmin):
list_display = ["id", "name"]
# Your IDE will now suggest all available options!
Field visibility control#
Control which fields appear in forms:
class Category(Model):
name = Field.String()
slug = Field.Slug(populate_from='name')
created_at = Field.CreatedAt()
class Admin(ModelAdmin):
form_exclude = ['slug', 'created_at'] # Completely hidden from forms
readonly_fields = ['created_at'] # Shown but not editable
Difference: - form_exclude — Field is completely hidden from create/edit forms - readonly_fields — Field is shown but disabled (useful for auto-generated fields)
Use form_exclude for fields that auto-populate (like slugs) or timestamps. Use readonly_fields when you want users to see the value but not change it.
All options#
| Option | Type | Description |
|---|---|---|
hidden | bool | Hide this model from admin (hidden = True) |
slug | str | URL slug (defaults to table name) |
label | str | Display name in sidebar |
group | str | Group models together in the sidebar |
list_display | list | Columns in the list view |
search_fields | list | Fields searched by the search box (multi-field OR-LIKE) |
list_filter | list | Fields exposed as filters in the sidebar |
readonly_fields | list | Fields displayed but not editable |
form_exclude | list | Fields to exclude completely from create/edit forms |
fieldsets | list | Group fields into labeled cards: [(label, [fields]), ...] |
per_page | int | Pagination size (default 20) |
inlines | list | Related models to display below the form (['comments', ...]) |
can_add | bool | Allow creation (default True) |
can_edit | bool | Allow editing (default True) |
can_delete | bool | Allow deletion (default True) |
actions | list | Names of @classmethods exposed as bulk actions |
Display helper#
Foreign keys and relations are displayed using the related model's __str__() method, falling back to name, title, label, email, username, slug, then #<id>.
Define __str__ to control the display:
class User(Model):
def __str__(self):
return f"{self.first_name} {self.last_name}"
Features#
- Search — multi-field LIKE across
search_fields - Filters — sidebar filter panel for
list_filterfields - Sort — click any column header
- Pagination — preserves search, filters, and sort
- Bulk actions — select rows and delete or run a custom action
- Soft delete & trash — restore or permanently delete from the trash view
- CSV export — exports the current filtered/sorted view
- BelongsToMany editor — checkbox grid for many-to-many relations
- Rich Text Editor (WYSIWYG) — supported on
Textfields withwysiwyg=True - Inlines — view related
HasManyrows below the parent form, with edit links - Date / datetime / file pickers — auto-detected from field types
- Image preview — preview uploaded images on the edit form
- Breadcrumbs — context-aware navigation
- Save variants — Save / Save and continue / Save and add another
- Dark mode — toggle in the topbar, persisted in localStorage
- Empty values —
—shown for null/empty cells - Boolean badges — colored Yes/No badges for boolean columns
- Error pages — beautiful 403, 404, and 500 error pages with admin design
Error Pages#
The admin interface includes beautifully designed error pages that match the admin theme:
Built-in Error Pages#
- 403 Forbidden — Access denied with login option
- 404 Not Found — Page or item not found
- 500 Internal Server Error — Server error with retry option
All error pages: - Match the admin theme (light/dark mode support) - Display contextual icons and messages - Provide relevant action buttons (Go Back, Dashboard, Retry, Login) - Are fully internationalized (English, French, Spanish) - Show helpful messages without exposing sensitive information
Custom Error Messages#
To customize error messages in your admin extensions, use the _render_error() method:
from asok.admin import Admin
class MyAdmin(Admin):
def custom_check(self, request):
if not some_condition:
return self._render_error(
request,
403,
self.t(request, "Custom Access Denied"),
self.t(request, "You need special permission for this action."),
)
The error page template automatically handles: - Error code badge display - Appropriate icons per error type - Contextual action buttons - Multi-language support
Templates and static files#
Templates and CSS/JS are bundled inside the asok package. To override a template, drop a file with the same name into src/templates/admin/ (e.g., src/templates/admin/list.html) — your template wins.
The admin CSS is self-contained (no Tailwind required) and supports light/dark themes via CSS variables.
Was this page helpful?