Foreign key model in model's __repr__() - python

I have a Artist-hasMany-Suggestion relation in my Flask application (implemented with Flask-SQLAlchemy).
My Artist class looks like:
class Artist(db.Model):
__tablename__ = 'artists'
suggestions = db.relationship('Suggestion', backref='artist')
And my Suggestion class looks like:
class Suggestion(db.Model):
__tablename__ = 'suggestions'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
artist_id = db.Column(db.Integer, db.ForeignKey('artists.id'))
def __repr__(self):
return '<Suggestion %r>' % (self.artist.name,)
The problem is, apparently, that there is no self.artist defined for the Suggestion model. What can I do to achieve this?

Actualy the problem is that Suggestion is not necessarily has associated Artist. And it's breaking everything. So either check self.artist for None or impose that it couldn't be NULL.
My test code:
class Artist(db.Model):
__tablename__ = 'artists'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
suggestions = db.relationship('Suggestion', backref='artist')
class Suggestion(db.Model):
__tablename__ = 'suggestions'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
artist_id = db.Column(db.Integer, db.ForeignKey('artists.id'))
def __repr__(self):
return '<Suggestion %r>' % (self.artist.name,)
And IPython session:
In [1]: from sqlalch import db, Artist, Suggestion
In [2]: Artist.query.all()
Out[2]: [<sqlalch.Artist at 0x3559490>]
In [3]: Suggestion.query.all()
Out[3]: [<Suggestion u'Hey You!'>]
In [4]: Suggestion.query.all()[0].artist
Out[4]: <sqlalch.Artist at 0x3559490>

Related

Flask-SQLAlchemy: get column in parent table as child model attribute by one-to-many relationship

I have a example about one-to-many relationship in Flask-SQLAlchemy:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
email = db.Column(db.String(120), unique=True)
posts = db.relationship('Post', backref='user')
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('user.id')
According to Declaring Models, I can get list of posts of User U by U.posts. But in constrast, how can I get author's name of Post P as an attribute like P.author_name, instead of P.user.name?
try P.user, according to what you defined in your backref
I can modify the Post class by:
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('user.id')
#cached_property
def author_name(self):
return self.user.name
then, it will see P.author_name as P.user.name.

SQLAlchemy union_all and all() returning incorrect number of items

For some reason when I'm using SQLAlchemy's union_all and .all(), it's returning the incorrect number of items.
As you can see below, I broke each one down to see where the error was. Does anyone have any idea why this would be happening?
>>> pn = PostNotification.query.filter_by(notified_id=1)
>>> cn = CommentNotification.query.filter_by(notified_id=1)
>>> pn.count()
4
>>> cn.count()
2
>>> u = pn.union_all(cn)
>>> u.count()
6
>>> all = u.all()
>>> len(all)
5
Here are my two models:
class NotificationMixin:
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(150), nullable=False)
read = db.Column(db.Boolean, default=False)
created = db.Column(db.DateTime, index=True, default=datetime.utcnow)
#declared_attr
def notifier_id(cls):
return db.Column(db.Integer, db.ForeignKey('user.id'))
#declared_attr
def notified_id(cls):
return db.Column(db.Integer, db.ForeignKey('user.id'))
class PostNotification(db.Model, NotificationMixin):
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
comment_id = db.Column(db.Integer)
def __repr__(self):
return '<PostNotification {}>'.format(self.name)
class CommentNotification(db.Model, NotificationMixin):
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
comment_id = db.Column(db.Integer, db.ForeignKey('post_comment.id'))
def __repr__(self):
return '<CommentNotification {}>'.format(self.name)
UPDATE:
Here is a screenshot of the data that represents the two models
When I define the columns explicitly, there is no issue when I'm using union_all. It only returns the incorrect amount of records when I db.session.query(PostNotification) and db.session.query(CommentNotification).
pn = db.session.query(
PostNotification.id,
PostNotification.name,
PostNotification.read,
PostNotification.created,
PostNotification.post_id,
PostNotification.comment_id,
PostNotification.notifier_id,
PostNotification.notified_id).filter_by(
notified_id=1)
cn = db.session.query(
CommentNotification.id,
CommentNotification.name,
CommentNotification.read,
CommentNotification.created,
CommentNotification.post_id,
CommentNotification.comment_id,
CommentNotification.notifier_id,
CommentNotification.notified_id).filter_by(
notified_id=1)
u = pn.union_all(cn).order_by(PostNotification.created.desc())
>>> pn.count()
4
>>> cn.count()
2
u.count()
6
>>> all = u.all()
>>> len(all)
6
The problem with this, is I lose the model, and my relationships are gone. Therefore, I have to use this very ugly workaround. This only makes sense if you see the data in https://i.stack.imgur.com/UHfo7.jpg.
result = []
for row in u:
if 'post' in row.name.split('_'):
n = PostNotification.query.filter_by(id=row.id).first()
result.append(n)
if 'comment' in row.name.split('_'):
n = CommentNotification.query.filter_by(id=row.id).first()
result.append(n)
Now my result is in descending order, both tables are combined via union_all, and my relationships are back in tact. The problem now is, I obviously can't use result.paginate, because result is now a list.
The union u is not polymorphic in the sense that it'd recognize which rows represent PostNotification and which CommentNotification entities – it simply treats all rows as representing the primary entity PostNotification.
It also happens that you have 2 "identical" notifications in both tables, i.e. they have the same numeric value for primary key. SQLAlchemy deduplicates model entities based on primary key when querying, as noted here by the author of SQLAlchemy, and so len(u.all()) returns fewer results. u.count() on the other hand counts in the database and so counts all rows. This deduplication does not take place if querying attributes or more than 1 entity.
Looks like I figured it out. I can now query AbstractNotification directly db.session.query(AbstractNotification).all()
from sqlalchemy.ext.declarative import AbstractConcreteBase
class AbstractNotification(AbstractConcreteBase, db.Model):
__table__ = None
class NotificationBaseModel:
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(150), nullable=False)
read = db.Column(db.Boolean, default=False)
created = db.Column(db.DateTime, index=True, default=datetime.utcnow)
#declared_attr
def notifier_id(cls):
return db.Column(db.Integer, db.ForeignKey('user.id'))
#declared_attr
def notified_id(cls):
return db.Column(db.Integer, db.ForeignKey('user.id'))
class PostNotification(AbstractNotification, NotificationBaseModel):
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
comment_id = db.Column(db.Integer)
__mapper_args__ = {
'polymorphic_identity': 'post_notification',
'concrete': True
}
def __repr__(self):
return '<PostNotification {}>'.format(self.name)
class CommentNotification(AbstractNotification, NotificationBaseModel):
post_id = db.Column(db.Integer, db.ForeignKey('post.id'))
comment_id = db.Column(db.Integer, db.ForeignKey('post_comment.id'))
__mapper_args__ = {
'polymorphic_identity': 'comment_notification',
'concrete': True
}
def __repr__(self):
return '<CommentNotification {}>'.format(self.name)

