Skip to content

Settings

All settings live in config/settings.py. Buraq reads them at startup via the settings_module argument passed to Buraq(settings_module="config.settings").

config/settings.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY
SECRET_KEY = "change-me-in-production"
DEBUG = True
ALLOWED_HOSTS = ["*"]
# APPS
INSTALLED_APPS = [
"buraq.contrib.auth",
"posts",
]
# DATABASE
DATABASE_URL = "sqlite+aiosqlite:///./db.sqlite3"
# TEMPLATES
TEMPLATES_DIR = str(BASE_DIR / "templates") # one path, or a list of them
APP_DIRS = True # also search each installed app's templates/
# Anything Jinja's Environment accepts. "undefined" and "extensions" take
# dotted paths so this file never has to import jinja2; everything else is
# passed straight through.
TEMPLATE_OPTIONS = {
"undefined": "jinja2.StrictUndefined", # a typo raises instead of rendering ""
"trim_blocks": True,
"extensions": ["jinja2.ext.loopcontrols"], # enables {% break %} / {% continue %}
}
# STATIC FILES
SERVE_STATIC = True # False for an API, or when a
# web server in front serves files
STATIC_URL = "/static/"
STATIC_ROOT = str(BASE_DIR / "staticfiles") # destination for collectstatic
STATICFILES_DIRS = [str(BASE_DIR / "static")] # source directories
# Storage backend — ManifestStaticFilesStorage adds content-hashed filenames
STATICFILES_STORAGE = "buraq.contrib.staticfiles.storage.StaticFilesStorage"
# MEDIA
MEDIA_DIR = str(BASE_DIR / "media")
MEDIA_URL = "/media/"
# SQLite (development)
DATABASE_URL = "sqlite+aiosqlite:///./db.sqlite3"
# PostgreSQL (production)
DATABASE_URL = "postgresql+asyncpg://user:password@localhost:5432/mydb"
# MySQL
DATABASE_URL = "mysql+aiomysql://user:password@localhost:3306/mydb"

The driver is named in the scheme — +aiosqlite, +asyncpg, +aiomysql — and it has to be an async one. Leaving it out, or naming a blocking driver, is refused at startup:

ImproperlyConfigured: DATABASE_URL is 'postgresql', which selects a blocking
driver. Buraq is async throughout, so the driver has to be one that can be
awaited.
Use: postgresql+asyncpg://...
Install it with: pip install buraq[postgres]

Left to SQLAlchemy this surfaces as ModuleNotFoundError: No module named 'psycopg2', which reads like a missing dependency and is not — installing psycopg2 cannot help, because it cannot be awaited.

DATABASE_URL is one connection. To use several, name them:

config/settings.py
DATABASES = {
"default": "postgresql+asyncpg://user:pass@primary/db",
"replica": "postgresql+asyncpg://user:pass@replica/db",
}
DATABASE_READ_REPLICAS = ["replica"]

default is required — every query that does not name a database uses it. DATABASE_READ_REPLICAS lists the aliases reads may be sent to, in rotation. Writes always go to default, and so do reads inside atomic().

Setting DATABASES replaces DATABASE_URL; a project needs one or the other, not both.

See Reading from a replica for what routes where, and using().

ROOT_URLCONF = "config.urls" # dotted path to the URLconf module
APPEND_SLASH = True # redirect /posts to /posts/ when it matches
PREPEND_WWW = False # redirect example.com to www.example.com
NUMBER_GROUPING = 3 # digits per group
DECIMAL_SEPARATOR = "."
THOUSAND_SEPARATOR = ","

Log every SQL statement the engine emits. Off by default; turn it on while debugging a query, not in normal development, since it prints during management commands too.

DATABASE_ECHO = True
# In-memory (default, single-process only)
CACHE_BACKEND = "buraq.contrib.cache.backends.memory.MemoryCacheBackend"
# Redis (recommended for production)
CACHE_BACKEND = "buraq.contrib.cache.backends.redis.RedisCacheBackend"
CACHE_REDIS_URL = "redis://localhost:6379/0"
# Memcached
CACHE_BACKEND = "buraq.contrib.cache.backends.memcached.MemcachedCacheBackend"
CACHE_MEMCACHED_URL = "memcached://localhost:11211"
# File
CACHE_BACKEND = "buraq.contrib.cache.backends.file.FileCacheBackend"
CACHE_FILE_PATH = "/tmp/buraq_cache"
# Shared options
CACHE_KEY_PREFIX = "myapp:"
CACHE_DEFAULT_TIMEOUT = 300 # seconds
EMAIL_BACKEND = "buraq.contrib.email.backends.smtp.SMTPEmailBackend"
EMAIL_HOST = "smtp.gmail.com"
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = "you@gmail.com"
EMAIL_HOST_PASSWORD = "your-app-password"
DEFAULT_FROM_EMAIL = "you@gmail.com"
# During development — write emails to disk instead of sending
EMAIL_BACKEND = "buraq.contrib.email.backends.file.FileEmailBackend"
EMAIL_FILE_PATH = "./sent_emails"
# Named email backends — select with send_mail(..., using="transactional")
MAILERS = {
"transactional": {
"BACKEND": "buraq.contrib.email.backends.smtp.SMTPEmailBackend",
"HOST": "smtp.sendgrid.net",
"PORT": 587,
"HOST_USER": "apikey",
"HOST_PASSWORD": "SG.xxx",
"USE_TLS": True,
},
"bulk": {
"BACKEND": "buraq.contrib.email.backends.smtp.SMTPEmailBackend",
"HOST": "bulk.mailrelay.com",
"PORT": 587,
"HOST_USER": "bulk@example.com",
"HOST_PASSWORD": "secret",
"USE_TLS": True,
},
}

