Skip to content

Instantly share code, notes, and snippets.

@benctamas
Created July 11, 2012 00:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benctamas/3087108 to your computer and use it in GitHub Desktop.
Save benctamas/3087108 to your computer and use it in GitHub Desktop.
django - heterogeneous proxy model based queryset
from django.db import models
from django.db.models.query import QuerySet
from django.utils.functional import memoize
_class_mapper_cache = {}
def class_mapper(model):
proxy_for = model._meta.proxy_for_model or model
mapper = dict([ (getattr(m, m.CLASS_MAP_ATTR), m) for m in models.get_models() if issubclass(m, proxy_for) and m != proxy_for])
return mapper
class_mapper = memoize(class_mapper, _class_mapper_cache, 1)
_arg_index_cache = {}
def arg_index(model):
return [ index for index,field in enumerate(model._meta.fields) if field.name == model.INSTANCE_MAP_ATTR][0] #any better idea?
arg_index = memoize(arg_index, _arg_index_cache, 1)
class QuerySetModelWrapper(object):
def __init__(self, model):
self.model = model
def __getattr__(self, attr):
return getattr(self.model, attr)
def __call__(self, *args, **kwargs):
index = arg_index(self.model)
value = kwargs.get(self.model.INSTANCE_MAP_ATTR, None) or args[index]
model = class_mapper(self.model).get(value, self.model)
return model(*args, **kwargs)
class ProxyQSManager(models.Manager):
def get_query_set(self):
ancestor = self.model._meta.proxy_for_model or self.model
wrapper = QuerySetModelWrapper(ancestor)
qs = QuerySet(wrapper, using=self._db)
if self.model._meta.proxy:
kwargs = { self.model.INSTANCE_MAP_ATTR: getattr(self.model, self.model.CLASS_MAP_ATTR) }
qs = qs.filter(**kwargs)
return qs
class ProxyQSModel(models.Model):
CLASS_MAP_ATTR = None
INSTANCE_MAP_ATTR = None
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
ret = super(ProxyQSModel, self).__init__(*args, **kwargs)
if self._meta.proxy and getattr(self, self.INSTANCE_MAP_ATTR) is None:
setattr(self, self.INSTANCE_MAP_ATTR, getattr(self, self.CLASS_MAP_ATTR))
return ret
# -- -- -- -- -- -- -- --
class ExampleManager(ProxyQSManager):
pass
class Example(ProxyQSModel):
CLASS_MAP_ATTR = 'KIND'
INSTANCE_MAP_ATTR = 'kind'
kind = models.IntegerField()
objects = ExampleManager()
class Proxy1(Example):
KIND = 1
class Meta:
proxy = True
class Proxy2(Example):
KIND = 2
class Meta:
proxy = True
# -- -- -- -- -- -- -- --
def test():
Proxy1().save()
Proxy2().save()
Example(kind=0).save()
print Example.objects.all()
print Proxy1.objects.all()
print Proxy2.objects.all()
@benctamas
Copy link
Author

Known limitations:

  • breaks .defer(), .only()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment