From ee514e6828b8f658b4253fc88219340284953737 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Sat, 19 Oct 2024 12:11:48 +0100 Subject: [PATCH 1/2] Don't proxy special methods to model in ProxyModel.__getattr__ ProxyModel.__getattr__ previously proxied all attribute lookups to the model class, but some third party libraries (e.g. DRF) will make calls which should be handled by the ProxyModel instance rather than the proxied class. For example, deepcopy invokes `__reduce_ex__()` that pickles an instance and needs access to `__getstate__()` which does note exist on a class. Proxying calls to the model is required in some cases, e.g. for access to _meta. This change avoids proxying any special methods (those starting with `__`) to the model. Fixes DRF schema generation for a serializer which contains a field using QuerySetSequence. Adds test cases to verify behaviour of method proxying. Fixes #107 --- queryset_sequence/__init__.py | 10 ++++++---- tests/test_proxymodel.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 tests/test_proxymodel.py diff --git a/queryset_sequence/__init__.py b/queryset_sequence/__init__.py index 1e65220..f269295 100644 --- a/queryset_sequence/__init__.py +++ b/queryset_sequence/__init__.py @@ -493,7 +493,11 @@ def __init__(self, model=None): self.DoesNotExist = ObjectDoesNotExist def __getattr__(self, name): - return getattr(self._model, name) + if name == "_meta": + return getattr(self._model, name) + if hasattr(super(), "__getattr__"): + return super().__getattr__(name) + raise AttributeError(name) class QuerySetSequence: @@ -846,9 +850,7 @@ def values_list(self, *fields, flat=False, named=False): clone._iterable_class = ( NamedValuesListIterable if named - else FlatValuesListIterable - if flat - else ValuesListIterable + else FlatValuesListIterable if flat else ValuesListIterable ) return clone diff --git a/tests/test_proxymodel.py b/tests/test_proxymodel.py new file mode 100644 index 0000000..c0fe298 --- /dev/null +++ b/tests/test_proxymodel.py @@ -0,0 +1,34 @@ +from django.core.exceptions import ObjectDoesNotExist +from django.test import TestCase + +from queryset_sequence import ProxyModel +from tests.models import Article + + +class TestProxyModel(TestCase): + """Tests calls to proxy model are handled as expected""" + + def test_no_model_doesnotexist(self): + """When no model is defined, generic ObjectDoesNotExist exception is returned""" + proxy = ProxyModel(model=None) + self.assertIs(proxy.DoesNotExist, ObjectDoesNotExist) + + def test_model_doesnotexist(self): + """When a model is defined, model-specific DoesNotExist exception is returned""" + proxy = ProxyModel(model=Article) + self.assertIs(proxy.DoesNotExist, Article.DoesNotExist) + + def test_model_meta(self): + """When a model is defined, model._meta is accessible""" + proxy = ProxyModel(model=Article) + self.assertEqual(proxy._meta.model_name, "article") + + def test_no_model_meta(self): + """When a model is not defined, accessing model meta should fail""" + proxy = ProxyModel(model=None) + self.assertRaises(AttributeError, lambda: proxy._meta) + + def test_model_special_methods_are_not_proxied(self): + """When a model is defined, special methods are not proxied to the model""" + proxy = ProxyModel(model=Article) + self.assertIsNot(proxy.__str__, Article.__str__) From 66bea0c2d53dda5bd10bcbf5d3ab69087a9ef2d7 Mon Sep 17 00:00:00 2001 From: Ben Roberts Date: Sat, 19 Oct 2024 13:20:59 +0100 Subject: [PATCH 2/2] Fix black warning (alerts in 22.3.0 used by GHA, not in 24.10.0) --- queryset_sequence/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/queryset_sequence/__init__.py b/queryset_sequence/__init__.py index f269295..dfb84cf 100644 --- a/queryset_sequence/__init__.py +++ b/queryset_sequence/__init__.py @@ -850,7 +850,9 @@ def values_list(self, *fields, flat=False, named=False): clone._iterable_class = ( NamedValuesListIterable if named - else FlatValuesListIterable if flat else ValuesListIterable + else FlatValuesListIterable + if flat + else ValuesListIterable ) return clone