Skip to content

Commit

Permalink
[3.2.x] Fixed #33333 -- Fixed setUpTestData() crash with models.Binar…
Browse files Browse the repository at this point in the history
…yField on PostgreSQL.

This makes models.BinaryField pickleable on PostgreSQL.

Regression in 3cf80d3.

Thanks Adam Zimmerman for the report.

Backport of 2c7846d from main.
  • Loading branch information
felixxm committed Dec 3, 2021
1 parent 0cf2d48 commit cb724ef
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 1 deletion.
13 changes: 13 additions & 0 deletions django/db/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,16 @@ def __getstate__(self):
state = self.__dict__.copy()
state['_state'] = copy.copy(state['_state'])
state['_state'].fields_cache = state['_state'].fields_cache.copy()
# memoryview cannot be pickled, so cast it to bytes and store
# separately.
_memoryview_attrs = []
for attr, value in state.items():
if isinstance(value, memoryview):
_memoryview_attrs.append((attr, bytes(value)))
if _memoryview_attrs:
state['_memoryview_attrs'] = _memoryview_attrs
for attr, value in _memoryview_attrs:
state.pop(attr)
return state

def __setstate__(self, state):
Expand All @@ -568,6 +578,9 @@ def __setstate__(self, state):
RuntimeWarning,
stacklevel=2,
)
if '_memoryview_attrs' in state:
for attr, value in state.pop('_memoryview_attrs'):
state[attr] = memoryview(value)
self.__dict__.update(state)

def _get_pk_val(self, meta=None):
Expand Down
4 changes: 3 additions & 1 deletion docs/releases/3.2.10.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ Django 3.2.10 fixes a security issue with severity "low" and several bugs in
Bugfixes
========

* ...
* Fixed a regression in Django 3.2 that caused a crash of ``setUpTestData()``
with ``BinaryField`` on PostgreSQL, which is ``memoryview``-backed
(:ticket:`33333`).
1 change: 1 addition & 0 deletions tests/queryset_pickle/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Happening(models.Model):
number1 = models.IntegerField(blank=True, default=standalone_number)
number2 = models.IntegerField(blank=True, default=Numbers.get_static_number)
event = models.OneToOneField(Event, models.CASCADE, null=True)
data = models.BinaryField(null=True)


class Container:
Expand Down
4 changes: 4 additions & 0 deletions tests/queryset_pickle/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def setUpTestData(cls):
def assert_pickles(self, qs):
self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(qs))

def test_binaryfield(self):
Happening.objects.create(data=b'binary data')
self.assert_pickles(Happening.objects.all())

def test_related_field(self):
g = Group.objects.create(name="Ponies Who Own Maybachs")
self.assert_pickles(Event.objects.filter(group=g.id))
Expand Down
1 change: 1 addition & 0 deletions tests/test_utils/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Car(models.Model):
class Person(models.Model):
name = models.CharField(max_length=100)
cars = models.ManyToManyField(Car, through='PossessedCar')
data = models.BinaryField(null=True)


class PossessedCar(models.Model):
Expand Down
20 changes: 20 additions & 0 deletions tests/test_utils/test_testcase.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,37 @@ def setUpTestData(cls):
)
cls.non_deepcopy_able = NonDeepCopyAble()

cls.person_binary = Person.objects.create(name='Person', data=b'binary data')
cls.person_binary_get = Person.objects.get(pk=cls.person_binary.pk)

@assert_no_queries
def test_class_attribute_equality(self):
"""Class level test data is equal to instance level test data."""
self.assertEqual(self.jim_douglas, self.__class__.jim_douglas)
self.assertEqual(self.person_binary, self.__class__.person_binary)
self.assertEqual(self.person_binary_get, self.__class__.person_binary_get)

@assert_no_queries
def test_class_attribute_identity(self):
"""
Class level test data is not identical to instance level test data.
"""
self.assertIsNot(self.jim_douglas, self.__class__.jim_douglas)
self.assertIsNot(self.person_binary, self.__class__.person_binary)
self.assertIsNot(self.person_binary_get, self.__class__.person_binary_get)

@assert_no_queries
def test_binaryfield_data_type(self):
self.assertEqual(bytes(self.person_binary.data), b'binary data')
self.assertEqual(bytes(self.person_binary_get.data), b'binary data')
self.assertEqual(
type(self.person_binary_get.data),
type(self.__class__.person_binary_get.data),
)
self.assertEqual(
type(self.person_binary.data),
type(self.__class__.person_binary.data),
)

@assert_no_queries
def test_identity_preservation(self):
Expand Down

0 comments on commit cb724ef

Please sign in to comment.