Configured via buraq.middleware.SecurityMiddleware:

# HTTPS redirect
SECURE_SSL_REDIRECT = True # redirect all HTTP → HTTPS (default: False)
# HSTS
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Other headers (all True/set by default)
SECURE_CONTENT_TYPE_NOSNIFF = True # X-Content-Type-Options: nosniff
SECURE_REFERRER_POLICY = "same-origin" # Referrer-Policy
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin" # COOP
X_FRAME_OPTIONS = "SAMEORIGIN" # X-Frame-Options
# Permissions-Policy (empty by default — add what you need)
SECURE_PERMISSIONS_POLICY = {
"geolocation": "()",
"microphone": "()",
"camera": "()",
}

See Security Middleware for setup instructions.

CORS_ORIGINS = ["https://myfrontend.com"]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
CORS_ALLOW_HEADERS = ["*"]
SECRET_KEY = "your-jwt-secret-key"
JWT_ALGORITHM = "HS256"
JWT_EXPIRY_MINUTES = 60
# Custom user model — dotted path to your User model class
# Default: "buraq.contrib.auth.models.User"
AUTH_USER_MODEL = "myapp.models.MyUser"
# How long (in seconds) password-reset links remain valid
# Default: 259200 (3 days)
PASSWORD_RESET_TIMEOUT = 259200

Control which password-strength rules are enforced on registration and password-change:

AUTH_PASSWORD_VALIDATORS = [
# Minimum 8 characters (default)
{"NAME": "buraq.contrib.auth.password_validation.MinimumLengthValidator"},
# Custom minimum length
{"NAME": "buraq.contrib.auth.password_validation.MinimumLengthValidator",
"OPTIONS": {"min_length": 12}},
# Reject common passwords (e.g. "password", "123456")
{"NAME": "buraq.contrib.auth.password_validation.CommonPasswordValidator"},
# Reject passwords that are entirely numeric
{"NAME": "buraq.contrib.auth.password_validation.NumericPasswordValidator"},
# Reject passwords too similar to username / email
{"NAME": "buraq.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
# Guard against bcrypt DoS — reject very long passwords
{"NAME": "buraq.contrib.auth.password_validation.MaximumLengthValidator",
"OPTIONS": {"max_length": 4096}},
]

See Password Validation for usage details.

SESSION_COOKIE_NAME = "buraq_session"
SESSION_COOKIE_MAX_AGE = 1209600 # 2 weeks in seconds
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "lax"
TEMPLATE_CONTEXT_PROCESSORS = [
"buraq.template.context_processors.request", # injects request
"buraq.template.context_processors.auth", # injects user
"buraq.template.context_processors.debug", # injects DEBUG flag
"buraq.template.context_processors.i18n", # injects LANGUAGE_CODE
"myapp.context_processors.site_settings", # custom processor
]

See Context Processors for writing custom processors.

USE_TZ = True # store and return timezone-aware datetimes (default: True)
TIME_ZONE = "UTC" # default timezone — any IANA name, e.g. "America/New_York"

Buraq has far more settings than a project names — anything absent keeps its default, which is why a scaffolded config/settings.py is short. To see the full list with the values actually in force:

Terminal window
buraq diffsettings --all # every setting; ### marks the ones you changed
buraq diffsettings # only what differs from the defaults

The application and the CLI both find and apply your settings module on their own. A standalone script — a cron job, a data import, a migration run — runs in a process where nothing has, so it can do the same explicitly:

from buraq.conf import load_settings_module, settings
load_settings_module() # or load_settings_module("config.prod_settings")
print(settings.DATABASE_URL)

With no argument it uses BURAQ_SETTINGS_MODULE if set, then looks for config/settings.py, ./settings.py, and finally a single top-level package containing settings.py. discover_settings_module() performs that search alone and returns the module name, or None when the layout is ambiguous.

Most scripts want buraq.apps.configure() instead, which does this and imports your models.

All settings have defaults. You only need to specify what you want to override. See buraq/conf/defaults.py for the complete list.