Package: django-pg-returning
django-pg-returningA small library implementing PostgreSQL ability to return rows in DML statements for Django.Link to PostgreSQL docsRequirementsPython Python 3.6+
Previous versions may also work, but are not tested with CIdjango >= 1.8Previous versions may also work, but are not tested with CI.bulk_create_returning method doesn't support .only() and .defer() filters for django before 1.10.psycopg2 or psycopg2-binary
This library is not added to requirements file as it is not used directly (thorugh django cursor only).
Any form of psycopg2 installation can be used.typing for python < 3.5PostgreSQL 9.4+Previous versions may also work, but are not tested with CI.InstallationInstall via pip:pip install django-pg-returningor via setup.py:python setup.py installUsageIntegrationThe easiest way to integrate, is to inherit your model fromUpdateReturningModelinstead ofdjango.db.models.Model.
It already has redeclared Manager, supporting returning operations.fromdjango.dbimportmodelsfromdjango_pg_returningimportUpdateReturningModelclassMyModel(UpdateReturningModel):field=models.IntegerField()If you already have custom manager, you can implementget_queryset()method in it:fromdjango.dbimportmodelsfromdjango_pg_returningimportUpdateReturningQuerySet,UpdateReturningModelclassMyManager(models.Manager):defget_queryset(self):returnUpdateReturningQuerySet(using=self.db,model=self.model)classMyModel(UpdateReturningModel):objects=MyManager()field=models.IntegerField()And if you have custom manager you can use a mixin:fromdjango.dbimportmodelsfromdjango_pg_returningimportUpdateReturningMixin,UpdateReturningModelclassMyQuerySet(models.QuerySet,UpdateReturningMixin):passclassMyManager(models.Manager):defget_queryset(self):returnMyQuerySet(using=self.db,model=self.model)classMyModel(UpdateReturningModel):objects=MyManager()field=models.IntegerField()MethodsQuerySet methodsAfter QuerySet mixin is integrated with your model, your QuerySet-s will have 3 additional methods:fromdjango.db.modelsimportValue# Any django queryset you likeqs=MyModel.objects.all()# Update and return a ReturningQuerySet, described belowresult=qs.update_returning(field=1)# Delete data and return a ReturningQuerySet, described belowresult=qs.delete_returning()# Acts like django's QuerySet.create() method, but updates all model fields to values stored in database# Can be used to retrieve values, saved by database default/triggers etc.result=MyModel.objects.create_returning(field=Value(1)+Value(2))print(result.field)# prints: "3" instead of "Value(1) + Value(2)"# Acts like django's QuerySet.bulk_create() method, but updates all model fields to values stored in database# Can be used to retrieve values, saved by database default/triggers etc.result=MyModel.objects.bulk_create_returning([MyModel(field=Value(1)+Value(2))])print(result[0].field)# prints: "3" instead of "Value(1) + Value(2)"By default methods get all fields, fetched by the model.
To limit fields returned, you can use standardQuerySet.only()andQuerySet.defer()methods.create_returningdoesn't support these methods.bulk_create_returningdoesn't support these methods for django before 1.10.Model methodsIf model instance is created, basicsave()method is called.If model is updated, database record is updated, and saved fields are refreshed with database values.
This may be useful, if you update fields withF() expressions.
By default all fields are saved and refreshed.
Useupdate_fieldsto specify concrete fields to save and refresh.fromdjango.db.modelsimportValue,Finstance=MyModel(pk=1,field=Value(1))instance.save_returning()print(instance.field)# Output: 2# if basic save() called: F('field') + Value(1)instance.field=F('field')+1# Basic save method will not change field and you don't know, what value is in databaseinstance.save()print(instance.field)# Output: F('field') + Value(1)# Library method gives ability to fetch updated resultinstance.save_returning()print(instance.field)# Output: 2Important notes:If you don't fetch field, and then try to get it,
library acts as django does - makes extra database query to fetch attribute deferred.These queries are not lazy, as well as basicQuerySet.update()andQuerySet.delete()methods.Primary key field is fetched not looking at limiting methods, as django needs it to form a QuerySetReturningQuerySetThe result of returning functions is django_pg_returning.ReturningQuerySet.
It is based on django's RawQuerySet, but adds some extra methods to be used easier.
The main difference is thatReturningQuerySet caches query results,
while RawQuerySet executes query each time it is iterated.
All ReturningQuerySet methods are not executed on database side, they are executed in python on cached result.
The only way, ReturningQuerySet makes extra database query - is deferred field loading, described above.
Implemented methods:# UPDATE ... RETURNING query is executed here once. The result is cached.result=MyModel.objects.all().update_returning(field=1)# Get number of values fetchedprint(result.count(),len(result))# Output: 1, 1# Index and slicing. Note that the order of result is not guaranteed by the database.print(result[1],result[0:2])# Output: MyModel(...), [MyModel(...), MyModel(...), MyModel(...)]# Sintax sugar for indexingprint(result.first(),result.last())# Output: MyModel(...), MyModel(...)# Fetching values and values_list. Both methods use cache and return lists, not ValuesQuerySet like django does.# values() method cakked without fields will return all fields, fetched in returning method.# values_list() method called without fields will raise exception, as order or fields in result tuple is not obvious.print(result.values())# Output: [{'id': 1, 'field': 1}, {'id': 2, 'field': 2}]print(result.values('field'))# Output: [{'field': 1}, {'field': 2}]print(result.values_list('field',flat=True))# Output: [1, 2]print(result.values_list('field','id',named=True))# Output: [Row(field=1, id=1), Row(field=2, id=2)]Thanks for support