cannot get one to many relationship working in (Flask-) SQLAlchemy

I have several classes:
import uuid
from app import db, create_app
from sqlalchemy.sql import func
from sqlalchemy.dialects.postgresql import UUID, ARRAY, JSONB
class Ticket(db.Model):
__tablename__ = 'tickets'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
time = db.Column(db.DateTime, server_default=func.now(), index=True)
proposed_names = db.Column(ARRAY(db.String))
measurements = db.relationship('TempMeasurement', back_populates='ticket')
class BaseMeasurement(object):
id = db.Column(db.Integer, primary_key=True)
#declared_attr
def type_id(self):
return db.Column(db.Integer, db.ForeignKey('optical_data_types.id'))
#declared_attr
def type(self):
return db.relationship('OpticalDataType')
#declared_attr
def operator_id(self):
return db.Column(db.Integer, db.ForeignKey('operators.id'))
#declared_attr
def operator(self):
return db.relationship('Operator')
#declared_attr
def item_id(self):
return db.Column(db.String, db.ForeignKey('items.serial'))
#declared_attr
def item(self):
return db.relationship('Item')
time = db.Column(db.DateTime, index=True)
instrument = db.Column(db.String)
instrument_sn = db.Column(db.String)
data = db.Column(JSONB)
class TempMeasurement(db.Model, BaseMeasurement):
__tablename__ = 'ticket_data'
id = db.Column(db.Integer, primary_key=True)
ticket_id = db.Column(UUID(as_uuid=True), db.ForeignKey('tickets.id'), index=True)
ticket = db.relationship('Ticket', back_populates='measurements')
original_paths = db.Column(ARRAY(db.String))
What I want/expect is that I can create a Ticket with several child TempMeasurements and commit this to the database. Something like:
app = create_app()
with app.app_context():
ticket = Ticket()
ticket.measurements = [TempMeasurement(...)]
db.session.add(ticket) # <-- error on this line
db.session.commit()
However, I get an obscure error deep in SQLAlchemy:
AttributeError: 'str' object has no attribute '_sa_instance_state'
with a full trace here.
I thought that it might be because the UUID ticket_id column has as_uuid, so I made it simply UUID (implicitly a str), but this did not solve my issue.
The error is too deep in SQLAlchemy for me to understand -- can anyone help?

How to set one to many and one to one relationship at same time in Flask-SQLAlchemy?

