Могу ли я дважды добавить один и тот же объект к InstrumentedList в SQLAlchemy?

#python #list #sqlalchemy #multiple-instances #adjacency-list

#python #Список #sqlalchemy #несколько экземпляров #список смежности

Вопрос:

У меня есть довольно простое отношение N: M в SQLAlchemy 0.6.6. У меня есть класс «attractLoop», который может содержать кучу медиа (изображений или видео). Мне нужно иметь список, в который один и тот же носитель (скажем, изображение) можно добавлять дважды. Взаимосвязь заключается в следующем:

Media — это базовый класс с большинством атрибутов, которыми будут делиться изображения и видео.

 class BaseMedia(BaseClass.BaseClass, declarativeBase):
    __tablename__ = "base_media"
    _polymorphicIdentity = Column("polymorphic_identity", String(20), key="polymorphicIdentity")
    __mapper_args__ = {
        'polymorphic_on': _polymorphicIdentity,
        'polymorphic_identity': None
    }

    _name = Column("name", String(50))
    _type = Column("type", String(50))
    _size = Column("size", Integer)
    _lastModified = Column("last_modified", DateTime, key="lastModified")
    _url = Column("url", String(512))
    _thumbnailFile = Column("thumbnail_file", String(512), key="thumbnailFile")
    _md5Hash = Column("md5_hash", LargeBinary(32), key="md5Hash")
  

Тогда класс, который собирается использовать эти «медиа» вещи:

 class TestSqlAlchemyList(BaseClass.BaseClass, declarativeBase):
    __tablename__ = "tests"
    _mediaItems = relationship("BaseMedia",
                               secondary=intermediate_test_to_media,
                               primaryjoin="tests.c.id == intermediate_test_to_media.c.testId",
                               secondaryjoin="base_media.c.id == intermediate_test_to_media.c.baseMediaId",
                               collection_class=list,
                               uselist=True
                               )

    def __init__(self):
        super(TestSqlAlchemyList, self).__init__()
        self.mediaItems = list()

    def getMediaItems(self):
        return self._mediaItems

    def setMediaItems(self, mediaItems):
        if mediaItems:
            self._mediaItems = mediaItems
        else:
            self._mediaItems = list()

    def addMediaItem(self, mediaItem):
        self.mediaItems.append(mediaItem)
        #log.debug("::addMediaItem > Added media item %s to %s. Now length is %d (contains: %s)" % (mediaItem.id, self.id, len(self.mediaItems), list(item.id for item in self.mediaItems)))

    def addMediaItemById(self, mediaItemId):
        mediaItem = backlib.media.BaseMediaManager.BaseMediaManager.getById(int(mediaItemId))
        if mediaItem:
            if mediaItem.validityCheck():
                self.addMediaItem(mediaItem)
            else:
                raise TypeError("Media item with id %s didn't pass the validity check" % mediaItemId)
        else:
            raise KeyError("Media Item with id %s not found" % mediaItem)

    mediaItems = synonym('_mediaItems', descriptor=property(getMediaItems, setMediaItems))
  

И промежуточный класс для связывания обеих таблиц:

 intermediate_test_to_media = Table(
                                   "intermediate_test_to_media",
                                   Database.Base.metadata,
                                   Column("id", Integer, primary_key=True),
                                   Column("test_id", Integer, ForeignKey("tests.id"), key="testId"),
                                   Column("base_media_id", Integer, ForeignKey("base_media.id"), key="baseMediaId")
                                   )
  

Когда я дважды добавляю один и тот же медиа-объект (экземпляр) к одному экземпляру этого TestSqlAlchemyList, он добавляет два правильно, но когда я извлекаю экземпляр TestSqlAlchemyList из базы данных, я получаю только один. Похоже, он ведет себя больше как set.

