#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