backref/back_populates question

274 views
Skip to first unread message

cd34

unread,
Aug 14, 2011, 3:41:04 PM8/14/11
to sqlalchemy
I'm writing a package for Pyramid which has a base AuthUser
declaration. I want to allow users that have installed the package the
ability to extend the AuthUserProfile so that the Mako templates can
be accessed with my subscriber which populates user. with the AuthUser
properties.

Currently, it does not populate ${user.email} where email is declared
in a separate class. I read the documentation and it appeared that
back_populates was probably the option I needed, but, I'm having some
difficulty getting it to work.

In the code, what I'm trying to accomplish is query against AuthUser,
but, have ExtendedProfile's fields available to that query. It seemed
like back_populate should do that, but, it says (correctly) that the
AuthUser doesn't point to a mapper on ExtendedProfile.

In this code sample, what I would like to do is query AuthUser, but,
receive the ExtendedProfile class declarations.

Thanks in advance for any pointer in the right direction.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from sqlalchemy import create_engine
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('mysql://test:test@localhost/test?
use_unicode=1&charset=utf8')
session = sessionmaker(bind=engine, autocommit=True)
DBSession = session()
Base = declarative_base()

import bcrypt
import transaction

from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Table
from sqlalchemy import Unicode
from sqlalchemy import types
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref
from sqlalchemy.orm import relation
from sqlalchemy.orm import scoped_session
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import synonym
from sqlalchemy.sql import functions

from velruse.store.sqlstore import SQLBase

from zope.sqlalchemy import ZopeTransactionExtension

"""
These definitions are part of a package. I want to have the package
able to query AuthUser, but, to know about the extended attributes.
"""

user_group_table = Table('auth_user_groups', Base.metadata,
Column('user_id', types.BigInteger(), \
ForeignKey('auth_users.id', onupdate='CASCADE',
ondelete='CASCADE')),
Column('group_id', types.BigInteger(), \
ForeignKey('auth_groups.id', onupdate='CASCADE',
ondelete='CASCADE'))
)

class AuthGroup(Base):
__tablename__ = 'auth_groups'
__table_args__ = {"sqlite_autoincrement": True}

id = Column(types.BigInteger(), primary_key=True)
name = Column(Unicode(80), unique=True, nullable=False)
description = Column(Unicode(255), default=u'')

users = relation('AuthUser', secondary=user_group_table, \
backref='auth_groups')

def __repr__(self):
return u'%s' % self.name

def __unicode__(self):
return self.name


class AuthUser(Base):
__tablename__ = 'auth_users'
__table_args__ = {"sqlite_autoincrement": True}

id = Column(types.BigInteger(), primary_key=True)
login = Column(Unicode(80), default=u'', index=True)
username = Column(Unicode(80), default=u'', index=True)
_password = Column('password', Unicode(80), default=u'',
index=True)
email = Column(Unicode(80), default=u'', index=True)

profile = relation('AuthUserProfile', uselist=False,
back_populates='user')

groups = relation('AuthGroup', secondary=user_group_table, \
backref='auth_users')
"""
Fix this to use association_proxy
groups = association_proxy('user_group_table', 'authgroup')
"""

def _set_password(self, password):
self._password = bcrypt.hashpw(password, bcrypt.gensalt())

def _get_password(self):
return self._password

password = synonym('_password', descriptor=property(_get_password,
\
_set_password))

def in_group(self, group):
for g in self.groups:
if g.name == group:
return True
else:
return False

@classmethod
def get_by_id(cls, id):
return DBSession.query(cls).filter(cls.id==id).first()

@classmethod
def get_by_login(cls, login):
return DBSession.query(cls).filter(cls.login==login).first()

@classmethod
def get_by_username(cls, username):
return
DBSession.query(cls).filter(cls.username==username).first()

@classmethod
def get_by_email(cls, email):
return DBSession.query(cls).filter(cls.email==email).first()

@classmethod
def check_password(cls, **kwargs):
if kwargs.has_key('id'):
user = cls.get_by_id(kwargs['id'])
if kwargs.has_key('username'):
user = cls.get_by_username(kwargs['username'])

if not user:
return False
if bcrypt.hashpw(kwargs['password'], user.password) ==
user.password:
return True
else:
return False

class AuthUserProfile(Base):
__tablename__ = 'auth_user_profile'

id = Column(types.BigInteger, primary_key=True)
user_id = Column(types.BigInteger, ForeignKey(AuthUser.id))

user = relationship('AuthUser', back_populates='profile')

"""
Present in code
"""

class ExtendedProfile(Base):
__tablename__ = 'auth_user_profile'
__table_args__ = {'extend_existing':True}
populate_existing=reload

id = Column(types.BigInteger, primary_key=True)
user_id = Column(types.BigInteger, ForeignKey(AuthUser.id))

email = Column(Unicode(80), unique=True, nullable=False)

user = relationship('AuthUser', back_populates='profile')


user = DBSession.query(AuthUser).filter(AuthUser.id==1).first()
print 'id:', user.profile.id
print 'email:', user.profile.email

Michael Bayer

unread,
Aug 14, 2011, 11:39:52 PM8/14/11
to sqlal...@googlegroups.com

On Aug 14, 2011, at 3:41 PM, cd34 wrote:


>
> In the code, what I'm trying to accomplish is query against AuthUser,
> but, have ExtendedProfile's fields available to that query. It seemed
> like back_populate should do that, but, it says (correctly) that the
> AuthUser doesn't point to a mapper on ExtendedProfile.

OK back_populates has nothing to do with querying, it only involves the behavior of AuthUser and AuthUserProfile objects as they are assigned to reference each other in Python. The docs at http://www.sqlalchemy.org/docs/orm/relationships.html#linking-relationships-with-backref should be pretty in-depth on this.

Reading some more here I think you're confusing it with "backref", which is more commonly used as it generates a relationship() in the other direction. back_populates associates two existing relationships with each other.

>
> In this code sample, what I would like to do is query AuthUser, but,
> receive the ExtendedProfile class declarations.

Well those are two different classes, so first you'd need to assign a relationship() to AuthUser that references ExtendedProfile. If you're looking for "backref" to do this via ExtendedProfile that's:

class ExtendedProfile(...):
user = relationship("AuthUser", backref="profile")

this assigns a relationship() to ExtendedProfile and AuthUser.

Query then joins via the relationship():

session.query(AuthUser).join(AuthUser.profile).filter(ExtendedProfile.email=='foo')

cd34

unread,
Aug 15, 2011, 12:46:42 AM8/15/11
to sqlalchemy
On Aug 14, 11:39 pm, Michael Bayer <mike...@zzzcomputing.com> wrote:
> > receive the ExtendedProfile class declarations.
>
> Well those are two different classes, so first you'd need to assign a relationship() to AuthUser that references ExtendedProfile.  If you're looking for "backref" to do this via ExtendedProfile that's:
>
> class ExtendedProfile(...):
>     user = relationship("AuthUser", backref="profile")
>
> this assigns a relationship() to ExtendedProfile and AuthUser.

I am able to do that. What I'm trying to do is have a base class that
is declared in a package that is installed through easy_install (for
Pyramid), then, allow the user to extend the class locally. So, the
person installs my package, but, needs to be able to extend
user_profile themselves.

https://github.com/cd34/pyramid_apex/blob/master/pyramid_apex/views.py

The issue I'm running into currently is that in the model definition
in pyramid, I don't have access to the config registry to override the
class creation. So, I'm looking for a way to either do a late binding
on the relation, or, need to do some really creative coding so that my
module can access a user supplied profile class.

Michael Bayer

unread,
Aug 15, 2011, 1:05:39 AM8/15/11
to sqlal...@googlegroups.com

A relationship() can be added at any time to any mapped class - when using declarative config, just assigning it to the class is all that's needed, mapper.add_property() is called as a result. Using "backref" where you had "back_populates" should also achieve the result of adding a relationship().

Otherwise if "extend" means "subclass", that's an option as well (though it means you can't change your base class without risking backwards incompatibility).

cd34

unread,
Aug 15, 2011, 11:43:49 AM8/15/11
to sqlalchemy
On Aug 15, 1:05 am, Michael Bayer <mike...@zzzcomputing.com> wrote:

> A relationship() can be added at any time to any mapped class - when using declarative config, just assigning it to the class is all that's needed, mapper.add_property() is called as a result.     Using "backref" where you had "back_populates" should also achieve the result of adding a relationship().
>
> Otherwise if "extend" means "subclass", that's an option as well (though it means you can't change your base class without risking backwards incompatibility).

I couldn't subclass as I couldn't get the config object in order to
get my local class. backref did work well. I don't really want to
override AuthUser - the theory for this library is that the auth data
is not changed, but, users can locally define their own User Profile
that is linked via a foreign key. Updates to my library won't require
them to make changes to their table and it should eliminate migration
issues, etc.

I do thank you for your response - works like a charm. Also learned
quite a bit about mappers in the process which will fix another issue
I had in some unrelated code.
Reply all
Reply to author
Forward
0 new messages