Skip to content

Migrating from Django

Buraq is intentionally designed to mirror Django’s patterns. If you know Django, most of what you know transfers directly — the main difference is adding async/await to your views and ORM calls.

Terminal window
pip install django
django-admin startproject myproject
cd myproject
python manage.py migrate
python manage.py runserver

Django Buraq
from django.urls import path, include from buraq.urls import path, include
from django.shortcuts import render, redirect from buraq.shortcuts import render, redirect
from django.shortcuts import get_object_or_404 from buraq.shortcuts import get_object_or_404
from django.shortcuts import get_list_or_404 from buraq.shortcuts import get_list_or_404
from django.urls import reverse_lazy from buraq.urls import reverse_lazy
from django.db import models from buraq import models
from django.views.generic import ListView from buraq.views.generic import ListView
from django.views.generic import DetailView from buraq.views.generic import DetailView
from django.views.generic import CreateView from buraq.views.generic import CreateView
from django.views.generic import UpdateView from buraq.views.generic import UpdateView
from django.views.generic import DeleteView from buraq.views.generic import DeleteView
from django import forms from buraq.forms import ModelForm
from django.contrib.auth.decorators import login_required from buraq.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST from buraq.views.decorators.http import require_POST
from django.views.decorators.cache import cache_page from buraq.views.decorators.cache import cache_page
from django.views.decorators.csrf import csrf_exempt from buraq.views.decorators.csrf import csrf_exempt
from django.views.decorators.vary import vary_on_headers from buraq.views.decorators.vary import vary_on_headers
from django.contrib import messages from buraq.contrib.messages import success, error, info

Django Buraq
python manage.py runserver buraq runserver
python manage.py makemigrations buraq makemigrations
python manage.py migrate buraq migrate
python manage.py startapp name buraq startapp name
python manage.py startproject name buraq startproject name
python manage.py createsuperuser buraq createsuperuser
python manage.py collectstatic buraq collectstatic
python manage.py findstatic file buraq findstatic file
python manage.py shell buraq shell
python manage.py check buraq check
python manage.py check --deploy buraq check --deploy
python manage.py dbshell buraq dbshell
python manage.py dumpdata buraq dumpdata
python manage.py loaddata fixture buraq loaddata fixture
python manage.py flush buraq flush
python manage.py changepassword buraq changepassword
python manage.py inspectdb buraq inspectdb
python manage.py diffsettings buraq diffsettings
python manage.py sendtestemail buraq sendtestemail
python manage.py makemessages -l ar buraq makemessages -l ar
python manage.py compilemessages buraq compilemessages
python manage.py sqlmigrate abc123 buraq sqlmigrate abc123
python manage.py squashmigrations buraq squashmigrations
python manage.py optimizemigration buraq optimizemigration
python manage.py sqlflush buraq sqlflush
python manage.py sqlsequencereset buraq sqlsequencereset
python manage.py testserver fixtures/ buraq testserver fixtures/
python manage.py test buraq test
python manage.py showmigrations buraq showmigrations
python manage.py clearsessions buraq clearsessions
python manage.py createcachetable buraq createcachetable
python manage.py remove_stale_contenttypes buraq remove_stale_contenttypes
python manage.py version buraq version
python manage.py worker buraq worker
django-admin buraq (same CLI, no separate admin tool)

Most settings follow the same naming convention. Key differences:

Django settings.py Buraq settings.py
DATABASES = {"default": {...}} DATABASE_URL = "postgresql+asyncpg://...", or DATABASES as a dict of URLs
INSTALLED_APPS INSTALLED_APPS
MIDDLEWARE MIDDLEWARE
TEMPLATES = [{...}] TEMPLATES_DIR, APP_DIRS, TEMPLATE_OPTIONS
STATIC_URL STATIC_URL
MEDIA_URL MEDIA_URL
SECRET_KEY SECRET_KEY
DEBUG DEBUG
ALLOWED_HOSTS ALLOWED_HOSTS

A new Django project lists six apps; a new Buraq project lists one. Nothing is missing — in Django, listing an app is how you switch it on, and in Buraq four of those five are already on.

