Skip to content

Commit

Permalink
Don't proxy special methods to model in ProxyModel.__getattr__
Browse files Browse the repository at this point in the history
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
  • Loading branch information
optiz0r committed Oct 19, 2024
1 parent 3fe2994 commit 98cd197
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 1 deletion.
4 changes: 3 additions & 1 deletion queryset_sequence/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,9 @@ 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)
return super().__getattr(name)


class QuerySetSequence:
Expand Down
34 changes: 34 additions & 0 deletions tests/test_proxymodel.py
Original file line number Diff line number Diff line change
@@ -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.__getstate__, Article.__getstate__)

0 comments on commit 98cd197

Please sign in to comment.