From 8a1d0662f104d76031540ca8ecc95c8e43f55fae Mon Sep 17 00:00:00 2001 From: Keryn Knight Date: Sun, 24 Nov 2013 14:16:18 +0000 Subject: [PATCH] Provide dir() support for PassThroughManagers. Reported in #55 by erikcw. --- CHANGES.rst | 6 ++++++ model_utils/managers.py | 14 ++++++++++++++ model_utils/tests/tests.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index de21f3e..b79408f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,6 +4,12 @@ CHANGES master (unreleased) ------------------- +* ``PassThroughManager`` now has support for seeing exposed methods via + ``dir``, allowing `IPython`_ tab completion to be useful. Merge of GH-104, + fixes GH-55. + +.. _IPython: http://ipython.org/ + 2.0.3 (2014.03.19) ------------------- diff --git a/model_utils/managers.py b/model_utils/managers.py index aac6d7c..0028f84 100644 --- a/model_utils/managers.py +++ b/model_utils/managers.py @@ -244,6 +244,20 @@ class PassThroughManagerMixin(object): return getattr(self.get_query_set(), name) return getattr(self.get_queryset(), name) + def __dir__(self): + """ + Allow introspection via dir() and ipythonesque tab-discovery. + + We do dir(type(self)) because to do dir(self) would be a recursion + error. + We call dir(self.get_query_set()) because it is possible that the + queryset returned by get_query_set() is interesting, even if + self._queryset_cls is None. + """ + my_values = frozenset(dir(type(self))) + my_values |= frozenset(dir(self.get_query_set())) + return list(my_values) + def get_queryset(self): try: qs = super(PassThroughManagerMixin, self).get_queryset() diff --git a/model_utils/tests/tests.py b/model_utils/tests/tests.py index 63b16d1..640fcc1 100644 --- a/model_utils/tests/tests.py +++ b/model_utils/tests/tests.py @@ -1235,6 +1235,40 @@ class PassThroughManagerTests(TestCase): self.assertFalse(hasattr(dude.cars_owned, 'by_name')) + def test_using_dir(self): + # make sure introspecing via dir() doesn't actually cause queries, + # just as a sanity check. + with self.assertNumQueries(0): + querysets_to_dir = ( + Dude.objects, + Dude.objects.by_name('Duder'), + Dude.objects.all().by_name('Duder'), + Dude.abiders, + Dude.abiders.rug_positive(), + Dude.abiders.all().rug_positive() + ) + for qs in querysets_to_dir: + self.assertTrue('by_name' in dir(qs)) + self.assertTrue('abiding' in dir(qs)) + self.assertTrue('rug_positive' in dir(qs)) + self.assertTrue('rug_negative' in dir(qs)) + # some standard qs methods + self.assertTrue('count' in dir(qs)) + self.assertTrue('order_by' in dir(qs)) + self.assertTrue('select_related' in dir(qs)) + # make sure it's been de-duplicated + self.assertEqual(1, dir(qs).count('distinct')) + + # manager only method. + self.assertTrue('get_stats' in dir(Dude.abiders)) + # manager only method shouldn't appear on the non AbidingManager + self.assertFalse('get_stats' in dir(Dude.objects)) + # standard manager methods + self.assertTrue('get_query_set' in dir(Dude.abiders)) + self.assertTrue('contribute_to_class' in dir(Dude.abiders)) + + + class CreatePassThroughManagerTests(TestCase): def setUp(self): self.dude = Dude.objects.create(name='El Duderino')