Skip to main content

django-pg-upsert

build PyPI version Downloads

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)

Source distribution for django-pg-upsert 0.6.1
File Size Uploaded
django_pg_upsert-0.6.1.tar.gz 5.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-pg-upsert 0.6.1
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

0.6.2

2 release files

This release

0.6.1 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page