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.2

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.2
File Size Uploaded
django_pg_upsert-0.6.2.tar.gz 5.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-pg-upsert 0.6.2
File Interpreter ABI Platform
django_pg_upsert-0.6.2-py3-none-any.whl Python 3 none any Details

Total release size: 10.1 kB

Release files / django_pg_upsert-0.6.2.tar.gz

Download URL django_pg_upsert-0.6.2.tar.gz
Size 5.0 kB
Tags Source
SHA-256 checksum
How to use checksums
4adfa041aaed8d6c40a17f2a5bee95c6682d1c1a08cf51709f087ede38ee5f90
BLAKE2b-256 checksum
How to use checksums
9f897e4ceb2186a170c851718cd5b1ced32b2adfd2cca2c86a48e9ce6bd5f7b1
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.2-py3-none-any.whl

Download URL django_pg_upsert-0.6.2-py3-none-any.whl
Size 5.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b2f330e31d045c10904ede2b2924804cb8fe7a1c7640b5281481353e55c585eb
BLAKE2b-256 checksum
How to use checksums
0a1ccfa533cfa3ee82faee3545cf015983f01a5642331f80530dcc87fed5b001
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

This release

0.6.2 This release

2 release files

0.6.1

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