Ответ 1
Вы можете сделать модели Django полиморфными, используя описанный подход здесь. Я считаю, что этот код находится на ранних этапах разработки, но стоит исследовать.
Скажем, у меня есть модели:
class Animal(models.Model):
type = models.CharField(max_length=255)
class Dog(Animal):
def make_sound(self):
print "Woof!"
class Meta:
proxy = True
class Cat(Animal):
def make_sound(self):
print "Meow!"
class Meta:
proxy = True
Скажем, я хочу сделать:
animals = Animal.objects.all()
for animal in animals:
animal.make_sound()
Я хочу вернуть серию Woofs и Meows. Ясно, что я мог бы просто определить make_sound в оригинальной модели, что вилки на основе animal_type, но затем каждый раз, когда я добавляю новый тип животных (представьте, что они находятся в разных приложениях), мне нужно будет войти и отредактировать эту функцию make_sound, Я бы предпочел просто определить прокси-модели и определить их поведение. Из того, что я могу сказать, нет способа вернуть смешанные экземпляры Cat или Dog, но я подумал, может быть, я могу определить метод get_proxy_model для основного класса, который возвращает модель кошки или собаки.
Конечно, вы могли бы сделать это и передать что-то вроде первичного ключа, а затем просто сделать Cat.objects.get(pk = pass_in_primary_key). Но это будет означать выполнение дополнительного запроса для данных, которые у вас уже есть, что кажется излишним. Есть ли способ превратить животное в кошку или собачий экземпляр эффективным способом? Какой правильный способ сделать то, что я хочу достичь?
Вы можете сделать модели Django полиморфными, используя описанный подход здесь. Я считаю, что этот код находится на ранних этапах разработки, но стоит исследовать.
Подход Metaclass, предложенный thedk, действительно очень эффективный способ, однако мне пришлось объединить его с ответом на вопрос здесь, чтобы получить запрос верните экземпляр прокси-модели. Упрощенная версия кода, адаптированная к предыдущему примеру, будет выглядеть следующим образом:
from django.db.models.base import ModelBase
class InheritanceMetaclass(ModelBase):
def __call__(cls, *args, **kwargs):
obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
return obj.get_object()
class Animal(models.Model):
__metaclass__ = InheritanceMetaclass
type = models.CharField(max_length=255)
object_class = models.CharField(max_length=20)
def save(self, *args, **kwargs):
if not self.object_class:
self.object_class = self._meta.module_name
super(Animal, self).save( *args, **kwargs)
def get_object(self):
if self.object_class in SUBCLASSES_OF_ANIMAL:
self.__class__ = SUBCLASSES_OF_ANIMAL[self.object_class]
return self
class Dog(Animal):
class Meta:
proxy = True
def make_sound(self):
print "Woof!"
class Cat(Animal):
class Meta:
proxy = True
def make_sound(self):
print "Meow!"
SUBCLASSES_OF_ANIMAL = dict([(cls.__name__, cls) for cls in ANIMAL.__subclasses__()])
Преимущество этого прокси-подхода заключается в том, что при создании новых подклассов миграция db не требуется. Недостатком является то, что никакие конкретные поля не могут быть добавлены в подклассы.
Я был бы рад получить отзывы об этом подходе.
единственный способ, известный человеческому роду, - использовать программирование метакласса.
Вот короткий ответ:
from django.db.models.base import ModelBase
class InheritanceMetaclass(ModelBase):
def __call__(cls, *args, **kwargs):
obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
return obj.get_object()
class Animal(models.Model):
__metaclass__ = InheritanceMetaclass
type = models.CharField(max_length=255)
object_class = models.CharField(max_length=20)
def save(self, *args, **kwargs):
if not self.object_class:
self.object_class = self._meta.module_name
super(Animal, self).save( *args, **kwargs)
def get_object(self):
if not self.object_class or self._meta.module_name == self.object_class:
return self
else:
return getattr(self, self.object_class)
class Dog(Animal):
def make_sound(self):
print "Woof!"
class Cat(Animal):
def make_sound(self):
print "Meow!"
и желаемый результат:
shell$ ./manage.py shell_plus
From 'models' autoload: Animal, Dog, Cat
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> dog1=Dog(type="Ozzie").save()
>>> cat1=Cat(type="Kitty").save()
>>> dog2=Dog(type="Dozzie").save()
>>> cat2=Cat(type="Kinnie").save()
>>> Animal.objects.all()
[<Dog: Dog object>, <Cat: Cat object>, <Dog: Dog object>, <Cat: Cat object>]
>>> for a in Animal.objects.all():
... print a.type, a.make_sound()
...
Ozzie Woof!
None
Kitty Meow!
None
Dozzie Woof!
None
Kinnie Meow!
None
>>>
Как это работает?
Дополнительная информация о Metaclass в Python: http://www.ibm.com/developerworks/linux/library/l-pymeta.html
Этот ответ может несколько пошатнуть вопрос, потому что он не использует прокси-модели. Однако, как задает вопрос, он позволяет написать один (и без необходимости обновлять класс Animal
при добавлении новых типов) -
animals = Animal.objects.all()
for animal in animals:
animal.make_sound()
Чтобы избежать программирования метакласса, можно было использовать композицию над наследованием. Например -
class Animal(models.Model):
type = models.CharField(max_length=255)
@property
def type_instance(self):
"""Return a Dog or Cat object, etc."""
return globals()[self.type]()
def make_sound(self):
return self.type_instance.make_sound()
class Dog(object):
def make_sound(self):
print "Woof!"
class Cat(object):
def make_sound(self):
print "Meow!"
Если классы Dog
и Cat
нуждаются в доступе к экземпляру Animal
, вы также можете настроить метод type_instance()
выше, чтобы передать то, что ему нужно для конструктора класса (например, self
).