diff --git a/.travis.yml b/.travis.yml index d86443f..810ab65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,77 +5,81 @@ cache: pip services: - postgresql addons: - postgresql: "11" + postgresql: "12" apt: packages: - postgresql-10 - postgresql-client-10 - postgresql-11 - postgresql-client-11 - - systemd-sysv - + - postgresql-12 + - postgresql-client-12 python: - 2.7 - - 3.4 - 3.5 - 3.6 - 3.7 + - 3.8 env: - - DJANGO=1.7 PG=9.3 - DJANGO=1.7 PG=9.4 - DJANGO=1.7 PG=9.5 - DJANGO=1.7 PG=9.6 - DJANGO=1.7 PG=10 - DJANGO=1.7 PG=11 - - DJANGO=1.8 PG=9.3 + - DJANGO=1.7 PG=12 - DJANGO=1.8 PG=9.4 - DJANGO=1.8 PG=9.5 - DJANGO=1.8 PG=9.6 - DJANGO=1.8 PG=10 - DJANGO=1.8 PG=11 - - DJANGO=1.9 PG=9.3 + - DJANGO=1.8 PG=12 - DJANGO=1.9 PG=9.4 - DJANGO=1.9 PG=9.5 - DJANGO=1.9 PG=9.6 - DJANGO=1.9 PG=10 - DJANGO=1.9 PG=11 - - DJANGO=1.10 PG=9.3 + - DJANGO=1.9 PG=12 - DJANGO=1.10 PG=9.4 - DJANGO=1.10 PG=9.5 - DJANGO=1.10 PG=9.6 - DJANGO=1.10 PG=10 - DJANGO=1.10 PG=11 - - DJANGO=1.11 PG=9.3 + - DJANGO=1.10 PG=12 - DJANGO=1.11 PG=9.4 - DJANGO=1.11 PG=9.5 - DJANGO=1.11 PG=9.6 - DJANGO=1.11 PG=10 - DJANGO=1.11 PG=11 - - DJANGO=2.0 PG=9.3 + - DJANGO=1.11 PG=12 - DJANGO=2.0 PG=9.4 - DJANGO=2.0 PG=9.5 - DJANGO=2.0 PG=9.6 - DJANGO=2.0 PG=10 - DJANGO=2.0 PG=11 - - DJANGO=2.1 PG=9.3 + - DJANGO=2.0 PG=12 - DJANGO=2.1 PG=9.4 - DJANGO=2.1 PG=9.5 - DJANGO=2.1 PG=9.6 - DJANGO=2.1 PG=10 - DJANGO=2.1 PG=11 - - DJANGO=2.2 PG=9.3 + - DJANGO=2.1 PG=12 - DJANGO=2.2 PG=9.4 - DJANGO=2.2 PG=9.5 - DJANGO=2.2 PG=9.6 - DJANGO=2.2 PG=10 - DJANGO=2.2 PG=11 + - DJANGO=2.2 PG=12 + - DJANGO=3.0 PG=9.4 + - DJANGO=3.0 PG=9.5 + - DJANGO=3.0 PG=9.6 + - DJANGO=3.0 PG=10 + - DJANGO=3.0 PG=11 + - DJANGO=3.0 PG=12 matrix: exclude: # Django 2.0+ doesn't support python 2.7 - - python: 2.7 - env: DJANGO=2.0 PG=9.3 - python: 2.7 env: DJANGO=2.0 PG=9.4 - python: 2.7 @@ -87,7 +91,7 @@ matrix: - python: 2.7 env: DJANGO=2.0 PG=11 - python: 2.7 - env: DJANGO=2.1 PG=9.3 + env: DJANGO=2.0 PG=12 - python: 2.7 env: DJANGO=2.1 PG=9.4 - python: 2.7 @@ -99,7 +103,7 @@ matrix: - python: 2.7 env: DJANGO=2.1 PG=11 - python: 2.7 - env: DJANGO=2.2 PG=9.3 + env: DJANGO=2.1 PG=12 - python: 2.7 env: DJANGO=2.2 PG=9.4 - python: 2.7 @@ -110,36 +114,44 @@ matrix: env: DJANGO=2.2 PG=10 - python: 2.7 env: DJANGO=2.2 PG=11 + - python: 2.7 + env: DJANGO=2.2 PG=12 + - python: 2.7 + env: DJANGO=3.0 PG=9.4 + - python: 2.7 + env: DJANGO=3.0 PG=9.5 + - python: 2.7 + env: DJANGO=3.0 PG=9.6 + - python: 2.7 + env: DJANGO=3.0 PG=10 + - python: 2.7 + env: DJANGO=3.0 PG=11 + - python: 2.7 + env: DJANGO=3.0 PG=12 - # Django 2.1+ doesn't support python 3.4 - - python: 3.4 - env: DJANGO=2.1 PG=9.3 - - python: 3.4 - env: DJANGO=2.1 PG=9.4 - - python: 3.4 - env: DJANGO=2.1 PG=9.5 - - python: 3.4 - env: DJANGO=2.1 PG=9.6 - - python: 3.4 - env: DJANGO=2.1 PG=10 - - python: 3.4 - env: DJANGO=2.1 PG=11 - - python: 3.4 - env: DJANGO=2.2 PG=9.3 - - python: 3.4 - env: DJANGO=2.2 PG=9.4 - - python: 3.4 - env: DJANGO=2.2 PG=9.5 - - python: 3.4 - env: DJANGO=2.2 PG=9.6 - - python: 3.4 - env: DJANGO=2.2 PG=10 - - python: 3.4 - env: DJANGO=2.2 PG=11 + # Django 3.0+ doesn't support python 3.5 + - python: 3.5 + env: DJANGO=3.0 PG=9.4 + - python: 3.5 + env: DJANGO=3.0 PG=9.5 + - python: 3.5 + env: DJANGO=3.0 PG=9.6 + - python: 3.5 + env: DJANGO=3.0 PG=10 + - python: 3.5 + env: DJANGO=3.0 PG=11 + - python: 3.5 + env: DJANGO=3.0 PG=12 + + # Django 3.0+ doesn't support Postgresql 9.4 + - python: 3.6 + env: DJANGO=3.0 PG=9.4 + - python: 3.7 + env: DJANGO=3.0 PG=9.4 + - python: 3.8 + env: DJANGO=3.0 PG=9.4 # Django 1.7 doesn't support python 3.5+ - - python: 3.5 - env: DJANGO=1.7 PG=9.3 - python: 3.5 env: DJANGO=1.7 PG=9.4 - python: 3.5 @@ -150,8 +162,8 @@ matrix: env: DJANGO=1.7 PG=10 - python: 3.5 env: DJANGO=1.7 PG=11 - - python: 3.6 - env: DJANGO=1.7 PG=9.3 + - python: 3.5 + env: DJANGO=1.7 PG=12 - python: 3.6 env: DJANGO=1.7 PG=9.4 - python: 3.6 @@ -162,8 +174,8 @@ matrix: env: DJANGO=1.7 PG=10 - python: 3.6 env: DJANGO=1.7 PG=11 - - python: 3.7 - env: DJANGO=1.7 PG=9.3 + - python: 3.6 + env: DJANGO=1.7 PG=12 - python: 3.7 env: DJANGO=1.7 PG=9.4 - python: 3.7 @@ -174,11 +186,23 @@ matrix: env: DJANGO=1.7 PG=10 - python: 3.7 env: DJANGO=1.7 PG=11 + - python: 3.7 + env: DJANGO=1.7 PG=12 + - python: 3.8 + env: DJANGO=1.7 PG=9.4 + - python: 3.8 + env: DJANGO=1.7 PG=9.5 + - python: 3.8 + env: DJANGO=1.7 PG=9.6 + - python: 3.8 + env: DJANGO=1.7 PG=10 + - python: 3.8 + env: DJANGO=1.7 PG=11 + - python: 3.8 + env: DJANGO=1.7 PG=12 - # python 3.6 has deprecated issue with django before 1.11 + # python 3.6+ has deprecated issue with django before 1.11 # https://stackoverflow.com/questions/41343263/provide-classcell-example-for-python-3-6-metaclass\ - - python: 3.6 - env: DJANGO=1.8 PG=9.3 - python: 3.6 env: DJANGO=1.8 PG=9.4 - python: 3.6 @@ -189,8 +213,8 @@ matrix: env: DJANGO=1.8 PG=10 - python: 3.6 env: DJANGO=1.8 PG=11 - - python: 3.7 - env: DJANGO=1.8 PG=9.3 + - python: 3.6 + env: DJANGO=1.8 PG=12 - python: 3.7 env: DJANGO=1.8 PG=9.4 - python: 3.7 @@ -201,8 +225,20 @@ matrix: env: DJANGO=1.8 PG=10 - python: 3.7 env: DJANGO=1.8 PG=11 - - python: 3.6 - env: DJANGO=1.9 PG=9.3 + - python: 3.7 + env: DJANGO=1.8 PG=12 + - python: 3.8 + env: DJANGO=1.8 PG=9.4 + - python: 3.8 + env: DJANGO=1.8 PG=9.5 + - python: 3.8 + env: DJANGO=1.8 PG=9.6 + - python: 3.8 + env: DJANGO=1.8 PG=10 + - python: 3.8 + env: DJANGO=1.8 PG=11 + - python: 3.8 + env: DJANGO=1.8 PG=12 - python: 3.6 env: DJANGO=1.9 PG=9.4 - python: 3.6 @@ -212,9 +248,7 @@ matrix: - python: 3.6 env: DJANGO=1.9 PG=10 - python: 3.6 - env: DJANGO=1.9 PG=11 - - python: 3.7 - env: DJANGO=1.9 PG=9.3 + env: DJANGO=1.9 PG=12 - python: 3.7 env: DJANGO=1.9 PG=9.4 - python: 3.7 @@ -225,8 +259,20 @@ matrix: env: DJANGO=1.9 PG=10 - python: 3.7 env: DJANGO=1.9 PG=11 - - python: 3.6 - env: DJANGO=1.10 PG=9.3 + - python: 3.7 + env: DJANGO=1.9 PG=12 + - python: 3.8 + env: DJANGO=1.9 PG=9.4 + - python: 3.8 + env: DJANGO=1.9 PG=9.5 + - python: 3.8 + env: DJANGO=1.9 PG=9.6 + - python: 3.8 + env: DJANGO=1.9 PG=10 + - python: 3.8 + env: DJANGO=1.9 PG=11 + - python: 3.8 + env: DJANGO=1.9 PG=12 - python: 3.6 env: DJANGO=1.10 PG=9.4 - python: 3.6 @@ -237,8 +283,8 @@ matrix: env: DJANGO=1.10 PG=10 - python: 3.6 env: DJANGO=1.10 PG=11 - - python: 3.7 - env: DJANGO=1.10 PG=9.3 + - python: 3.6 + env: DJANGO=1.10 PG=12 - python: 3.7 env: DJANGO=1.10 PG=9.4 - python: 3.7 @@ -249,13 +295,29 @@ matrix: env: DJANGO=1.10 PG=10 - python: 3.7 env: DJANGO=1.10 PG=11 - + - python: 3.7 + env: DJANGO=1.10 PG=12 + - python: 3.8 + env: DJANGO=1.10 PG=9.4 + - python: 3.8 + env: DJANGO=1.10 PG=9.5 + - python: 3.8 + env: DJANGO=1.10 PG=9.6 + - python: 3.8 + env: DJANGO=1.10 PG=10 + - python: 3.8 + env: DJANGO=1.10 PG=11 + - python: 3.8 + env: DJANGO=1.10 PG=12 before_install: - # Use default PostgreSQL 11 port + # Set up PostgreSQL 11 and 12 - sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/11/main/postgresql.conf - sudo cp /etc/postgresql/{10,11}/main/pg_hba.conf + - sudo sed -i 's/port = 5434/port = 5432/' /etc/postgresql/12/main/postgresql.conf + - sudo cp /etc/postgresql/{10,12}/main/pg_hba.conf + # Start PostgreSQL version we need - sudo systemctl stop postgresql - sudo systemctl start postgresql@$PG-main diff --git a/README.md b/README.md index 88ccdf3..849859f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A small library implementing PostgreSQL ability to return rows in DML statements [Link to PostgreSQL docs](https://www.postgresql.org/docs/10/static/sql-update.html) ## Requirements -* Python 2.7 or Python 3.4+ +* Python 2.7 or Python 3.5+ * django >= 1.7 Previous versions may also work, but haven't been tested. bulk_create_returning method doesn't support .only() and .defer() filters for django before 1.10. @@ -11,7 +11,7 @@ A small library implementing PostgreSQL ability to return rows in DML statements * six * typing * psycopg2 -* PostgreSQL 9.3+ +* PostgreSQL 9.4+ Previous versions may also work, but haven't been tested. ## Installation diff --git a/src/django_pg_returning/manager.py b/src/django_pg_returning/manager.py index 7060966..84f833d 100644 --- a/src/django_pg_returning/manager.py +++ b/src/django_pg_returning/manager.py @@ -1,8 +1,8 @@ +from typing import Dict, Any, List, Type, Optional, Tuple + import django from django.db import transaction, models from django.db.models import sql, Field, QuerySet -from typing import Dict, Any, List, Type, Optional, Tuple - from django.db.models.query import EmptyResultSet from .compatibility import chain_query, get_model_fields @@ -17,12 +17,12 @@ def _get_loaded_field_cb(target, model, fields): """ target[model] = fields - def _insert(self, objs, fields, return_id=False, raw=False, using=None, ignore_conflicts=False): + def _insert(self, objs, fields, **kwargs): """ Replaces standard insert procedure for bulk_create_returning """ if not getattr(self.model, '_insert_returning', False): - return QuerySet._insert(self, objs, fields, return_id=return_id, raw=raw, using=using) + return QuerySet._insert(self, objs, fields, **kwargs) # Returns attname, not column. # Before django 1.10 pk fields hasn't been returned from postgres. @@ -33,13 +33,20 @@ def _insert(self, objs, fields, return_id=False, raw=False, using=None, ignore_c "You can't fetch relative model fields with returning operation" self._for_write = True + using = kwargs.get('using', None) or self.db - kwargs = {} if django.VERSION < (2, 2) else {'ignore_conflicts': ignore_conflicts} - query = sql.InsertQuery(self.model, **kwargs) - query.insert_values(fields, objs, raw=raw) + query_kwargs = {} if django.VERSION < (2, 2) else {'ignore_conflicts': kwargs.get('ignore_conflicts')} + query = sql.InsertQuery(self.model, **query_kwargs) + query.insert_values(fields, objs, raw=kwargs.get('raw')) self.model._insert_returning_cache = self._execute_sql(query, return_fields, using=using) - return self.model._insert_returning_cache.values_list(self.model._meta.pk.column, flat=True) if return_id else None + if django.VERSION < (3,): + return self.model._insert_returning_cache.values_list(self.model._meta.pk.column, flat=True) \ + if kwargs.get('return_id', False) else None + else: + returning_fields = kwargs.get('returning_fields', None) + return self.model._insert_returning_cache.values_list(*(f.column for f in returning_fields)) \ + if returning_fields is not None else None _insert.alters_data = True _insert.queryset_only = False @@ -92,7 +99,7 @@ def _get_returning_qs(self, query_type, values=None, **updates): :raises AssertionError: If input data is invalid """ assert self.query.can_filter(), "Can not update or delete once a slice has been taken." - assert getattr(self, '_fields', None) is None,\ + assert getattr(self, '_fields', None) is None, \ "Can not call delete() or update() after .values() or .values_list()" # Returns attname, not column. @@ -169,7 +176,8 @@ def bulk_create_returning(self, objs, batch_size=None): # Replace values fetched from returned data if result and result[0].pk: # For django 1.10+ where objects can be matched - values_dict = {item[self.model._meta.pk.column]: item for item in self.model._insert_returning_cache.values()} + values_dict = {item[self.model._meta.pk.column]: item for item in + self.model._insert_returning_cache.values()} for item in result: for k, v in values_dict[item.pk].items(): setattr(item, k, v)