I'm trying to create one-to-one and one-to-many relationship at the same time in Flask-SQLAlchemy. I want to achieve this:
"A group has many members and one administrator."
Here is what I did:
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
members = db.relationship('User', backref='group')
admin = db.relationship('User', backref='admin_group', uselist=False)
def __repr__(self):
return '<Group %r>' % (self.name)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
admin_group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
However I got an error:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join
condition between parent/child tables on relationship Group.members -
there are multiple foreign key paths linking the tables. Specify the
'foreign_keys' argument, providing a list of those columns which
should be counted as containing a foreign key reference to the parent
table.
Does anyone know how to do that properly?
The solution is to specify the foreign_keys argument on all relationships:
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'))
admin_group_id = Column(Integer, ForeignKey('groups.id'))
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
members = relationship('User', backref='group', foreign_keys=[User.group_id])
admin = relationship('User', backref='admin_group', uselist=False, foreign_keys=[User.admin_group_id])
Perhaps consider the admin relation in the other direction to implement "a group has many members and one admin":
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
group_id = Column(Integer, ForeignKey('groups.id'))
group = relationship('Group', foreign_keys=[group_id], back_populates='members')
class Group(Base):
__tablename__ = 'groups'
id = Column(Integer, primary_key=True)
members = relationship('User', foreign_keys=[User.group_id], back_populates='group')
admin_user_id = Column(Integer, ForeignKey('users.id'))
admin = relationship('User', foreign_keys=[admin_user_id], post_update=True)
See note on post_update in the documentation. It is necessary when two models are mutually dependent, referencing each other.
The problem you're getting comes from the fact that you've defined two links between your classes - a User has a group_id (which is a Foreign Key), and a Group has an admin (which is also defined by a Foreign Key). If you remove the Foreign Key from the admin field the connection is no longer ambiguous and the relationship works. This is my solution to your problem (making the link one-to-one):
from app import db,app
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
admin_id = db.Column(db.Integer) #, db.ForeignKey('user.id'))
members = db.relationship('User', backref='group')
def admin(self):
return User.query.filter_by(id=self.admin_id).first()
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
The one drawback to this is that the group object doesn't have a neat admin member object you can just use - you have to call the function group.admin() to retrieve the administrator. However, the group can have many members, but only one of them can be the administrator. Obviously there is no DB-level checking to ensure that the administrator is actually a member of the group, but you could add that check into a setter function - perhaps something like:
# setter method
def admin(self, user):
if user.group_id == self.id:
self.admin_id = user.id
# getter method
def admin(self):
return User.query.filter_by(id=self.admin_id).first()
Ok, I found a workaround for this problem finally. The many-to-many relationship can coexist with one-to-many relationship between the same two tables at the same time.
Here is the code:
groups_admins = db.Table('groups_admins',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('group_id', db.Integer, db.ForeignKey('group.id'))
)
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(140), index=True, unique=True)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, server_default=db.func.now())
members = db.relationship('User', backref='group')
admins = db.relationship('User',
secondary=groups_admins,
backref=db.backref('mod_groups', lazy='dynamic'),
lazy='dynamic')
def __repr__(self):
return '<Group %r>' % (self.name)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'))
created_at = db.Column(db.DateTime, server_default=db.func.now())
I still want someone to tell me how to set one-to-many and one-to-one relationship at the same time, so I leave my answer here and won't accept it forever.
This link solved it for me
most important thing is to specify foreign_keys value in the relation as well as the primary join

Flask-SQLAlchemy Abstract Base Model

In my Flask-SQLAlchemy App I want to add a few fields (created(by|on), changed(by|on)) to every Model/Table
my code right now
from .. import db
class Brand(db.Model):
__tablename__ = 'md_brands'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, nullable=False)
def __repr__(self):
return u'<Brand {}>'.format(self.name)
I am not sure if it's better to use Mixins or somehow extend the base db.Model (or if even there is a better way to do this).
What (and why) is the best way to add such fields (created(by|on), changed(by|on)) to all my models?
Using __abstract__.
How do I declare a base model class in Flask-SQLAlchemy?
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
class Base(db.Model):
__abstract__ = True
created_on = db.Column(db.DateTime, default=db.func.now())
updated_on = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now())
class User(Base):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key = True)
email = db.Column(db.String(255), unique = True)
Both are pretty much the same. Here is a Mixin that I use
class ModelMixin(object):
def __repr__(self):
return unicode(self.__dict__)
#property
def public_view(self):
"""return dict without private fields like password"""
return model_to_dict(self, self.__class__)
and then
class User(db.Model, ModelMixin):
""" attributes with _ are not exposed with public_view """
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)

Categories

Resources