django-unfold-farsi
Drop-in RTL / Persian support and sane defaults for the django-unfold admin.
django-unfold-farsi turns the Unfold admin into a polished right-to-left,
Persian-first experience with a single settings helper — no template forks, no
manual CSS patching. It bundles the Vazirmatn
font, a set of surgical dir="rtl" layout overrides, a translated login/logout
page, and language-forcing middleware. Everything is i18n-driven: RTL styling
activates only while the active language is bidirectional (fa, ar, he, …).
Switch the admin to English and you get Unfold's stock LTR look untouched.
Table of contents
- Features
- Requirements
- Installation
- Quick start
- Configuration
- How it works
- API reference
- System checks
- What's shipped
- Customization
- Translations
- Compatibility
- FAQ
- Development
- Contributing
- License
- Credits
Features
- One-call setup —
apply_unfold_farsi_defaults()merges STYLES, SCRIPTS and a default color palette into your existingUNFOLDdict. Your keys always win. - True RTL layout — every override is scoped to
html[dir="rtl"], covering the sidebar, dashboard, changelist, forms, inlines, buttons, breadcrumbs, tabs, date/select/select2 widgets and the bulk-actions bar. - Self-hosted Vazirmatn font — no external CDN; ships subsetted
woff2files (arabic / latin / latin-ext) and applies them in RTL only. - Persian-first, still switchable — middleware makes Persian the default
regardless of the browser's
Accept-Language, while an explicit language choice from the switcher is always honoured. - Translated auth screens — Persianized login and logout templates plus a
bundled
famessage catalog for Unfold's own UI strings. - Guardrails — Django system checks warn you when the app or middleware is wired in the wrong order.
Requirements
| Dependency | Supported versions |
|---|---|
| Python | 3.10 – 3.14 |
| Django | 4.2 LTS – 6.1 |
| django-unfold | 0.20+ |
Installation
pip install django-unfold-farsi
Using uv:
uv add django-unfold-farsi
From a local checkout (editable):
uv add --editable ../django-unfold-farsi
django and django-unfold are pulled in automatically as dependencies.
Quick start
The minimum to go from a stock Unfold admin to RTL/Persian:
# settings.py
from django.templatetags.static import static
from unfold_farsi.settings import apply_unfold_farsi_defaults
INSTALLED_APPS = [
"unfold_farsi", # before unfold
"unfold",
"django.contrib.admin",
# ...
]
MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware",
"unfold_farsi.middleware.PersianDefaultLanguageMiddleware", # before LocaleMiddleware
"django.middleware.locale.LocaleMiddleware",
# ...
]
LANGUAGE_CODE = "fa"
USE_I18N = True
UNFOLD = apply_unfold_farsi_defaults(
{"SITE_TITLE": "پنل مدیریت", "SITE_HEADER": "سایت من"},
static=static,
)
python manage.py collectstatic --noinput
python manage.py check # confirms the wiring (see System checks)
That's it — open /admin/ and you're in a Persian, right-to-left Unfold.
Configuration
1. INSTALLED_APPS — order matters
Place unfold_farsi before unfold so its templates and static assets take
precedence over Unfold's originals.
INSTALLED_APPS = [
"unfold_farsi", # must precede unfold so its template/static overrides win
"unfold",
"unfold.contrib.filters",
"unfold.contrib.forms",
"unfold.contrib.inlines",
"django.contrib.admin",
# ...your apps
]
2. MIDDLEWARE — bracket LocaleMiddleware
MIDDLEWARE = [
"django.contrib.sessions.middleware.SessionMiddleware",
"unfold_farsi.middleware.PersianDefaultLanguageMiddleware", # before LocaleMiddleware
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
# ...
]
PersianDefaultLanguageMiddleware strips the incoming Accept-Language header
so Django's language detection falls through to LANGUAGE_CODE (fa) instead of
following the browser. An explicit choice from the language switcher (the
django_language cookie / session) still wins, so users can switch to English at
will. It must run before LocaleMiddleware.
3. UNFOLD — apply the defaults
Wrap your existing UNFOLD dict with the helper:
from django.templatetags.static import static
from unfold_farsi.settings import apply_unfold_farsi_defaults
UNFOLD = apply_unfold_farsi_defaults(
{
"SITE_TITLE": "پنل مدیریت",
"SITE_HEADER": "سایت من",
# ...your project-specific SIDEBAR, SITE_ICON, COLORS, etc.
},
static=static,
)
The helper is non-destructive:
- Appends this package's
STYLESandSCRIPTSafter any you already declared. - Fills in a purple
COLORSpalette only if you didn't set one. - Sets
SHOW_LANGUAGES = True(the language switcher) unless you override it.
Your own keys always take priority. static must be
django.templatetags.static.static — Unfold requires each asset entry to be a
lambda request: ... callable, which the helper builds for you.
Note —
SIDEBAR,SITE_TITLE,SITE_ICONand friends are intentionally not shipped; they're project-specific. Set them yourself in the dict you pass in.
4. Language switching (optional)
To let users switch languages, declare them and wire Django's set_language
view:
# settings.py
LANGUAGE_CODE = "fa"
LANGUAGES = [("fa", "فارسی"), ("en", "English")]
USE_I18N = True
# urls.py
from django.urls import include, path
urlpatterns = [
path("i18n/", include("django.conf.urls.i18n")),
# ...
]
Switch the admin to English and the RTL layout, Vazirmatn font and Persian copy all deactivate automatically — you get a clean LTR English admin on Unfold's default font.
5. Collect static
python manage.py collectstatic --noinput
How it works
i18n-driven, not settings-driven. Nothing keys off a "Persian mode" flag.
The overrides target html[dir="rtl"], and Django sets dir="rtl" on the
<html> element whenever the active language is bidirectional. So the same build
serves a right-to-left Persian admin and a left-to-right English one depending
only on the active language.
Three layers:
- CSS (
unfold_custom.css) — logical mirroring of Unfold's physicalml-*/mr-*utilities, plus targeted fixes for widgets Unfold pins to a physical edge (date/time shortcut, select & select2 chevrons, timezone badge, collapsible/summary chevrons, accent bar, breadcrumb separator). - JavaScript (
unfold_custom.js) — the few fixes CSS can't express: mirroring the inline offset Unfold sets on the bulk-actions bar from JS, and seeding Alpine'schangeListWidthwhen itsResizeObserverreports0. Guarded bydocument.documentElement.dir !== 'rtl'— a no-op in LTR. - Middleware + templates + locale — Persian-default language selection and translated auth screens.
API reference
apply_unfold_farsi_defaults(unfold=None, *, static)
Merge this package's RTL/Persian defaults into an UNFOLD settings dict.
(Also aliased as apply_unfold_rtl_defaults for backwards compatibility).
| Parameter | Type | Description |
|---|---|---|
unfold |
dict | None |
Your existing UNFOLD dict. Copied, not mutated. |
static |
Callable[[str], str] |
Must be django.templatetags.static.static. |
Returns a new dict with STYLES/SCRIPTS appended, COLORS defaulted, and
SHOW_LANGUAGES enabled — caller keys preserved.
PersianDefaultLanguageMiddleware
unfold_farsi.middleware.PersianDefaultLanguageMiddleware — drops the request's
Accept-Language header so language detection falls back to LANGUAGE_CODE.
Must be listed before django.middleware.locale.LocaleMiddleware.
System checks
Run python manage.py check. The app registers checks that surface common
wiring mistakes:
| ID | Warning |
|---|---|
unfold_farsi.W001 |
unfold_farsi is listed after unfold in INSTALLED_APPS. |
unfold_farsi.W002 |
The middleware is not installed at all. |
unfold_farsi.W003 |
The middleware runs after LocaleMiddleware. |
What's shipped
| Path | Purpose |
|---|---|
static/unfold_farsi/css/fonts.css |
Self-hosted Vazirmatn (@font-face), RTL only |
static/unfold_farsi/css/unfold_custom.css |
html[dir="rtl"] layout/typography overrides |
static/unfold_farsi/js/unfold_custom.js |
Runtime RTL fixups (RTL only) |
static/unfold_farsi/fonts/vazirmatn/*.woff2 |
Subsetted font files |
templates/admin/login.html |
Translatable Unfold login page |
templates/registration/logged_out.html |
Translatable Unfold logout page |
locale/fa/LC_MESSAGES/ |
Persian catalog for the strings above + Unfold UI |
unfold_farsi.middleware.PersianDefaultLanguageMiddleware |
Persian-default, switchable |
unfold_farsi.settings.apply_unfold_farsi_defaults |
The wiring helper |
Customization
- Colors — pass your own
COLORSin theUNFOLDdict and the helper leaves it alone. The bundled default is a purpleprimaryramp. - Extra CSS/JS — declare your own
STYLES/SCRIPTSin the dict; the package appends its assets after yours, so your rules can override. - A different font — override the
font-familyunderhtml[dir="rtl"]in a stylesheet loaded after this package's.
Translations
The bundled fa catalog covers the auth templates plus Unfold's own UI strings
(Unfold ships no locale of its own). If you edit the translatable strings in the
templates, refresh the catalog:
django-admin makemessages -l fa
django-admin compilemessages # requires GNU gettext
The package also registers its locale/ directory on LOCALE_PATHS at startup
so the admin's jsi18n JavaScript catalog picks up the djangojs strings.
Compatibility
Any RTL/bidirectional language works — the overrides key off dir="rtl", which
Django sets for fa, ar, he, ur, etc. Persian is only the default; the
CSS itself is language-agnostic. The Vazirmatn font is optimised for Persian and
Arabic script.
FAQ
Does this affect the English admin? No. Every override is scoped to
html[dir="rtl"]; in LTR you get stock Unfold.
Do I need to set a Persian mode flag anywhere? No. It follows the active
language via dir="rtl".
Can users still use English? Yes — wire the language switcher (step 4). The middleware only sets the default; an explicit choice always wins.
The bulk-actions "Run" button is clipped / the bar collapses. Make sure
collectstatic ran and unfold_custom.js is being served; those are the exact
issues it fixes.
Development
git clone <your-fork-url>
cd django-unfold-farsi
uv sync # or: pip install -e .
# Run the bundled demo project
cd demo
python manage.py migrate
python manage.py runserver
The demo/ project is a minimal Unfold admin wired exactly as the docs
describe — use it to see changes live.
Contributing
Issues and pull requests are welcome. Please keep CSS overrides scoped to
html[dir="rtl"], and run python manage.py check in the demo before
submitting.
License
Released under the MIT License.
Credits
- django-unfold — the admin theme this builds on.
- Vazirmatn by Saber Rastikerdar — the bundled Persian font.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_unfold_farsi-0.1.0.tar.gz.
File metadata
- Download URL: django_unfold_farsi-0.1.0.tar.gz
- Upload date:
- Size: 128.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c276479ead1e508544788a832e00ffb2018df4e7bb388941bf1f73cc904946b
|
|
| MD5 |
6132745c977f2e7ed6bb051f7776a882
|
|
| BLAKE2b-256 |
c8a03364a60b575385939bf907801e32c3f3025aa3dcf76ced901401832c408e
|
File details
Details for the file django_unfold_farsi-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_unfold_farsi-0.1.0-py3-none-any.whl
- Upload date:
- Size: 133.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd193963056c95699c91c45a9e6abd31559621074c331b0658f1e4fc58ff0e11
|
|
| MD5 |
4a843e865499e318de7517cb3490b04a
|
|
| BLAKE2b-256 |
fbc424500be8a5dc05f66496d23394133c5748b8ad431a3a1a757a4f2bbe8316
|