diff --git a/django/db/models/base.py b/django/db/models/base.py index 3c46afcf9f..218782c8df 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -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): diff --git a/docs/releases/3.2.10.txt b/docs/releases/3.2.10.txt index 8df403e3d4..18f0f9f09a 100644 --- a/docs/releases/3.2.10.txt +++ b/docs/releases/3.2.10.txt @@ -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`). diff --git a/tests/queryset_pickle/models.py b/tests/queryset_pickle/models.py index 689a11f65e..9a033ffe95 100644 --- a/tests/queryset_pickle/models.py +++ b/tests/queryset_pickle/models.py @@ -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: diff --git a/tests/queryset_pickle/tests.py b/tests/queryset_pickle/tests.py index bf61960419..60d30cd8ce 100644 --- a/tests/queryset_pickle/tests.py +++ b/tests/queryset_pickle/tests.py @@ -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)) diff --git a/tests/test_utils/models.py b/tests/test_utils/models.py index b0497f12b9..57518aef90 100644 --- a/tests/test_utils/models.py +++ b/tests/test_utils/models.py @@ -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): diff --git a/tests/test_utils/test_testcase.py b/tests/test_utils/test_testcase.py index 9b11168b13..0c2417c339 100644 --- a/tests/test_utils/test_testcase.py +++ b/tests/test_utils/test_testcase.py @@ -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):