1. Deepwalker

    30.09.2008 08:17

    Итак, я на днях поразбирался с наследованием в Django.

    from django.db import models
    from django.contrib.contenttypes import generic
    
    # Create your models here.
    
    class Parent(models.Model):
        name = models.CharField(max_length=30)
        def __unicode__(self):
            return 'Parent: ' + self.name
    
    class Child1(Parent):
        number = models.IntegerField()
        def __unicode__(self):
            return 'Child1: ' + self.name
    
    class Child2(Parent):
        num = models.IntegerField()
    
    class second(models.Model):
        parent = models.ForeignKey(Parent)
    

    Создаю Child1 и second, присваиваю second.parent Child1. Это работает. Но проблема в том, что когда я обращаюсь к second.parent, я попадаю именно на экземпляр класса Parent, с его методами и полями. А хотелось то конечно, чтобы вызывался именно __unicode__ Child1, а не Parent.

    Видимо так сделано просто. И в самом деле - как ORM должен вычислить, что вот этот конкретный Parent на самом деле его потомок Child1. Как вы думаете, должна ли быть возможность получить именно потомка? Я знаю, что можно получить потомка через parent.child1, но откуда же мне знать какого именно потомка получать?

    Самым простым решением смотрится добавление к предку поля generic.GenericForeignKey и заполнение его потомком.

    Хотелось бы прочитать мыли сообщества на эту тему.

  2. Иван Сагалаев

    30.09.2008 11:45

    Но проблема в том, что когда я обращаюсь к second.parent, я попадаю именно на экземпляр класса Parent, с его методами и полями.

    Не верю :-). Если сделать ровно так:

    c = Child1(name='Child1', number=1)
    c.save()
    s = second(parent=c)
    s.save()
    print s.parent
    

    То позовется Child1.__unicode__. Потому что живой объект класса конечно знает, к какому классу он относится.

    Но я подозреваю, что проблема в другом. Если потом сделать так:

    s = second.objects.get(pk=s.id)
    print s.parent
    

    То это уже будет объект класса Parent. Потому что при создании объекта s из базы он понятия не имеет, каким именно наследником он когда-то был проинициализирован, у него остается только ссылка на parent. Сохранение объекта в базу не является его полной десериализацией и вся питоновая метаинформация о том, какому классу он принадлежит, теряется. Это частный случай проявления объектно-реляционного несовпадения импеданса, из-за которого ORM никогда не бывают полностью прозрачными. Надо просто это учитывать. Проще всего — вообще не пользоваться наследованием моделей без крайней надобности.

  3. Deepwalker

    01.10.2008 09:08

    Ну а если убрать несовпадение? Чтобы по s.parent.successor получать именно потомка, а не перебирать всех возможных.

    Для этого, как я понимаю, надо информацию о потомке сохранять в parent. Как вы думаете, стоит сделать такую опцию? Как мне видится, надо будет добавить поле (можно просто информацию о классе наследника, наверное) к предку, и по обращению к successor отдавать именно потомка или самого предка, если к потомку он не относится. Конечно же возможность должна быть опциональной, так как это добавит оверхед к данным.

  4. Иван Сагалаев

    01.10.2008 10:39

    Не, несовпадение — это другое. Оно означает, что в целом есть принципиальная разница между реляционной моделью и объектной, и она приводит ко всяким неудобствам, вроде вот этого. Конечно, этот конкретный случай можно решить — например, да, хранением поля с типом. Но я бы подумал, а стоит ли вообще этим заниматься, потому что это выглядит как-то излишне сложно.

  5. Deepwalker

    01.10.2008 10:54

    Зато удобно, кому то может пригодится. Может быть попробую в свободное время сделать патч к джанге.

  6. Проблема в том, что у одного класса потомков то может быть несколько.

    Я сейчас как раз потихоньку пишу специальный blog application, в котором используется наследование для создания постов разного типа. И для рендеринга придется решать проблему вызова метода потомка.

    Пока что придумал такое решение в виде декоратора:

    def virtual(func):
        '''Find a child object and call method against it
           instead os original 'func'.
    
           Apply this decorator to any method of the base
           class.
        '''
    
        def wrap(self, *args, **kwargs):
            if getattr(self, '_child_object', None) is None:
                sub_objects = CollectedObjects()
                self._collect_sub_objects(sub_objects)
                setattr(self,
                        '_child_object',
                        sub_objects.items()[0][1].values()[0])
    
            child_method = getattr(self._child_object, func.__name__)
            if getattr(child_method, '_is_virtual_wrap', False):
                return func(self, *args, **kwargs)
            return child_method(*args, **kwargs)
        wrap.__doc__  = func.__doc__
        wrap.__name__ = func.__name__
        setattr(wrap, '_is_virtual_wrap', True)
        return wrap
    
  7. ob3r0n.livejournal.com

    03.10.2008 23:43

    а нельзя в родителе записывать свое имя (т.е. имя родителя) и после наследования (уже в потомке) в __init__ его менять?

    хотя не понятно, что делать при множественном наследовании, но это изврат: в моделях джанги использоваться множественное наследование.

bbcode