1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 | from abc import abstractmethod
from django.db import models
from django.db.models import query
class PolymorphicQuerySet(query.QuerySet):
def get(self, *args, **kwargs):
if kwargs.has_key('model_class'):
cls = kwargs.pop('model_class')
kwargs['mod'] = cls.__module__
kwargs['cls'] = cls.__name__
return super(PolymorphicQuerySet, self).get(*args, **kwargs)
def filter(self, *args, **kwargs):
if kwargs.has_key('model_class'):
cls = kwargs.pop('model_class')
kwargs['mod'] = cls.__module__
kwargs['cls'] = cls.__name__
return super(PolymorphicQuerySet, self).filter(*args, **kwargs)
class PolymorphicManager(models.Manager):
def get_query_set(self):
return PolymorphicQuerySet(self.model, using=self._db)
def get(self, *args, **kwargs):
return self.get_query_set().get(*args, **kwargs)
def filter(self, *args, **kwargs):
return self.get_query_set().filter(*args, **kwargs)
class PolymorphicModel(models.Model):
classes = dict()
mod = models.CharField(max_length=50)
cls = models.CharField(max_length=30)
class Meta:
abstract = True
@staticmethod
def __new__(cls, *args, **kwargs):
if len(args) > len(cls._meta.fields):
raise IndexError("Number of args exceeds number of fields")
c = None
m = None
fields_iter = iter(cls._meta.fields)
for val, field in itertools.izip(args, fields_iter):
if field.name == 'cls':
c = val
elif field.name == 'mod':
m = val
if c is None:
c = kwargs.get('cls', None)
if m is None:
m = kwargs.get('mod', None)
if c is not None:
assert m is not None
m = str(m)
c = str(c)
if not Model.classes.has_key(c):
cls = getattr(__import__(m, globals(), locals(), [c]), c)
PolymorphicModel.classes[(m, c)] = cls
else:
cls = Model.classes[(m, c)]
return super(PolymorphicModel, cls).__new__(cls, *args, **kwargs)
def save(self, *args, **kwargs):
if not self.cls:
self.mod = self.__class__.__module__
self.cls = self.__class__.__name__
super(PolymorphicModel, self).save(*args, **kwargs)
class SomeBase(PolymorphicModel):
a = IntegerField(default=0)
b = IntegerField(default=0)
@abstractmethod
def something(self): pass
class SomeDerivedA(SomeBase):
class Meta:
proxy = True
def something(self):
self.a += 1
class SomeDerivedB(SomeBase):
class Meta:
proxy = True
def something(self):
self.b += 1
class SomeCollection(models.Model):
values = models.ManyToManyField(SomeBase)
>>> a = SomeDerivedA()
>>> a.save()
>>> b = SomeDerivedB()
>>> b.save()
>>> x = SomeCollection()
>>> x.values = [a, b]
>>> x.save()
>>> y = SomeCollection.objects.get(pk=x.pk)
>>> y.values.objects[0].something()
>>> y.values.objects[0].a
1
>>> y.values.objects[0].b
0
>>> y.values.objects[1].something()
>>> y.values.objects[1].a
0
>>> y.values.objects[1].b
1
|
Comments
Corrected a serious bug - using new instead of init to perform class changes.
#
I think there are some imports missing, one is itertools, the other is Model; the second I don't know where to import from.
#