Промежуточная таблица содержит всю необходимую информацию, поэтому вставка, похоже, работает нормально. Это когда я пытаюсь загрузить список из базы данных, когда я не получаю все элементы, которые я вставил.

 mysql> SELECT * FROM intermediate_test_to_media;
 ---- --------- --------------- 
| id | test_id | base_media_id |
 ---- --------- --------------- 
|  1 |       1 |             1 |
|  2 |       1 |             1 |
|  3 |       1 |             2 |
|  4 |       1 |             2 |
|  5 |       1 |             1 |
|  6 |       1 |             1 |
|  7 |       2 |             1 |
|  8 |       2 |             1 |
|  9 |       2 |             1 |
| 10 |       2 |             2 |
| 11 |       2 |             1 |
| 12 |       2 |             1 |
  

Как вы можете видеть, экземпляр «test» с id = 1 должен иметь носитель [1, 1, 2, 2, 1, 1]. Ну, это не так. Когда я загружаю его из базы данных, у него есть только носитель [1, 2]

Я попытался установить любой параметр в отношениях, который мог бы иметь значение list … uselist, collection_class = список… Ничего…

Вы увидите, что классы наследуются от базового класса. Это просто класс, который на самом деле не сопоставлен ни с одной таблицей, но содержит числовое поле («id»), которое будет первичным ключом для каждого класса, и кучу других методов, полезных для остальных классов в моей системе (toJSON, ToXML …). На всякий случай, я прилагаю выдержку из него:

 class BaseClass(object):
    _id = Column("id", Integer, primary_key=True, key="id")
    def __hash__(self):
        return int(self.id)

    def setId(self, id):
            try:
                    self._id = int(id)
            except TypeError:
                    self._id = None

    def getId(self):
        return self._id

    @declared_attr
    def id(cls):
        return synonym('_id', descriptor=property(cls.getId, cls.setId))
  

Если кто-нибудь может меня подтолкнуть, я буду очень признателен. Спасибо. И извините за огромный пост… Я действительно не знаю, как объяснить лучше.

Комментарии:

1. Вы пробовали включить функцию отладки SQL, чтобы просмотреть инструкции SQL? Интересно, не выбирает ли Django уникальные записи. docs.djangoproject.com/en/dev/faq/models/… Где вы проверяете результаты? В шаблоне или интерфейсе администратора или через командную строку?

2. @Carl F.: Это будет частью веб-сервера с Grok / Zope, поэтому я настраиваю «тестовую» страницу, где я открываю сеанс SQLAlchemy и запрашиваю этот класс TestSqlAlchemyList. Я начал думать, что такое поведение может быть ожидаемым для InstrumentedList (это не ошибка, это особенность): если я вставляю 4 строки с одинаковым отношением, а затем пытаюсь удалить одну, я получаю исключение, говорящее что-то вроде «one () ожидал 1 результат, найдено только 4», что означает… метод, используемый для поиска элементов в списке, — это SQLAlchemy .one(), поэтому InstrumentedList, кажется , подготовлен только для 1 записи.

Ответ №1:

Я думаю, что то, что вы хотите, описано в документации как «Дополнительные поля в отношениях «Многие ко многим»«. Вместо того, чтобы хранить уникальную строку в базе данных для каждой «ссылки» между attractLoop и Media, вы бы сохранили единственную ассоциацию и указали (как часть объектной модели link), сколько раз на нее ссылаются и / или в каком расположении (ах) в конечном списке должны отображаться медиа. Это парадигма, отличная от той, с которой вы начали, поэтому, безусловно, потребуется некоторое перекодирование, но я думаю, что это решает вашу проблему. Вероятно, вам потребуется использовать свойство, чтобы переопределить способ добавления или удаления мультимедиа из attactLoop.

Комментарии:

1. Спасибо за ваш ответ. Я думаю, что смогу заставить его работать со смесью этого дополнительного поля и скрыть «промежуточный» объект с помощью прокси-сервера ассоциации: sqlalchemy.org/docs/orm/extensions/associationproxy.html