Django SQL Fun
Django SQLFun allows you to define and manage custom SQL functions in code. When you change the function definitions and call makemigrations, it will generate migrations for any functions that have been added, removed, or changed. These function classes can also be used in Django querysets since the SqlFun class inherits from django.db.models.expressions.Func.
Note: I'm still developing this so there may be some rough edges. Breaking changes may happen.
Installation
- Install using your favorite python package manager, eg.
pip install django-sqlfun. - Add
sqlfuntoINSTALLED_APPSin your django settings - Run
manage.py migrate(on a fresh install this is a no-op for sqlfun; on upgrades from ≤0.1.x it removes sqlfun's old bookkeeping table)
Use
- Define a custom function in a module that gets imported on project load (eg.
models.py). See below for example, or thetest_project. - Run
manage.py makemigrations - Run
manage.py migrate
Example
Define a custom function in your models.py:
# models.py
from sqlfun import SqlFun
from django.db.models import IntegerField
class BadSum(SqlFun):
"""Almost returns the sum of two numbers."""
app_label = 'test_project' # [optional] if omitted, sqlfun will atempt to auto-resolve it
sql = """
CREATE OR REPLACE FUNCTION bad_sum(
first integer,
second integer
) RETURNS integer as $$
SELECT first + second + 1;
$$
LANGUAGE sql
stable;
"""
output_field = IntegerField()
Then run manage.py makemigrations and manage.py migrate and you should be good to go. You can use it in SQL: SELECT bad_sum(2, 2), or in a Python queryset like so: MyModel.objects.annotate(foo=BadSum(Value(2), Value(2))).
Notes
- Function definitions must use
CREATE OR REPLACE FUNCTION—makemigrationsrejects plainCREATE FUNCTION, since sqlfun re-executes definitions against databases where the function may already exist makemigrationscreates each changed function inside a rolled-back transaction to read its signature, so the argument and return types must already exist in the database. WithAS $$ ... $$bodies, tables and views the body references do not need to exist yet. SQL-standard bodies (BEGIN ATOMIC ... ENDor a bareRETURN) are always checked when the function is created, so if a function must be generated before the migration that creates its tables has been applied, write its body asAS $$ ... $$- SQL functions are normalized before comparison, so whitespace-only changes do not generate migrations
- Change detection works by replaying sqlfun's operations from your existing migration files — there is no state outside your repo, so fresh clones and CI see exactly what you see
- If you squash or delete migrations that contain sqlfun operations, that state is lost: the next
makemigrationsre-emits a baseline migration re-declaring the affected functions (harmless to apply, but noisy) - the
--dry-run,--name, and--checkoptions ofmakemigrationsare respected.--checkexits with a non-zero status if any sqlfun function changes are missing migrations (in addition to Django's own model-change check), writes nothing, and requires a reachable database — it fails rather than silently passing if sqlfun changes cannot be evaluated. Usemakemigrations --database <alias>to run introspection against a specific database alias.
Upgrading
From ≤0.1.x to 0.2.0 (breaking): sqlfun no longer keeps a bookkeeping
table — a function's history now lives in your migration files as
sqlfun.operations.CreateFunction / DropFunction operations. To upgrade
an existing project:
- Upgrade the package.
- Run
manage.py migrate— this drops sqlfun's old tracking table. - Run
manage.py makemigrationsonce, before editing or deleting any function definitions. Your old migrations contain onlyRunSQLoperations, which the new change detection does not read, so this run emits one baseline migration per app re-declaring every registered function. - Run
manage.py migrate— the baseline applies as a no-opCREATE OR REPLACEagainst your existing functions.
If you deleted a function class before step 3, sqlfun has no record of it:
drop that function manually. If you changed a function's arguments or
return type before step 3, the baseline also drops the old function first.
sqlfun reads that old definition from the database makemigrations runs
against, so reversing the baseline restores it.
Reversing the post-upgrade baseline migration drops the function outright, since the baseline carries no previous definition — even though on an upgraded install the function predates it. Treat the baseline as forward-only.
Development
These instructions assume a recent Ubuntu/Debian environment.
- Clone the repository
- Install uv
- Install the
libpq-devpackage sincepsycopg2depends on it. - Install dependencies with
uv sync(this creates.venvand installs the dev group)
Testing also requires a recent install of docker which is used to spin up a test postgres instance.
Credits
This project is inspired by two great projects: django-pgtrigger and django-pgviews.
Release files for django-sqlfun 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_sqlfun-0.2.0.tar.gz | 18.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_sqlfun-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 40.2 kB
Release files / django_sqlfun-0.2.0.tar.gz
| Download URL | django_sqlfun-0.2.0.tar.gz |
|---|---|
| Size | 18.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e3994c8084e21a543d91758bbb89634725df7f1c902105ccd7045f6ec1ed7a3a
|
|
BLAKE2b-256 checksum How to use checksums |
51c1776d4d6f091a0c888dba480fe119cf8d6abb2209998d318a513ea32e8d9d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / django_sqlfun-0.2.0-py3-none-any.whl
| Download URL | django_sqlfun-0.2.0-py3-none-any.whl |
|---|---|
| Size | 22.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
794b0eaff524e4e4ddbede4cf5401bb7db76f4801dcf538855469351a8a3158d
|
|
BLAKE2b-256 checksum How to use checksums |
532dce878c2fcc4a77ec12812f19cbf7789a417e81d15f930592dd385afefac9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|