From 55ad2dc22a715bf38e070a5566e57e6228eaa0d7 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 7 Mar 2006 03:14:08 +0000 Subject: [PATCH] added backref() function, allows the creation of a backref where you also send keyword arguments that will be placed on the relation --- examples/backref/backref_tree.py | 41 ++++++++++++++++++++++ lib/sqlalchemy/mapping/__init__.py | 7 +++- lib/sqlalchemy/mapping/properties.py | 51 ++++++++++++++++++---------- 3 files changed, 80 insertions(+), 19 deletions(-) create mode 100644 examples/backref/backref_tree.py diff --git a/examples/backref/backref_tree.py b/examples/backref/backref_tree.py new file mode 100644 index 0000000000..09598069d4 --- /dev/null +++ b/examples/backref/backref_tree.py @@ -0,0 +1,41 @@ +from sqlalchemy import * +import sqlalchemy.attributes as attributes + +engine = create_engine('sqlite://', echo=True) + +class Tree(object): + def __init__(self, name='', father=None): + self.name = name + self.father = father + def __str__(self): + return '' % self.name + def __repr__(self): + return self.__str__() + +table = Table('tree', engine, + Column('id', Integer, primary_key=True), + Column('name', String(64), nullable=False), + Column('father_id', Integer, ForeignKey('tree.id'), nullable=True),) + +assign_mapper(Tree, table, + properties={ + # set up a backref using a string + #'father':relation(Tree, foreignkey=table.c.id,primaryjoin=table.c.father_id==table.c.id, backref='childs')}, + + # or set up using the backref() function, which allows arguments to be passed + 'childs':relation(Tree, foreignkey=table.c.father_id, primaryjoin=table.c.father_id==table.c.id, backref=backref('father', uselist=False, foreignkey=table.c.id))}, + ) + +table.create() +root = Tree('root') +child1 = Tree('child1', root) +child2 = Tree('child2', root) +child3 = Tree('child3', child1) + +objectstore.commit() + +print root.childs +print child1.childs +print child2.childs +print child2.father +print child3.father diff --git a/lib/sqlalchemy/mapping/__init__.py b/lib/sqlalchemy/mapping/__init__.py index 895b8b642c..e694d2917a 100644 --- a/lib/sqlalchemy/mapping/__init__.py +++ b/lib/sqlalchemy/mapping/__init__.py @@ -19,7 +19,7 @@ from mapper import * from properties import * import mapper as mapperlib -__all__ = ['relation', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', +__all__ = ['relation', 'backref', 'eagerload', 'lazyload', 'noload', 'deferred', 'defer', 'undefer', 'mapper', 'clear_mappers', 'objectstore', 'sql', 'extension', 'class_mapper', 'object_mapper', 'MapperExtension', 'assign_mapper', 'cascade_mappers' ] @@ -39,7 +39,12 @@ def _relation_loader(mapper, secondary=None, primaryjoin=None, secondaryjoin=Non else: return EagerLoader(mapper, secondary, primaryjoin, secondaryjoin, **kwargs) +def backref(name, **kwargs): + return BackRef(name, **kwargs) + def deferred(*columns, **kwargs): + """returns a DeferredColumnProperty, which indicates this object attributes should only be loaded + from its corresponding table column when first accessed.""" return DeferredColumnProperty(*columns, **kwargs) def mapper(class_, table=None, *args, **params): diff --git a/lib/sqlalchemy/mapping/properties.py b/lib/sqlalchemy/mapping/properties.py index 617f3bbaf7..6ca0a7641b 100644 --- a/lib/sqlalchemy/mapping/properties.py +++ b/lib/sqlalchemy/mapping/properties.py @@ -136,7 +136,10 @@ class PropertyLoader(MapperProperty): print "'use_alias' argument to relation() is deprecated. eager loads automatically alias-ize tables now." self.order_by = order_by self.attributeext=attributeext - self.backref = backref + if isinstance(backref, str): + self.backref = BackRef(backref) + else: + self.backref = backref self.is_backref = is_backref def copy(self): @@ -197,27 +200,13 @@ class PropertyLoader(MapperProperty): # if a backref name is defined, set up an extension to populate # attributes in the other direction if self.backref is not None: - self.attributeext = attributes.GenericBackrefExtension(self.backref) + self.attributeext = self.backref.get_extension() # set our class attribute self._set_class_attribute(parent.class_, key) if self.backref is not None: - # try to set a LazyLoader on our mapper referencing the parent mapper - if not self.mapper.props.has_key(self.backref): - if self.secondaryjoin is not None: - # if setting up a backref to a many-to-many, reverse the order - # of the "primary" and "secondary" joins - pj = self.secondaryjoin - sj = self.primaryjoin - else: - pj = self.primaryjoin - sj = None - self.mapper.add_property(self.backref, LazyLoader(self.parent, self.secondary, pj, sj, backref=self.key, is_backref=True)); - else: - # else set one of us as the "backreference" - if not self.mapper.props[self.backref].is_backref: - self.is_backref=True + self.backref.compile(self) elif not objectstore.global_attributes.is_class_managed(parent.class_, key): raise ArgumentError("Non-primary property created for attribute '%s' on class '%s', but that attribute is not managed! Insure that the primary mapper for this class defines this property" % (key, parent.class_.__name__)) @@ -816,7 +805,33 @@ class GenericOption(MapperOption): kwargs = util.constructor_args(oldprop) mapper.set_property(key, class_(**kwargs )) - +class BackRef(object): + """stores the name of a backreference property as well as a relation (PropertyLoader), + used to construct more customized backrefs""" + def __init__(self, key, **kwargs): + self.key = key + self.kwargs = kwargs + def compile(self, prop): + # try to set a LazyLoader on our mapper referencing the parent mapper + if not prop.mapper.props.has_key(self.key): + if prop.secondaryjoin is not None: + # if setting up a backref to a many-to-many, reverse the order + # of the "primary" and "secondary" joins + pj = prop.secondaryjoin + sj = prop.primaryjoin + else: + pj = prop.primaryjoin + sj = None + relation = LazyLoader(prop.parent, prop.secondary, pj, sj, backref=prop.key, is_backref=True, **self.kwargs) + prop.mapper.add_property(self.key, relation); + else: + # else set one of us as the "backreference" + if not prop.mapper.props[self.key].is_backref: + prop.is_backref=True + + def get_extension(self): + return attributes.GenericBackrefExtension(self.key) + class EagerLazyOption(GenericOption): """an option that switches a PropertyLoader to be an EagerLoader or LazyLoader""" def __init__(self, key, toeager = True, **kwargs): -- 2.47.2