django-pg-upsert
This package adds support for the native Postgres upsert (INSERT ... ON CONFLICT) to Django.
Installation
pip install django-pg-upsert
Usage
As a manager
from django.db import models
from django_pg_upsert import PgUpsertManager
class Pet(models.Model):
name = models.CharField(max_length=30, unique=True)
age = models.PositiveIntegerField()
objects = PgUpsertManager()
Pet.objects.insert_conflict(data={"name": "dog", "age": 12})
# The second query does not insert a record and does not raise an error.
Pet.objects.insert_conflict(data={"name": "dog", "age": 20})
This code produces the SQL statement:
[
'INSERT INTO "pets" ("name", "age") VALUES (%s, %s) ON CONFLICT DO NOTHING',
("dog", 12),
]
Explicit constraint name
Pet.objects.insert_conflict(
data={"name": "dog", "age": 12},
constraint="pet_name_uniq"
)
[
'INSERT INTO "pets" ("name", "age") VALUES (%s, %s) ON CONFLICT ON CONSTRAINT pet_name_uniq DO NOTHING',
("dog", 12),
]
Using field names
Pet.objects.insert_conflict(
data={"name": "dog", "age": 12},
fields=["name"]
)
[
'INSERT INTO "pets" ("name", "age") VALUES (%s, %s) ON CONFLICT ("name") DO NOTHING',
("dog", 12),
]
As a standalone function
import django_pg_upsert
pet = Pet(name="dog", age=12)
django_pg_upsert.insert_conflict(pet)
django_pg_upsert.insert_conflict(pet, constraint="pet_name_uniq")
django_pg_upsert.insert_conflict(pet, fields=["name"])
Update
Pet.objects.insert_conflict(
data={"name": "dog", "age": 100},
fields=["name"],
update=["age"]
)
or
django_pg_upsert.insert_conflict(pet, fields=["name"], update=["age"])
[
'INSERT INTO "pets" ("name", "age") VALUES (%s, %s) ON CONFLICT ("name") DO UPDATE SET age = EXCLUDED.age',
("dog", 100),
]
Update with condition
The update_where argument restricts which existing rows are updated
(ON CONFLICT ... DO UPDATE SET ... WHERE ...). It accepts a Q object.
from django.db.models import Q
Pet.objects.insert_conflict(
data={"name": "dog", "age": 100},
fields=["name"],
update=["age"],
update_where=Q(age__lt=100),
)
[
'INSERT INTO "pets" ("name", "age") VALUES (%s, %s) ON CONFLICT ("name") DO UPDATE SET age = EXCLUDED.age WHERE "pets"."age" < %s',
("dog", 100, 100),
]
The argument also accepts a raw SQL string. Use a raw string to reference the
proposed row through EXCLUDED.
django_pg_upsert.insert_conflict(
pet,
fields=["name"],
update=["age"],
update_where="EXCLUDED.age > pets.age",
)
[
'INSERT INTO "pets" ("name", "age") VALUES (%s, %s) ON CONFLICT ("name") DO UPDATE SET age = EXCLUDED.age WHERE EXCLUDED.age > pets.age',
("dog", 100),
]
Do not build a raw string from user input. The package inlines the string into the statement as is.
Batch
To insert many rows in one statement, pass a list. The manager accepts a list of dictionaries. The standalone function accepts a list of model objects. All other arguments work as they do for a single row.
Pet.objects.insert_conflict(
data=[
{"name": "dog", "age": 12},
{"name": "cat", "age": 8},
],
fields=["name"],
update=["age"],
)
or
dog = Pet(name="dog", age=12)
cat = Pet(name="cat", age=8)
django_pg_upsert.insert_conflict([dog, cat], fields=["name"], update=["age"])
Both calls produce one statement with one row set for each object:
[
'INSERT INTO "pets" ("name", "age") VALUES (%s, %s), (%s, %s) ON CONFLICT ("name") DO UPDATE SET age = EXCLUDED.age',
("dog", 12, "cat", 8),
]
Motivation
django-postgres-extra also has a pg upsert method, but that package requires a different DB backend in the Django settings. Sometimes this change is not possible.
django-pg-upsert solves only one problem and depends only on Django. It is not a Swiss army knife.
Metadata
Release files for django-pg-upsert 0.6.1
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_pg_upsert-0.6.1.tar.gz | 5.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_pg_upsert-0.6.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 10.2 kB
Release files / django_pg_upsert-0.6.1.tar.gz
| Download URL | django_pg_upsert-0.6.1.tar.gz |
|---|---|
| Size | 5.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
95b6242d9d789590a74f06c27329ca62f508bcc484e792a82427e03350cae7a0
|
|
BLAKE2b-256 checksum How to use checksums |
baf3848e938bbbc8cbf30a3f40bfae850928fd21677781d72f787dc03ee62f6b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.1 CPython/3.13.1 Darwin/25.5.0
|
Release files / django_pg_upsert-0.6.1-py3-none-any.whl
| Download URL | django_pg_upsert-0.6.1-py3-none-any.whl |
|---|---|
| Size | 5.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0d3381b09346bfd65faac3b34f42b2b11d918633617b1ce5ba0fc3cdf047367e
|
|
BLAKE2b-256 checksum How to use checksums |
ad03506144ee5e04afafc6d3eb3531cc3b6686e0774b9dc87b9d9577195a8418
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.1.1 CPython/3.13.1 Darwin/25.5.0
|