SQLAlchemy: гибридное выражение с отношением

У меня есть две модели Flask-SQLAlchemy с простым отношением «один ко многим», как в минимальном примере ниже:

class School(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30))
    address = db.Column(db.String(30))

class Teacher(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(30))
    id_school = db.Column(db.Integer, db.ForeignKey(School.id))

    school = relationship('School', backref='teachers')

Затем я добавляю к учителю гибридное свойство, которое использует отношение, например так:

@hybrid_property
def school_name(self):
    return self.school.name

И это свойство отлично работает, когда я использую его как teacher_instance.school_name. Однако я также хотел бы делать запросы типа Teacher.query.filter(Teacher.school_name == 'x'), но это дает мне ошибку:

`AttributeError: Neither 'InstrumentedAttribute' object nor 
'Comparator' object has an attribute 'school_name'`. 

Следуя документации SQLAlchemy, я добавил простое гибридное выражение, например следующее:

@school_name.expression
def school_name(cls):
    return School.name

Однако, когда я снова пытаюсь выполнить тот же запрос, он генерирует запрос SQL без предложения соединения, поэтому я получаю все доступные строки в School, а не только те, которые соответствуют внешнему ключу в Teacher.

Из документации SQLAlchemy я понял, что выражение ожидает контекст, в котором соединение уже присутствует, поэтому я повторил запрос как:

Teacher.query.join(School).filter(Teacher.school_name == 'x')

И это на самом деле работает, но это противоречит цели попытки получить синтаксический сахар в первую очередь, если мне нужно знание модели школы, чтобы получить это. Я ожидаю, что есть способ получить это соединение в выражении, но я нигде не мог его найти. В документации есть пример с выражением, возвращающим подзапрос, построенный непосредственно с помощью select(), но даже это у меня не сработало.

Любые идеи?

ОБНОВЛЕНИЕ

После ответа Eevee ниже я использовал прокси-сервер ассоциации, как было предложено, и он работает, но мне также стало любопытно, что он должен работать с подзапросом select(), и я попытался выяснить, что я сделал неправильно. Моя первоначальная попытка была:

@school_name.expression
def school_name(cls):
    return select(School.name).where(cls.id_school == School.id).as_scalar()

И оказалось, что это выдавало мне ошибку, потому что я пропустил список в select(). Код ниже работает нормально:

@school_name.expression
def school_name(cls):
    return select([School.name]).where(cls.id_school == School.id).as_scalar()

person Pedro Werneck    schedule 05.11.2013    source источник


Ответы (1)


Гораздо более простым подходом для такого простого случая является прокси-ассоциация:

class Teacher(db.Model):
    school_name = associationproxy('school', 'name')

Это поддерживает автоматический запрос (по крайней мере, с ==).

Мне любопытно, почему гибридный пример select() у вас не сработал, так как это самый простой способ исправить это в гибриде. И для завершения вы также можете использовать преобразователь для изменения запроса напрямую, а не в подзапросе.

person Eevee    schedule 05.11.2013
comment
Это до смешного просто. Большое спасибо. - person Pedro Werneck; 05.11.2013
comment
Вы были правы насчет select(). Моя ошибка. Я добавил обновление к вопросу с подробным описанием. - person Pedro Werneck; 05.11.2013