On Thu, Apr 26, 2018 at 11:52:48AM -0400, Mike Bayer wrote: > Attached is your script worked into separate tests. For test_five, > we can do it like this: > > > s.query(Document).filter(Document.translations.any(DocumentTranslation.body > == 'doc1_body_en')).all() > > For the tests which state "# XXX the current_translation isn't > joinedloaded()", it looks like they are, I added assertions that the > object is present without SQL being emitted; if I remove lazy="joined" > then it fails, so that joinedload is working. > > For the other general issue that a query with > Document/DocumentTranslation pulls in other tables, that's because the > relationship has no idea that Document only refers to > DocumentTranslation objects and nothing else. We can make these > relationships explicit. That's in the second attachment. For > test_three, the queries only involve the content/document tables and > there are no LEFT OUTER joins. > > For the content_type_id thing, I guess the trigger is so that you can > use raw SQL? SQLAlchemy will do those columns for you within the > ORM. But you can also set polymorphic_on to be a correlated > subquery. the third attachment shows that as well. > > In all three cases no changes were needed at all to the persistence side. > > The tests include a setup_class() which I don't know how to do in > unittest so I run these with py.test: > > py.test test_thing_3.py -k test_three -s >
Hi Mike, It works almost like a charm. One minor issue I have is in the test_six I've added in the attached test_thing_3.py. When I'm querying through the base class I'm wondering why SQLAlchemy issues a query for a property which is already lazy='joined' loaded in the previously query, and I wonder if there is way to avoid that .. ? Thanks ! Julien > > > > On Thu, Apr 26, 2018 at 6:54 AM, Julien Cigar <[email protected]> wrote: > > Hello Mike, > > > > I finally had the time to make a little full POC, see attached file. > > > > As said previously I'm used to the old mapper() and never used the > > declarative until now, so forgive me if I missed something in the > > script.. > > > > Some explanations on the context and on what I'd like to do: > > > > It's a $work-made CMS that is used in some projects here at $work. > > It is developed with the Pyramid framework which allow us to split > > in a very simple and clean way "subprojects" that use our core CMS > > package. > > > > At the core joinedload inheritance is used. "Content" is the base class > > from which other polymorphic entities (Event, Document, Folder, ...) > > inherits. > > > > The discriminator (polymorphic_on) is on a "content_type_id" column in > > the content table which is a foreign key to a content_type table. In my > > real app I use a function to retrieve the content_type.id, which is > > "faked" in my POC (_fake_ids). > > > > Every polymorphic entity has common properties/columns (like > > "title", "description") and specific properties/columns (like "body" for > > a Document, etc). Nothing special, common properties go in the Content > > class and specific properties in their own subclass. > > > > Now I'd like to add a "multilingual layer" to the app. Common properties > > will me moved to a dedicated content_translation table, and each > > polymorphic entity will have their own dedicated table > > (document_translation, event_translation, etc). See attached file > > (sa.png) for the db model. (Note that some entities may not have a > > dedicated _translation table, like Folder, which has no more than a > > "title" and "description") > > > > I'm not sure how to map this in SQLAlchemy as the "translation" parts > > (which are also mapped in a joinedload inheritance) are "part of" or > > "closely linked to" the translatable entity (ex: DocumentTranslation -> > > Document, EventTranslation -> Event, etc). > > > > In an ideal scenario I'd like to have a .translations property > > (relationship()) available in each entity which is lazy='subquery' > > loaded and for which only related _translation tables are JOIN. > > I'm not sure if I should add/overwrite the "translations" relationship > > in every entity (Event, Document, ...).. > > > > In advance, a big "thank you" for the time spent analyzing this :) > > > > Julien > > > > On Fri, Apr 13, 2018 at 10:34:46AM +0200, Julien Cigar wrote: > >> On Thu, Apr 12, 2018 at 08:25:06PM -0400, Mike Bayer wrote: > >> > try sending me a long a full POC and Ill try to play with it, any > >> > >> yep I'll do it ..! I miss some time for now.. but it's definitively on > >> my todo list. I don't think I have an overly complicated use case, it's > >> just that I pay a lot of attention on the generated SQL queries. > >> > >> > reason you arne't using declarative? the classical mappings are hard > >> > to work with. > >> > >> mostly for historical reasons, but also because I prefer the classical > >> mapping approach (mostly for separation of concerns). > >> > >> My application (internal CMS) is based on the Pyramid framework and it's > >> easier to extend the "core" package when the "mapping" part is separated > >> (maybe it's also possible with the declarative approach, I must admit > >> that I haven't looked at it in details). > >> > >> > > >> > On Wed, Apr 11, 2018 at 8:32 AM, Mike Bayer <[email protected]> > >> > wrote: > >> > > On Wed, Apr 11, 2018 at 6:15 AM, Julien Cigar <[email protected]> > >> > > wrote: > >> > >> On Tue, Apr 10, 2018 at 11:53:09AM -0400, Mike Bayer wrote: > >> > >>> On Tue, Apr 10, 2018 at 9:28 AM, Julien Cigar > >> > >>> <[email protected]> wrote: > >> > >>> > Hello, > >> > >>> > > >> > >>> > I wondered if it is possible to use a class mapped against multiple > >> > >>> > tables as a relationship() in another class? > >> > >>> > >> > >>> it is, there's examples at > >> > >>> http://docs.sqlalchemy.org/en/latest/orm/join_conditions.html#relationship-to-non-primary-mapper > >> > >>> > >> > >>> that is map your related class to whatever you want in a non primary > >> > >>> mapper. If you are just selecting data you have a lot of options. > >> > >>> > >> > >> > >> > >> Hi Mike, > >> > >> > >> > >> Thanks for the link :) I tried and it seems to work, except in an > >> > >> inheritance scenario (1). I guess that in a non primary mapper I can't > >> > >> put my polymorphic_on condition on a Join clause? > >> > >> > >> > >> (1) https://gist.github.com/silenius/a237baf8c4bcd79550dc884f2eeb1998 > >> > > > >> > > this is a lot of code to follow, but if the point of the mutli-table > >> > > thing you're doing is to game the joined inheritance into doing > >> > > something, then that's probably not the appropriate use for it. It > >> > > looks like you're trying to add all kinds of columns to > >> > > "ContentTranslation" from the other mappers like Content. > >> > > > >> > > > >> > >> > >> > >> Thanks! > >> > >> Julien > >> > >> > >> > >>> > >> > >>> > > >> > >>> > something like: > >> > >>> > > >> > >>> > ### Content > >> > >>> > > >> > >>> > content_translation_join = sql.join( > >> > >>> > t_content, t_content_translation > >> > >>> > ) > >> > >>> > > >> > >>> > orm.mapper( > >> > >>> > ContentTranslation, content_translation_join, > >> > >>> > polymorphic_on=t_content.c.content_type_id > >> > >>> > ) > >> > >>> > > >> > >>> > orm.mapper( > >> > >>> > Content, t_content, > >> > >>> > polymorphic_on=t_content.c.content_type_id, > >> > >>> > properties={ > >> > >>> > 'translations': orm.relationship( > >> > >>> > ContentTranslation, > >> > >>> > # more stuff here > >> > >>> > ) > >> > >>> > } > >> > >>> > ) > >> > >>> > > >> > >>> > ### Document > >> > >>> > > >> > >>> > orm.mapper( > >> > >>> > DocumentTranslation, t_document_translation, > >> > >>> > inherits=ContentTranslation, > >> > >>> > polymorphic_identity=some_id > >> > >>> > ) > >> > >>> > > >> > >>> > orm.mapper( > >> > >>> > Document, t_document, > >> > >>> > inherits=Content, > >> > >>> > polymorphic_identity=some_id > >> > >>> > ) > >> > >>> > > >> > >>> > I tried (1) but it doesn't seems to work so I want to be sure that > >> > >>> > it's > >> > >>> > not possible :) > >> > >>> > > >> > >>> > Thanks! > >> > >>> > Julien > >> > >>> > > >> > >>> > (1) > >> > >>> > https://gist.github.com/silenius/561b13f4b987c36434cd81e2c08cab6e#file-foo-py > >> > >>> > > >> > >>> > > >> > >>> > > >> > >>> > -- > >> > >>> > Julien Cigar > >> > >>> > Belgian Biodiversity Platform (http://www.biodiversity.be) > >> > >>> > PGP fingerprint: EEF9 F697 4B68 D275 7B11 6A25 B2BB 3710 A204 23C0 > >> > >>> > No trees were killed in the creation of this message. > >> > >>> > However, many electrons were terribly inconvenienced. > >> > >>> > > >> > >>> > -- > >> > >>> > SQLAlchemy - > >> > >>> > The Python SQL Toolkit and Object Relational Mapper > >> > >>> > > >> > >>> > http://www.sqlalchemy.org/ > >> > >>> > > >> > >>> > To post example code, please provide an MCVE: Minimal, Complete, > >> > >>> > and Verifiable Example. See http://stackoverflow.com/help/mcve > >> > >>> > for a full description. > >> > >>> > --- > >> > >>> > You received this message because you are subscribed to the Google > >> > >>> > Groups "sqlalchemy" group. > >> > >>> > To unsubscribe from this group and stop receiving emails from it, > >> > >>> > send an email to [email protected]. > >> > >>> > To post to this group, send email to [email protected]. > >> > >>> > Visit this group at https://groups.google.com/group/sqlalchemy. > >> > >>> > For more options, visit https://groups.google.com/d/optout. > >> > >>> > >> > >>> -- > >> > >>> SQLAlchemy - > >> > >>> The Python SQL Toolkit and Object Relational Mapper > >> > >>> > >> > >>> http://www.sqlalchemy.org/ > >> > >>> > >> > >>> To post example code, please provide an MCVE: Minimal, Complete, and > >> > >>> Verifiable Example. See http://stackoverflow.com/help/mcve for a > >> > >>> full description. > >> > >>> --- > >> > >>> You received this message because you are subscribed to the Google > >> > >>> Groups "sqlalchemy" group. > >> > >>> To unsubscribe from this group and stop receiving emails from it, > >> > >>> send an email to [email protected]. > >> > >>> To post to this group, send email to [email protected]. > >> > >>> Visit this group at https://groups.google.com/group/sqlalchemy. > >> > >>> For more options, visit https://groups.google.com/d/optout. > >> > >> > >> > >> -- > >> > >> Julien Cigar > >> > >> Belgian Biodiversity Platform (http://www.biodiversity.be) > >> > >> PGP fingerprint: EEF9 F697 4B68 D275 7B11 6A25 B2BB 3710 A204 23C0 > >> > >> No trees were killed in the creation of this message. > >> > >> However, many electrons were terribly inconvenienced. > >> > >> > >> > >> -- > >> > >> SQLAlchemy - > >> > >> The Python SQL Toolkit and Object Relational Mapper > >> > >> > >> > >> http://www.sqlalchemy.org/ > >> > >> > >> > >> To post example code, please provide an MCVE: Minimal, Complete, and > >> > >> Verifiable Example. See http://stackoverflow.com/help/mcve for a > >> > >> full description. > >> > >> --- > >> > >> You received this message because you are subscribed to the Google > >> > >> Groups "sqlalchemy" group. > >> > >> To unsubscribe from this group and stop receiving emails from it, > >> > >> send an email to [email protected]. > >> > >> To post to this group, send email to [email protected]. > >> > >> Visit this group at https://groups.google.com/group/sqlalchemy. > >> > >> For more options, visit https://groups.google.com/d/optout. > >> > > >> > -- > >> > SQLAlchemy - > >> > The Python SQL Toolkit and Object Relational Mapper > >> > > >> > http://www.sqlalchemy.org/ > >> > > >> > To post example code, please provide an MCVE: Minimal, Complete, and > >> > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > >> > description. > >> > --- > >> > You received this message because you are subscribed to the Google > >> > Groups "sqlalchemy" group. > >> > To unsubscribe from this group and stop receiving emails from it, send > >> > an email to [email protected]. > >> > To post to this group, send email to [email protected]. > >> > Visit this group at https://groups.google.com/group/sqlalchemy. > >> > For more options, visit https://groups.google.com/d/optout. > >> > >> -- > >> Julien Cigar > >> Belgian Biodiversity Platform (http://www.biodiversity.be) > >> PGP fingerprint: EEF9 F697 4B68 D275 7B11 6A25 B2BB 3710 A204 23C0 > >> No trees were killed in the creation of this message. > >> However, many electrons were terribly inconvenienced. > >> > >> -- > >> SQLAlchemy - > >> The Python SQL Toolkit and Object Relational Mapper > >> > >> http://www.sqlalchemy.org/ > >> > >> To post example code, please provide an MCVE: Minimal, Complete, and > >> Verifiable Example. See http://stackoverflow.com/help/mcve for a full > >> description. > >> --- > >> You received this message because you are subscribed to the Google Groups > >> "sqlalchemy" group. > >> To unsubscribe from this group and stop receiving emails from it, send an > >> email to [email protected]. > >> To post to this group, send email to [email protected]. > >> Visit this group at https://groups.google.com/group/sqlalchemy. > >> For more options, visit https://groups.google.com/d/optout. > > > > > > > > -- > > Julien Cigar > > Belgian Biodiversity Platform (http://www.biodiversity.be) > > PGP fingerprint: EEF9 F697 4B68 D275 7B11 6A25 B2BB 3710 A204 23C0 > > No trees were killed in the creation of this message. > > However, many electrons were terribly inconvenienced. > > > > -- > > SQLAlchemy - > > The Python SQL Toolkit and Object Relational Mapper > > > > http://www.sqlalchemy.org/ > > > > To post example code, please provide an MCVE: Minimal, Complete, and > > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > > description. > > --- > > You received this message because you are subscribed to the Google Groups > > "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send an > > email to [email protected]. > > To post to this group, send email to [email protected]. > > Visit this group at https://groups.google.com/group/sqlalchemy. > > For more options, visit https://groups.google.com/d/optout. > > -- > SQLAlchemy - > The Python SQL Toolkit and Object Relational Mapper > > http://www.sqlalchemy.org/ > > To post example code, please provide an MCVE: Minimal, Complete, and > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > description. > --- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to [email protected]. > To post to this group, send email to [email protected]. > Visit this group at https://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/d/optout. > import logging > > from sqlalchemy import * > from sqlalchemy.orm import * > from sqlalchemy.orm.collections import attribute_mapped_collection > from sqlalchemy.ext.declarative import declarative_base > from sqlalchemy.ext.declarative import declared_attr > from sqlalchemy.ext.hybrid import hybrid_property > import unittest > > Base = declarative_base() > > logging.basicConfig() > logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) > > _fake_ids = { > 'document': 1, > 'event': 2, > 'folder': 3 > } > > > def _get_locale(): > return 'en' > > > class ContentType(Base): > > __tablename__ = 'content_type' > > id = Column(Integer, primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, id, name): > self.id = id > self.name = name > > > class Language(Base): > > __tablename__ = 'language' > > id = Column(String(2), primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, id, name): > self.id = id > self.name = name > > > class Country(Base): > > __tablename__ = 'country' > > iso = Column(String(2), primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, iso, name): > self.iso = iso > self.name = name > > > class Content(Base): > > __tablename__ = 'content' > > # XXX: not supported by SQLite > #__table_args__ = ( > # Index( > # 'idx_container_id', > # text('((1)) where container_id is NULL') > # ), > #) > > id = Column(Integer, primary_key=True) > content_type_id = Column(Integer, ForeignKey('content_type.id'), > nullable=False) > container_id = Column(Integer, ForeignKey('folder.content_id')) > > parent = relationship( > lambda: Folder, > foreign_keys=lambda: Content.container_id, > innerjoin=True, > uselist=False, > backref=backref('children', cascade='all, delete-orphan') > ) > > current_translation = relationship( > lambda: ContentTranslation, > primaryjoin=lambda: and_( > ContentTranslation.content_id == Content.id, > ContentTranslation.language_id == bindparam( > None, > callable_=lambda: _get_locale(), > type_=String() > ) > ), > lazy='joined', > uselist=False, > innerjoin=True, > viewonly=True, > bake_queries=False, > back_populates='content' > ) > > translations = relationship( > lambda: ContentTranslation, > cascade='all, delete-orphan', > lazy='subquery', > innerjoin=True, > back_populates='content', > collection_class=attribute_mapped_collection('language_id') > ) > > content_type = relationship( > ContentType, > lazy='joined', > innerjoin=True, > uselist=False > ) > > __mapper_args__ = { > 'polymorphic_on': content_type_id > } > > @hybrid_property > def title(self): > return self.current_translation.title > > @title.setter > def title(self, value): > self.current_translation.title = value > > @hybrid_property > def description(self): > return self.current_translation.description > > @description.setter > def description(self, value): > self.current_translation.description = value > > > class ContentTranslation(Base): > > __tablename__ = 'content_translation' > > language_id = Column(String(2), ForeignKey('language.id'), > primary_key=True) > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > > title = Column(String, nullable=False) > description = Column(String) > > # XXX: In an ideal scenario I'd like to put the discriminator on the > # "content.content_type_id" column, something like; > # polymorphic_on = sql.join( > # content_translation, content, > # content_translation.c.content_id == content.c.id > # ).c.content_content_type_id > > # But as this is not supported by SQLAlchemy (yet?) I "duplicated" the > # "content_type_id" column and created a trigger which simply select the > # "content_type_id" in the "content" table for the corresponding > # "content_translation.content_id". So in the "real" app there is > something > # like: > > # create or replace function content_translation_content_id() > # returns trigger as $content_id$ > # begin > # NEW.content_type_id := ( > # select > # content_type_id > # from > # content > # where > # content.id = NEW.content_id > # ); > # > # return NEW; > # end; > # $content_id$ language plpgsql; > > # create trigger content_translation_content_id > # before insert or update on content_translation > # for each row execute procedure content_translation_content_id(); > > content_type_id = Column(Integer, ForeignKey('content_type.id'), > nullable=False) > > content = relationship( > Content, > back_populates='translations', > innerjoin=True, > uselist=False > ) > > content_type = relationship( > ContentType, > lazy='joined', > innerjoin=True, > uselist=False > ) > > __mapper_args__ = { > 'polymorphic_on': content_type_id > } > > def __init__(self, **kwargs): > if 'language_id' not in kwargs: > self.language_id = _get_locale() > > for k, v in kwargs.items(): > setattr(self, k, v) > > > class Folder(Content): > > __tablename__ = 'folder' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > some_folder_col = Column(Integer, nullable=True) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['folder'], > 'inherit_condition': content_id == Content.id > } > > def __init__(self, title, description=None, some_folder_col=None): > self.some_folder_col = some_folder_col > locale = _get_locale() > self.translations[locale] = FolderTranslation( > title=title, content_type_id=_fake_ids['folder'], > description=description > ) > > > class FolderTranslation(ContentTranslation): > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['folder'], > } > > > class Document(Content): > > __tablename__ = 'document' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['document'], > } > > def __init__(self, title, body, description=None): > locale = _get_locale() > self.translations[locale] = DocumentTranslation( > body=body, title=title, content_type_id=_fake_ids['document'], > description=description > ) > > @hybrid_property > def body(self): > return self.current_translation.body > > @body.setter > def body(self, value): > self.current_translation.body = value > > > class DocumentTranslation(ContentTranslation): > > __tablename__ = 'document_translation' > > language_id = Column(String(2), primary_key=True) > content_id = Column(Integer, ForeignKey('document.content_id'), > primary_key=True) > > body = Column(String, nullable=False) > > __table_args__ = ( > ForeignKeyConstraint( > ['language_id', 'content_id'], > ['content_translation.language_id', > 'content_translation.content_id'] > ), > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['document'], > 'polymorphic_load': 'inline' > } > > > class Event(Content): > > __tablename__ = 'event' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > country_iso = Column(String(2), ForeignKey('country.iso'), nullable=False) > > country = relationship( > Country, > innerjoin=True, > uselist=False, > backref='events', > lazy='joined' > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['event'], > } > > def __init__(self, title, country_iso, description=None, > some_event_col1=None, > some_event_col2=None): > locale = _get_locale() > self.country_iso = country_iso > self.translations[locale] = EventTranslation( > title=title, content_type_id=_fake_ids['event'], > some_event_col1=some_event_col1, some_event_col2=some_event_col2 > ) > > > class EventTranslation(ContentTranslation): > > __tablename__ = 'event_translation' > > language_id = Column(String(2), primary_key=True) > content_id = Column(Integer, ForeignKey('event.content_id'), > primary_key=True) > > some_event_col1 = Column(String()) > some_event_col2 = Column(String()) > > __table_args__ = ( > ForeignKeyConstraint( > ['language_id', 'content_id'], > ['content_translation.language_id', > 'content_translation.content_id'] > ), > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['event'], > 'polymorphic_load': 'inline' > } > > > e = create_engine("sqlite://", echo=False) > > Base.metadata.create_all(e) > > > class FooTest(unittest.TestCase): > @classmethod > def setup_class(cls): > s = Session(e) > > for name, fake_id in _fake_ids.items(): > s.add(ContentType(fake_id, name)) > > s.add(Country('be', 'Belgium')) > s.add(Country('fr', 'France')) > > s.add(Language('fr', 'Français')) > s.add(Language('en', 'English')) > > root = Folder('root') > s.add(root) > > subroot = Folder('subroot') > root.children.append(subroot) > > doc1 = Document('doc1_title_en', 'doc1_body_en') > doc2 = Document('doc2_title_en', 'doc2_body_en') > subroot.children.append(doc1) > subroot.children.append(doc2) > > s.flush() > > cls.doc1_id = doc1.id > > trans_fr = DocumentTranslation( > language_id='fr', > title='doc1_title_fr', > description='doc1_description_fr', > body='doc1_body_fr' > ) > doc1.translations['fr'] = trans_fr > > evt1 = Event('Event1', 'be', some_event_col1='sometext') > evt2 = Event('Event2', 'be', some_event_col1='sometext', > some_event_col2="blah") > evt3 = Event('Event3', 'fr', some_event_col2='sometext') > subroot.children.append(evt1) > subroot.children.append(evt2) > subroot.children.append(evt3) > s.commit() > > def test_one(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > > s.close() > assert foo.current_translation > > def test_two(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > > s.close() > assert foo.current_translation > assert foo.translations > > def test_three(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > > # .body is specific to document_translation > # without polymorphic_load='inline' a query is made .. in practice > # with the lazy='subquery' on the "translations" relationship it > # should be the case however, with polymorphic_load='inline' the > #table is always joined, even for non-Document entities > > s.close() > assert foo.current_translation > assert foo.translations > > foo.translations['en'].body > > def test_four(self): > s = Session(e) > > s.query(Document).filter(Document.translations.any(language_id='fr')).all() > > def test_five(self): > s = Session(e) > # XXX should work, but fail, as .body is specific to > DocumentTranslation > > s.query(Document).filter(Document.translations.any(DocumentTranslation.body > == 'doc1_body_en')).all() > import logging > > from sqlalchemy import * > from sqlalchemy.orm import * > from sqlalchemy.orm.collections import attribute_mapped_collection > from sqlalchemy.ext.declarative import declarative_base > from sqlalchemy.ext.declarative import declared_attr > from sqlalchemy.ext.hybrid import hybrid_property > import unittest > > Base = declarative_base() > > logging.basicConfig() > logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) > > _fake_ids = { > 'document': 1, > 'event': 2, > 'folder': 3 > } > > > def _get_locale(): > return 'en' > > > class ContentType(Base): > > __tablename__ = 'content_type' > > id = Column(Integer, primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, id, name): > self.id = id > self.name = name > > > class Language(Base): > > __tablename__ = 'language' > > id = Column(String(2), primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, id, name): > self.id = id > self.name = name > > > class Country(Base): > > __tablename__ = 'country' > > iso = Column(String(2), primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, iso, name): > self.iso = iso > self.name = name > > > def _setup_translation(content_cls, translation_cls): > content_cls.current_translation = relationship( > lambda: translation_cls, > primaryjoin=lambda: and_( > foreign(translation_cls.content_id) == Content.id, > translation_cls.language_id == bindparam( > None, > callable_=lambda: _get_locale(), > type_=String() > ) > ), > lazy='joined', > uselist=False, > innerjoin=True, > viewonly=True, > bake_queries=False, > back_populates='content' > ) > > content_cls.translations = relationship( > lambda: translation_cls, > cascade='all, delete-orphan', > lazy='subquery', > innerjoin=True, > back_populates='content', > collection_class=attribute_mapped_collection('language_id') > ) > > translation_cls.content = relationship( > content_cls, > back_populates='translations', > innerjoin=True, > uselist=False > ) > > > class Content(Base): > > __tablename__ = 'content' > > id = Column(Integer, primary_key=True) > content_type_id = Column(Integer, ForeignKey('content_type.id'), > nullable=False) > container_id = Column(Integer, ForeignKey('folder.content_id')) > > parent = relationship( > lambda: Folder, > foreign_keys=lambda: Content.container_id, > innerjoin=True, > uselist=False, > backref=backref('children', cascade='all, delete-orphan') > ) > > content_type = relationship( > ContentType, > lazy='joined', > innerjoin=True, > uselist=False > ) > > __mapper_args__ = { > 'polymorphic_on': content_type_id > } > > @hybrid_property > def title(self): > return self.current_translation.title > > @title.setter > def title(self, value): > self.current_translation.title = value > > @hybrid_property > def description(self): > return self.current_translation.description > > @description.setter > def description(self, value): > self.current_translation.description = value > > > class ContentTranslation(Base): > > __tablename__ = 'content_translation' > > language_id = Column(String(2), ForeignKey('language.id'), > primary_key=True) > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > > title = Column(String, nullable=False) > description = Column(String) > > # XXX: In an ideal scenario I'd like to put the discriminator on the > # "content.content_type_id" column, something like; > # polymorphic_on = sql.join( > # content_translation, content, > # content_translation.c.content_id == content.c.id > # ).c.content_content_type_id > > # But as this is not supported by SQLAlchemy (yet?) I "duplicated" the > # "content_type_id" column and created a trigger which simply select the > # "content_type_id" in the "content" table for the corresponding > # "content_translation.content_id". So in the "real" app there is > something > # like: > > # create or replace function content_translation_content_id() > # returns trigger as $content_id$ > # begin > # NEW.content_type_id := ( > # select > # content_type_id > # from > # content > # where > # content.id = NEW.content_id > # ); > # > # return NEW; > # end; > # $content_id$ language plpgsql; > > # create trigger content_translation_content_id > # before insert or update on content_translation > # for each row execute procedure content_translation_content_id(); > > content_type_id = Column(Integer, ForeignKey('content_type.id'), > nullable=False) > > content_type = relationship( > ContentType, > lazy='joined', > innerjoin=True, > uselist=False > ) > > __mapper_args__ = { > 'polymorphic_on': content_type_id > } > > def __init__(self, **kwargs): > if 'language_id' not in kwargs: > self.language_id = _get_locale() > > for k, v in kwargs.items(): > setattr(self, k, v) > > > class Folder(Content): > > __tablename__ = 'folder' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > some_folder_col = Column(Integer, nullable=True) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['folder'], > 'inherit_condition': content_id == Content.id > } > > def __init__(self, title, description=None, some_folder_col=None): > self.some_folder_col = some_folder_col > locale = _get_locale() > self.translations[locale] = FolderTranslation( > title=title, content_type_id=_fake_ids['folder'], > description=description > ) > > > class FolderTranslation(ContentTranslation): > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['folder'], > } > > > class Document(Content): > > __tablename__ = 'document' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['document'], > } > > def __init__(self, title, body, description=None): > locale = _get_locale() > self.translations[locale] = DocumentTranslation( > body=body, title=title, content_type_id=_fake_ids['document'], > description=description > ) > > @hybrid_property > def body(self): > return self.current_translation.body > > @body.setter > def body(self, value): > self.current_translation.body = value > > > class DocumentTranslation(ContentTranslation): > > __tablename__ = 'document_translation' > > language_id = Column(String(2), primary_key=True) > content_id = Column(Integer, ForeignKey('document.content_id'), > primary_key=True) > > body = Column(String, nullable=False) > > __table_args__ = ( > ForeignKeyConstraint( > ['language_id', 'content_id'], > ['content_translation.language_id', > 'content_translation.content_id'] > ), > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['document'], > 'polymorphic_load': 'inline' > } > > > class Event(Content): > > __tablename__ = 'event' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > country_iso = Column(String(2), ForeignKey('country.iso'), nullable=False) > > country = relationship( > Country, > innerjoin=True, > uselist=False, > backref='events', > lazy='joined' > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['event'], > } > > def __init__(self, title, country_iso, description=None, > some_event_col1=None, > some_event_col2=None): > locale = _get_locale() > self.country_iso = country_iso > self.translations[locale] = EventTranslation( > title=title, content_type_id=_fake_ids['event'], > some_event_col1=some_event_col1, some_event_col2=some_event_col2 > ) > > > class EventTranslation(ContentTranslation): > > __tablename__ = 'event_translation' > > language_id = Column(String(2), primary_key=True) > content_id = Column(Integer, ForeignKey('event.content_id'), > primary_key=True) > > some_event_col1 = Column(String()) > some_event_col2 = Column(String()) > > __table_args__ = ( > ForeignKeyConstraint( > ['language_id', 'content_id'], > ['content_translation.language_id', > 'content_translation.content_id'] > ), > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['event'], > 'polymorphic_load': 'inline' > } > > _setup_translation(Content, ContentTranslation) > _setup_translation(Folder, FolderTranslation) > _setup_translation(Document, DocumentTranslation) > _setup_translation(Event, EventTranslation) > > > e = create_engine("sqlite://", echo=False) > > Base.metadata.create_all(e) > > > class FooTest(unittest.TestCase): > @classmethod > def setup_class(cls): > s = Session(e) > > for name, fake_id in _fake_ids.items(): > s.add(ContentType(fake_id, name)) > > s.add(Country('be', 'Belgium')) > s.add(Country('fr', 'France')) > s.flush() > > s.add(Language('fr', 'Français')) > s.add(Language('en', 'English')) > s.flush() > > root = Folder('root') > s.add(root) > s.flush() > > subroot = Folder('subroot') > root.children.append(subroot) > s.flush() > > doc1 = Document('doc1_title_en', 'doc1_body_en') > doc2 = Document('doc2_title_en', 'doc2_body_en') > subroot.children.append(doc1) > subroot.children.append(doc2) > s.flush() > > cls.doc1_id = doc1.id > > trans_fr = DocumentTranslation( > language_id='fr', > title='doc1_title_fr', > description='doc1_description_fr', > body='doc1_body_fr' > ) > doc1.translations['fr'] = trans_fr > s.flush() > > evt1 = Event('Event1', 'be', some_event_col1='sometext') > evt2 = Event('Event2', 'be', some_event_col1='sometext', > some_event_col2="blah") > evt3 = Event('Event3', 'fr', some_event_col2='sometext') > subroot.children.append(evt1) > subroot.children.append(evt2) > subroot.children.append(evt3) > s.commit() > > def test_one(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > > s.close() > assert foo.current_translation > > def test_two(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > foo.translations > > def test_three(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > > # .body is specific to document_translation > # without polymorphic_load='inline' a query is made .. in practice > # with the lazy='subquery' on the "translations" relationship it > # should be the case however, with polymorphic_load='inline' the > #table is always joined, even for non-Document entities > > foo.translations['en'].body > > def test_four(self): > s = Session(e) > > s.query(Document).filter(Document.translations.any(language_id='fr')).all() > > def test_five(self): > s = Session(e) > # XXX should work, but fail, as .body is specific to > DocumentTranslation > > s.query(Document).filter(Document.translations.any(body='doc1_body_en')).all() > import logging > > from sqlalchemy import * > from sqlalchemy.orm import * > from sqlalchemy.orm.collections import attribute_mapped_collection > from sqlalchemy.ext.declarative import declarative_base > from sqlalchemy.ext.declarative import declared_attr > from sqlalchemy.ext.associationproxy import association_proxy > from sqlalchemy.ext.hybrid import hybrid_property > import unittest > > Base = declarative_base() > > logging.basicConfig() > logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO) > > _fake_ids = { > 'document': 1, > 'event': 2, > 'folder': 3 > } > > > def _get_locale(): > return 'en' > > > class ContentType(Base): > > __tablename__ = 'content_type' > > id = Column(Integer, primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, id, name): > self.id = id > self.name = name > > > class Language(Base): > > __tablename__ = 'language' > > id = Column(String(2), primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, id, name): > self.id = id > self.name = name > > > class Country(Base): > > __tablename__ = 'country' > > iso = Column(String(2), primary_key=True) > name = Column(String(50), nullable=False) > > def __init__(self, iso, name): > self.iso = iso > self.name = name > > > def _setup_translation(content_cls, translation_cls): > content_cls.current_translation = relationship( > lambda: translation_cls, > primaryjoin=lambda: and_( > foreign(translation_cls.content_id) == Content.id, > translation_cls.language_id == bindparam( > None, > callable_=lambda: _get_locale(), > type_=String() > ) > ), > lazy='joined', > uselist=False, > innerjoin=True, > viewonly=True, > bake_queries=False, > back_populates='content' > ) > > content_cls.translations = relationship( > lambda: translation_cls, > cascade='all, delete-orphan', > lazy='subquery', > innerjoin=True, > back_populates='content', > collection_class=attribute_mapped_collection('language_id') > ) > > translation_cls.content = relationship( > content_cls, > back_populates='translations', > innerjoin=True, > uselist=False > ) > > > class Content(Base): > > __tablename__ = 'content' > > id = Column(Integer, primary_key=True) > content_type_id = Column(Integer, ForeignKey('content_type.id'), > nullable=False) > container_id = Column(Integer, ForeignKey('folder.content_id')) > > parent = relationship( > lambda: Folder, > foreign_keys=lambda: Content.container_id, > innerjoin=True, > uselist=False, > backref=backref('children', cascade='all, delete-orphan') > ) > > content_type = relationship( > ContentType, > lazy='joined', > innerjoin=True, > uselist=False > ) > > __mapper_args__ = { > 'polymorphic_on': content_type_id > } > > @hybrid_property > def title(self): > return self.current_translation.title > > @title.setter > def title(self, value): > self.current_translation.title = value > > @hybrid_property > def description(self): > return self.current_translation.description > > @description.setter > def description(self, value): > self.current_translation.description = value > > > class ContentTranslation(Base): > > __tablename__ = 'content_translation' > > language_id = Column(String(2), ForeignKey('language.id'), > primary_key=True) > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > > title = Column(String, nullable=False) > description = Column(String) > > content_type = association_proxy("content", "content_type") > > __mapper_args__ = { > 'polymorphic_on': > select([Content.content_type_id]).where(content_id == > Content.id).\ > correlate_except(Content).as_scalar() > } > > def __init__(self, **kwargs): > if 'language_id' not in kwargs: > self.language_id = _get_locale() > > for k, v in kwargs.items(): > setattr(self, k, v) > > > class Folder(Content): > > __tablename__ = 'folder' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > some_folder_col = Column(Integer, nullable=True) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['folder'], > 'inherit_condition': content_id == Content.id > } > > def __init__(self, title, description=None, some_folder_col=None): > self.some_folder_col = some_folder_col > locale = _get_locale() > self.translations[locale] = FolderTranslation( > title=title, content_type_id=_fake_ids['folder'], > description=description > ) > > > class FolderTranslation(ContentTranslation): > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['folder'], > } > > > class Document(Content): > > __tablename__ = 'document' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['document'], > } > > def __init__(self, title, body, description=None): > locale = _get_locale() > self.translations[locale] = DocumentTranslation( > body=body, title=title, content_type_id=_fake_ids['document'], > description=description > ) > > @hybrid_property > def body(self): > return self.current_translation.body > > @body.setter > def body(self, value): > self.current_translation.body = value > > > class DocumentTranslation(ContentTranslation): > > __tablename__ = 'document_translation' > > language_id = Column(String(2), primary_key=True) > content_id = Column(Integer, ForeignKey('document.content_id'), > primary_key=True) > > body = Column(String, nullable=False) > > __table_args__ = ( > ForeignKeyConstraint( > ['language_id', 'content_id'], > ['content_translation.language_id', > 'content_translation.content_id'] > ), > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['document'], > 'polymorphic_load': 'inline' > } > > > class Event(Content): > > __tablename__ = 'event' > > content_id = Column(Integer, ForeignKey('content.id'), primary_key=True) > country_iso = Column(String(2), ForeignKey('country.iso'), nullable=False) > > country = relationship( > Country, > innerjoin=True, > uselist=False, > backref='events', > lazy='joined' > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['event'], > } > > def __init__(self, title, country_iso, description=None, > some_event_col1=None, > some_event_col2=None): > locale = _get_locale() > self.country_iso = country_iso > self.translations[locale] = EventTranslation( > title=title, content_type_id=_fake_ids['event'], > some_event_col1=some_event_col1, some_event_col2=some_event_col2 > ) > > > class EventTranslation(ContentTranslation): > > __tablename__ = 'event_translation' > > language_id = Column(String(2), primary_key=True) > content_id = Column(Integer, ForeignKey('event.content_id'), > primary_key=True) > > some_event_col1 = Column(String()) > some_event_col2 = Column(String()) > > __table_args__ = ( > ForeignKeyConstraint( > ['language_id', 'content_id'], > ['content_translation.language_id', > 'content_translation.content_id'] > ), > ) > > __mapper_args__ = { > 'polymorphic_identity': _fake_ids['event'], > 'polymorphic_load': 'inline' > } > > _setup_translation(Content, ContentTranslation) > _setup_translation(Folder, FolderTranslation) > _setup_translation(Document, DocumentTranslation) > _setup_translation(Event, EventTranslation) > > > e = create_engine("sqlite://", echo=False) > > Base.metadata.create_all(e) > > > class FooTest(unittest.TestCase): > @classmethod > def setup_class(cls): > s = Session(e) > > for name, fake_id in _fake_ids.items(): > s.add(ContentType(fake_id, name)) > > s.add(Country('be', 'Belgium')) > s.add(Country('fr', 'France')) > s.flush() > > s.add(Language('fr', 'Français')) > s.add(Language('en', 'English')) > s.flush() > > root = Folder('root') > s.add(root) > s.flush() > > subroot = Folder('subroot') > root.children.append(subroot) > s.flush() > > doc1 = Document('doc1_title_en', 'doc1_body_en') > doc2 = Document('doc2_title_en', 'doc2_body_en') > subroot.children.append(doc1) > subroot.children.append(doc2) > s.flush() > > cls.doc1_id = doc1.id > > trans_fr = DocumentTranslation( > language_id='fr', > title='doc1_title_fr', > description='doc1_description_fr', > body='doc1_body_fr' > ) > doc1.translations['fr'] = trans_fr > s.flush() > > evt1 = Event('Event1', 'be', some_event_col1='sometext') > evt2 = Event('Event2', 'be', some_event_col1='sometext', > some_event_col2="blah") > evt3 = Event('Event3', 'fr', some_event_col2='sometext') > subroot.children.append(evt1) > subroot.children.append(evt2) > subroot.children.append(evt3) > s.commit() > > def test_one(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > > s.close() > assert foo.current_translation > > def test_two(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > foo.translations > > def test_three(self): > s = Session(e) > foo = s.query(Document).get(self.doc1_id) # XXX the > current_translation isn't joinedloaded() > > # .body is specific to document_translation > # without polymorphic_load='inline' a query is made .. in practice > # with the lazy='subquery' on the "translations" relationship it > # should be the case however, with polymorphic_load='inline' the > #table is always joined, even for non-Document entities > > foo.translations['en'].body > > def test_four(self): > s = Session(e) > > s.query(Document).filter(Document.translations.any(language_id='fr')).all() > > def test_five(self): > s = Session(e) > # XXX should work, but fail, as .body is specific to > DocumentTranslation > > s.query(Document).filter(Document.translations.any(body='doc1_body_en')).all() -- Julien Cigar Belgian Biodiversity Platform (http://www.biodiversity.be) PGP fingerprint: EEF9 F697 4B68 D275 7B11 6A25 B2BB 3710 A204 23C0 No trees were killed in the creation of this message. However, many electrons were terribly inconvenienced. -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.
import logging
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.orm.collections import attribute_mapped_collection
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.hybrid import hybrid_property
import unittest
Base = declarative_base()
logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
_fake_ids = {
'document': 1,
'event': 2,
'folder': 3
}
def _get_locale():
return 'en'
class ContentType(Base):
__tablename__ = 'content_type'
id = Column(Integer, primary_key=True)
name = Column(String(50), nullable=False)
def __init__(self, id, name):
self.id = id
self.name = name
class Language(Base):
__tablename__ = 'language'
id = Column(String(2), primary_key=True)
name = Column(String(50), nullable=False)
def __init__(self, id, name):
self.id = id
self.name = name
class Country(Base):
__tablename__ = 'country'
iso = Column(String(2), primary_key=True)
name = Column(String(50), nullable=False)
def __init__(self, iso, name):
self.iso = iso
self.name = name
def _setup_translation(content_cls, translation_cls):
content_cls.current_translation = relationship(
lambda: translation_cls,
primaryjoin=lambda: and_(
foreign(translation_cls.content_id) == Content.id,
translation_cls.language_id == bindparam(
None,
callable_=lambda: _get_locale(),
type_=String()
)
),
lazy='joined',
uselist=False,
innerjoin=True,
viewonly=True,
bake_queries=False,
back_populates='content'
)
content_cls.translations = relationship(
lambda: translation_cls,
cascade='all, delete-orphan',
lazy='subquery',
innerjoin=True,
back_populates='content',
collection_class=attribute_mapped_collection('language_id')
)
translation_cls.content = relationship(
content_cls,
back_populates='translations',
innerjoin=True,
uselist=False
)
class Content(Base):
__tablename__ = 'content'
id = Column(Integer, primary_key=True)
content_type_id = Column(Integer, ForeignKey('content_type.id'),
nullable=False)
container_id = Column(Integer, ForeignKey('folder.content_id'))
parent = relationship(
lambda: Folder,
foreign_keys=lambda: Content.container_id,
innerjoin=True,
uselist=False,
backref=backref('children', cascade='all, delete-orphan')
)
content_type = relationship(
ContentType,
lazy='joined',
innerjoin=True,
uselist=False
)
__mapper_args__ = {
'polymorphic_on': content_type_id
}
@hybrid_property
def title(self):
return self.current_translation.title
@title.setter
def title(self, value):
self.current_translation.title = value
@hybrid_property
def description(self):
return self.current_translation.description
@description.setter
def description(self, value):
self.current_translation.description = value
class ContentTranslation(Base):
__tablename__ = 'content_translation'
language_id = Column(String(2), ForeignKey('language.id'),
primary_key=True)
content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
title = Column(String, nullable=False)
description = Column(String)
content_type = association_proxy("content", "content_type")
__mapper_args__ = {
'polymorphic_on':
select([Content.content_type_id]).where(content_id == Content.id).\
correlate_except(Content).as_scalar()
}
def __init__(self, **kwargs):
if 'language_id' not in kwargs:
self.language_id = _get_locale()
for k, v in kwargs.items():
setattr(self, k, v)
class Folder(Content):
__tablename__ = 'folder'
content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
some_folder_col = Column(Integer, nullable=True)
__mapper_args__ = {
'polymorphic_identity': _fake_ids['folder'],
'inherit_condition': content_id == Content.id
}
def __init__(self, title, description=None, some_folder_col=None):
self.some_folder_col = some_folder_col
locale = _get_locale()
self.translations[locale] = FolderTranslation(
title=title, content_type_id=_fake_ids['folder'],
description=description
)
class FolderTranslation(ContentTranslation):
__mapper_args__ = {
'polymorphic_identity': _fake_ids['folder'],
}
class Document(Content):
__tablename__ = 'document'
content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': _fake_ids['document'],
}
def __init__(self, title, body, description=None):
locale = _get_locale()
self.translations[locale] = DocumentTranslation(
body=body, title=title, content_type_id=_fake_ids['document'],
description=description
)
@hybrid_property
def body(self):
return self.current_translation.body
@body.setter
def body(self, value):
self.current_translation.body = value
class DocumentTranslation(ContentTranslation):
__tablename__ = 'document_translation'
language_id = Column(String(2), primary_key=True)
content_id = Column(Integer, ForeignKey('document.content_id'),
primary_key=True)
body = Column(String, nullable=False)
__table_args__ = (
ForeignKeyConstraint(
['language_id', 'content_id'],
['content_translation.language_id',
'content_translation.content_id']
),
)
__mapper_args__ = {
'polymorphic_identity': _fake_ids['document'],
'polymorphic_load': 'inline'
}
class Event(Content):
__tablename__ = 'event'
content_id = Column(Integer, ForeignKey('content.id'), primary_key=True)
country_iso = Column(String(2), ForeignKey('country.iso'), nullable=False)
country = relationship(
Country,
innerjoin=True,
uselist=False,
backref='events',
lazy='joined'
)
__mapper_args__ = {
'polymorphic_identity': _fake_ids['event'],
}
def __init__(self, title, country_iso, description=None,
some_event_col1=None,
some_event_col2=None):
locale = _get_locale()
self.country_iso = country_iso
self.translations[locale] = EventTranslation(
title=title, content_type_id=_fake_ids['event'],
some_event_col1=some_event_col1, some_event_col2=some_event_col2
)
class EventTranslation(ContentTranslation):
__tablename__ = 'event_translation'
language_id = Column(String(2), primary_key=True)
content_id = Column(Integer, ForeignKey('event.content_id'),
primary_key=True)
some_event_col1 = Column(String())
some_event_col2 = Column(String())
__table_args__ = (
ForeignKeyConstraint(
['language_id', 'content_id'],
['content_translation.language_id',
'content_translation.content_id']
),
)
__mapper_args__ = {
'polymorphic_identity': _fake_ids['event'],
'polymorphic_load': 'inline'
}
_setup_translation(Content, ContentTranslation)
_setup_translation(Folder, FolderTranslation)
_setup_translation(Document, DocumentTranslation)
_setup_translation(Event, EventTranslation)
e = create_engine("sqlite://", echo=False)
Base.metadata.create_all(e)
class FooTest(unittest.TestCase):
@classmethod
def setup_class(cls):
s = Session(e)
for name, fake_id in _fake_ids.items():
s.add(ContentType(fake_id, name))
s.add(Country('be', 'Belgium'))
s.add(Country('fr', 'France'))
s.flush()
s.add(Language('fr', 'Français'))
s.add(Language('en', 'English'))
s.flush()
root = Folder('root')
s.add(root)
s.flush()
subroot = Folder('subroot')
root.children.append(subroot)
s.flush()
doc1 = Document('doc1_title_en', 'doc1_body_en')
doc2 = Document('doc2_title_en', 'doc2_body_en')
subroot.children.append(doc1)
subroot.children.append(doc2)
s.flush()
cls.doc1_id = doc1.id
trans_fr = DocumentTranslation(
language_id='fr',
title='doc1_title_fr',
description='doc1_description_fr',
body='doc1_body_fr'
)
doc1.translations['fr'] = trans_fr
s.flush()
evt1 = Event('Event1', 'be', some_event_col1='sometext')
evt2 = Event('Event2', 'be', some_event_col1='sometext',
some_event_col2="blah")
evt3 = Event('Event3', 'fr', some_event_col2='sometext')
subroot.children.append(evt1)
subroot.children.append(evt2)
subroot.children.append(evt3)
s.commit()
def test_one(self):
s = Session(e)
foo = s.query(Document).get(self.doc1_id) # XXX the current_translation isn't joinedloaded()
s.close()
assert foo.current_translation
def test_two(self):
s = Session(e)
foo = s.query(Document).get(self.doc1_id) # XXX the current_translation isn't joinedloaded()
foo.translations
def test_three(self):
s = Session(e)
foo = s.query(Document).get(self.doc1_id) # XXX the current_translation isn't joinedloaded()
# .body is specific to document_translation
# without polymorphic_load='inline' a query is made .. in practice
# with the lazy='subquery' on the "translations" relationship it
# should be the case however, with polymorphic_load='inline' the
#table is always joined, even for non-Document entities
foo.translations['en'].body
def test_four(self):
s = Session(e)
s.query(Document).filter(Document.translations.any(language_id='fr')).all()
def test_five(self):
s = Session(e)
# XXX should work, but fail, as .body is specific to DocumentTranslation
s.query(Document).filter(Document.translations.any(body='doc1_body_en')).all()
def test_six(self):
s = Session(e)
a = s.query(Content).all() # note: current_translation is joinedload
[_.title for _ in a]
signature.asc
Description: PGP signature
