[4.0.x] Fixed #33333 -- Fixed setUpTestData() crash with models.BinaryField on PostgreSQL.

This makes models.BinaryField pickleable on PostgreSQL.

Regression in 3cf80d3fcf.

Thanks Adam Zimmerman for the report.

Backport of 2c7846d992 from main.
This commit is contained in:
Mariusz Felisiak 2021-12-03 11:56:22 +01:00
parent 306fbf197a
commit 2c20883cb0
6 changed files with 42 additions and 1 deletions

View File

@ -553,6 +553,16 @@ class Model(metaclass=ModelBase):
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):
@ -572,6 +582,9 @@ class Model(metaclass=ModelBase):
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):

View File

@ -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`).

View File

@ -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:

View File

@ -16,6 +16,10 @@ class PickleabilityTestCase(TestCase):
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))

View File

@ -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):

View File

@ -81,10 +81,15 @@ class TestDataTests(TestCase):
)
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):
@ -92,6 +97,21 @@ class TestDataTests(TestCase):
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):