Django lists it because In Buraq
staticfiles — registers the finders and collectstatic mounted by the application; each app’s static/ is still found through INSTALLED_APPS, and SERVE_STATIC = False turns the serving off
admin — app-directory templates and AppConfig.ready() autodiscovery path("/admin", admin.site.urls) in your urlconf does both
messages — a context processor and middleware plain functions over the session; nothing to register
contenttypes — models and migrations the same, and optional: only generic relations need it
auth — models, migrations, templates the same, which is why it is listed

So INSTALLED_APPS in Buraq means one thing: this app contributes models, migrations, templates, static files, management commands or a ready() hook. Three of Django’s five contribute none of those here, and listing them would change nothing at all.

Two are worth adding when you use them:

INSTALLED_APPS = [
"buraq.contrib.auth",
"buraq.contrib.sessions", # only for SESSION_ENGINE = "...backends.db"
"buraq.contrib.contenttypes", # only for generic relations
]

Your own apps go here for the usual reasons — a listed app’s models are found by makemigrations, its templates/ and static/ are searched, its management/commands/ become buraq <name>, and its AppConfig.ready() runs.


from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey("auth.User", on_delete=models.CASCADE)
class Meta:
ordering = ["-created_at"]

Django Buraq
models.GeneratedField(expression=..., output_field=..., db_persist=True) models.GeneratedField(expression=..., output_field=..., db_persist=True)
models.CompositePrimaryKey("field1", "field2") in Meta.primary_key models.CompositePrimaryKey("field1", "field2") in Meta.primary_key

Django Buraq
Post.objects.all() await Post.objects.all()
Post.objects.filter(published=True) await Post.objects.filter(published=True)
Post.objects.get(pk=pk) await Post.objects.get(pk=pk)
Post.objects.create(title="Hello") await Post.objects.create(title="Hello")
post.save() await post.save()
post.delete() await post.delete()
Post.objects.order_by("-created_at") await Post.objects.order_by("-created_at")
Post.objects.filter(...).count() await Post.objects.filter(...).count()
Post.objects.select_related("author") await Post.objects.select_related("author")

The only difference: every ORM call needs await.


Django Buraq
from django.db.models import AnyValue from buraq.orm.aggregates import AnyValue

from django.urls import path, include
from posts import views
urlpatterns = [
path("posts/", views.PostListView.as_view(), name="post_list"),
path("posts/new/", views.PostCreateView.as_view(), name="post_create"),
path("posts/<int:pk>/", views.PostDetailView.as_view(), name="post_detail"),
path("api/", include("myapp.api_urls")),
]

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
@login_required
def post_create(request):
form = PostForm(request.POST or None)
if form.is_valid():
form.save()
messages.success(request, "Post created.")
return redirect("/posts")
return await render(request, "posts/form.html", {"form": form})
from django.views.generic import ListView, CreateView
class PostListView(ListView):
model = Post
template_name = "posts/list.html"
paginate_by = 10
class PostCreateView(CreateView):
model = Post
form_class = PostForm
template_name = "posts/form.html"
success_url = "/posts/"

from django import forms
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "slug", "content", "is_published"]

Buraq uses Jinja2 instead of Django’s template engine. The syntax is nearly identical.

Django template Jinja2 (Buraq)
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
{% for item in items %} {% for item in items %}
{% if condition %} {% if condition %}
{% url 'post_list' %} {{ url('post_list') }}
{{ variable }} {{ variable }}
{% include "partial.html" %} {% include "partial.html" %}
{{ variable|upper }} {{ variable|upper }}

Django Buraq
STATIC_URL = "/static/" STATIC_URL = "/static/"
STATIC_ROOT = str(BASE_DIR / "staticfiles") STATIC_ROOT = str(BASE_DIR / "staticfiles")
STATICFILES_DIRS = [str(BASE_DIR / "static")] STATICFILES_DIRS = [str(BASE_DIR / "static")]
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage" STATICFILES_STORAGE = "buraq.contrib.staticfiles.storage.ManifestStaticFilesStorage"
STATICFILES_FINDERS = [...] STATICFILES_FINDERS = [...]
MEDIA_ROOT = str(BASE_DIR / "media") MEDIA_DIR = str(BASE_DIR / "media")
MEDIA_URL = "/media/" MEDIA_URL = "/media/"

