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.
This commit is contained in:
Mariusz Felisiak 2021-12-03 11:56:22 +01:00 committed by GitHub
parent 9c1fe446b6
commit 2c7846d992
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 42 additions and 1 deletions

View File

@ -572,6 +572,16 @@ class Model(metaclass=ModelBase):
"""Hook to allow choosing the attributes to pickle.""" """Hook to allow choosing the attributes to pickle."""
state = self.__dict__.copy() state = self.__dict__.copy()
state['_state'] = copy.copy(state['_state']) state['_state'] = copy.copy(state['_state'])
# 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 return state
def __setstate__(self, state): def __setstate__(self, state):
@ -591,6 +601,9 @@ class Model(metaclass=ModelBase):
RuntimeWarning, RuntimeWarning,
stacklevel=2, stacklevel=2,
) )
if '_memoryview_attrs' in state:
for attr, value in state.pop('_memoryview_attrs'):
state[attr] = memoryview(value)
self.__dict__.update(state) self.__dict__.update(state)
def _get_pk_val(self, meta=None): 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 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) number1 = models.IntegerField(blank=True, default=standalone_number)
number2 = models.IntegerField(blank=True, default=Numbers.get_static_number) number2 = models.IntegerField(blank=True, default=Numbers.get_static_number)
event = models.OneToOneField(Event, models.CASCADE, null=True) event = models.OneToOneField(Event, models.CASCADE, null=True)
data = models.BinaryField(null=True)
class Container: class Container:

View File

@ -16,6 +16,10 @@ class PickleabilityTestCase(TestCase):
def assert_pickles(self, qs): def assert_pickles(self, qs):
self.assertEqual(list(pickle.loads(pickle.dumps(qs))), list(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): def test_related_field(self):
g = Group.objects.create(name="Ponies Who Own Maybachs") g = Group.objects.create(name="Ponies Who Own Maybachs")
self.assert_pickles(Event.objects.filter(group=g.id)) self.assert_pickles(Event.objects.filter(group=g.id))

View File

@ -8,6 +8,7 @@ class Car(models.Model):
class Person(models.Model): class Person(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
cars = models.ManyToManyField(Car, through='PossessedCar') cars = models.ManyToManyField(Car, through='PossessedCar')
data = models.BinaryField(null=True)
class PossessedCar(models.Model): class PossessedCar(models.Model):

View File

@ -74,10 +74,15 @@ class TestDataTests(TestCase):
belongs_to=cls.jim_douglas, belongs_to=cls.jim_douglas,
) )
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 @assert_no_queries
def test_class_attribute_equality(self): def test_class_attribute_equality(self):
"""Class level test data is equal to instance level test data.""" """Class level test data is equal to instance level test data."""
self.assertEqual(self.jim_douglas, self.__class__.jim_douglas) 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 @assert_no_queries
def test_class_attribute_identity(self): def test_class_attribute_identity(self):
@ -85,6 +90,21 @@ class TestDataTests(TestCase):
Class level test data is not identical to instance level test data. Class level test data is not identical to instance level test data.
""" """
self.assertIsNot(self.jim_douglas, self.__class__.jim_douglas) 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 @assert_no_queries
def test_identity_preservation(self): def test_identity_preservation(self):