Django requires {% load static %} before using {% static %}. Buraq registers the tag automatically — no load needed:

{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}">
Django Buraq
django.contrib.staticfiles.finders.FileSystemFinder buraq.contrib.staticfiles.finders.FileSystemFinder
django.contrib.staticfiles.finders.AppDirectoriesFinder buraq.contrib.staticfiles.finders.AppDirectoriesFinder
Django Buraq
django.contrib.staticfiles.storage.StaticFilesStorage buraq.contrib.staticfiles.storage.StaticFilesStorage
django.contrib.staticfiles.storage.ManifestStaticFilesStorage buraq.contrib.staticfiles.storage.ManifestStaticFilesStorage

from django.contrib import messages
messages.success(request, "Saved successfully.")
messages.error(request, "Something went wrong.")
messages.info(request, "Please verify your email.")

Template usage is identical in both:

{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}

from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
def login_view(request):
user = authenticate(request, username=username, password=password)
if user:
login(request, user)
return redirect("/dashboard")

Django Buraq
django.contrib.auth.backends.ModelBackend buraq.contrib.auth.backends.ModelBackend
django.contrib.auth.backends.AllowAllUsersModelBackend buraq.contrib.auth.backends.AllowAllUsersModelBackend
django.contrib.auth.backends.AllowAllUsersRemoteUserBackend buraq.contrib.auth.backends.AllowAllUsersRemoteUserBackend

Django Buraq
django.middleware.csp.ContentSecurityPolicyMiddleware buraq.middleware.csp.ContentSecurityPolicyMiddleware
CONTENT_SECURITY_POLICY = {...} CONTENT_SECURITY_POLICY = {...}
CONTENT_SECURITY_POLICY_REPORT_ONLY = {...} CONTENT_SECURITY_POLICY_REPORT_ONLY = {...}
CONTENT_SECURITY_POLICY_NONCE_DIRECTIVES CONTENT_SECURITY_POLICY_NONCE_DIRECTIVES
from django.views.decorators.csp import csp_override from buraq.views.decorators.csp import csp_override
from django.views.decorators.csp import csp_report_only_override from buraq.views.decorators.csp import csp_report_only_override
from django.utils.csp import CSP from buraq.utils.csp import CSP

Both frameworks sign data the same way — HMAC-SHA256 keyed with SECRET_KEY:

Django Buraq
from django.core import signing from buraq.utils import signing
signing.dumps(obj) signing.dumps(obj)
signing.loads(token, max_age=3600) signing.loads(token, max_age=3600)
signing.Signer() signing.Signer()
signing.TimestampSigner() signing.TimestampSigner()
BadSignature signing.BadSignature
SignatureExpired signing.SignatureExpired

The API is identical — only the import path differs.


Django Buraq
from django.tasks import background_task from buraq.contrib.tasks import background_task
@background_task @background_task
@background_task(queue="q", priority=5) @background_task(queue="q", priority=5)
await task.aenqueue(...) await task.aenqueue(...)
BaseTaskBackend buraq.contrib.tasks.backends.base.BaseTaskBackend
TaskResult.arefresh() result.arefresh()
TASKS = {"default": {"BACKEND": "..."}} TASKS = {"default": {"BACKEND": "..."}}

Both frameworks use an identical API. The only difference is the import path.


Django Buraq
from django.test import TestCase from buraq.test import TestCase
self.captureOnCommitCallbacks() captureOnCommitCallbacks() context manager from buraq.test
MessagesTestMixin + assertMessages() from buraq.test import MessagesTestMixin
InMemoryStorage from buraq.contrib.staticfiles.storage import InMemoryStorage

  • Every ORM call needs await — this is the biggest change
  • Views must be async def — synchronous views are not supported
  • Databases are URLs, not ENGINE/NAME dicts — DATABASES takes several, and using() and read replicas work; there are no database routers
  • Jinja2 instead of Django’s template engine — syntax is ~95% compatible
  • JWT auth is built-in alongside session auth
  • No syncdb — Alembic handles all migrations via buraq makemigrations
  • buraq CLI replaces python manage.py