One-to-many Flask | SQLAlchemy - python

I am trying to create a one-to-many relationship using Flask and SQLAlchemy.
I want the one-to-many relationship to be as so:
"For any single movie, there can be multiple characters"
Here it what I have so far, but it is saving in my DB as one-to-one right now. (One movie to one character, saving multiple times in DB for multiple characters)
class Movie(db.Model):
__tablename__ = "movies"
id = db.Column('movies_id', db.Integer, primary_key=True)
movie_type = db.Column('movie_type', db.Text())
def __init__(self, movie_type):
self.movie_type = movie_type
def __repr__(self):
return '<Movie %r>' % self.id
class Character(db.Model):
__tablename__ = "characters"
id = db.Column('character_id', db.Integer, primary_key=True)
character_description = db.Column('character_description', db.Text())
movie_id = db.Column(db.Integer, db.ForeignKey('movies.movie_id'))
movie = db.relationship('Movie', backref='characters', lazy='dynamic')
def __init__(self, character_description, movie):
self.character_description = character_description
self.movie = movie
def __repr__(self):
return '<Character %r>' % self.id
I am saving into the DB like this:
movie = models.movie(movie_type)
character = models.Character(character_description, movie)
db.session.add(movie)
db.session.add(character)
db.session.commit()
The end goal is to be able to look up what movie a character is in. If you could also help me out with that query, that would be great!
Thanks ahead of time.

Well, I think you miss the characters relations in the movie + the insert was not totaly right.
There is also little details that you have to be carefull. Why id of movie is movieS_id and id of character is character_id ?
Also, the name of the column is the same as the name of the variable if not specified.
For example you can do that:
character_description = db.Column(db.Text())
Anyway, without changing this details, you can try this:
class Movie(db.Model):
__tablename__ = "movies"
id = db.Column('movies_id', db.Integer, primary_key=True)
movie_type = db.Column('movie_type', db.Text())
characters = db.relationship("Character", backref="movie", lazy='dynamic')
def __init__(self, movie_type):
self.movie_type = movie_type
def __repr__(self):
return '<Movie %r>' % self.id
class Character(db.Model):
__tablename__ = "characters"
id = db.Column('character_id', db.Integer, primary_key=True)
character_description = db.Column('character_description', db.Text())
movie_id = db.Column(db.Integer, db.ForeignKey('movies.movies_id'))
movie = db.relationship('Movie')
def __init__(self, character_description, movie):
self.character_description = character_description
self.movie = movie
def __repr__(self):
return '<Character %r>' % self.id
Inserting
c = Character(character_description='c')
c2 = Character(character_description='c2')
m = Movie(movie_type ='action')
# link characters to movie
m.characters.append(c)
m.characters.append(c2)
# or
m.characters.extend([c,c2])
db.session.add(m)
# add characters
db.session.add(c)
db.session.add(c2)
# or
db.session.add_all([c,c2])
# commit
db.session.commit()

Related

Flask SQLalchemy filter integer list

Currently trying to implement a recommender system for games but I'm having some problems on the REST-API with flask/sqlalchemy
I've successfully implemented routes for retrieving games and trying to apply more filters. I want to filter the retrieved games by genres - which works when I'm filtering by a single genre id - the problem arises when I try to filter by multiple genres.
The route should look like /games?genres=3,7,8
The database has a Game and a Genre table and there is a one to many relationship between them, so one game is linked to multiple genres.
This is the Game db.Model
game_genres = db.Table('game_genres',
db.Column('game_id', db.Integer, db.ForeignKey(
'games.id'), primary_key=True),
db.Column('genre_id', db.Integer, db.ForeignKey(
'genres.id'), primary_key=True),
db.PrimaryKeyConstraint('game_id', 'genre_id')
)
game_platforms = db.Table('game_platforms',
db.Column('game_id', db.Integer, db.ForeignKey(
'games.id'), primary_key=True),
db.Column('platform_id', db.Integer, db.ForeignKey(
'platforms.id'), primary_key=True),
db.PrimaryKeyConstraint('game_id', 'platform_id')
)
class Game(db.Model):
__tablename__ = 'games'
id = db.Column(db.Integer, unique=True, primary_key=True)
title = db.Column(db.String, nullable=False)
description = db.Column(db.String)
year = db.Column(db.Integer)
genres = db.relationship(
'Genre',
secondary=game_genres,
lazy='subquery',
backref=db.backref('genres', lazy=True, cascade='all, delete')
)
platforms = db.relationship(
'Platform',
secondary=game_platforms,
lazy='subquery',
backref=db.backref('games', lazy=True, cascade='all, delete')
)
def __init__(self, title: str):
self.title = title
def __repr__(self):
return f'<Game {self.title}>'
#property
def to_json(self):
genres = []
for genre in self.genres:
genres.append(genre.to_json)
platforms = []
for platform in self.platforms:
platforms.append(platform.to_json)
return {
'id': self.id,
'title': self.title,
'description': self.description,
'year': self.year,
'genres': genres,
'platforms': platforms,
}
#classmethod
def return_all(self, offset, limit):
return {'games': [g.to_json for g in self.query.order_by(Game.id).offset(offset).limit(limit).all()]}
#classmethod
def return_bygenres(self, offset, limit, genres2):
return {'games': [g.to_json for g in self.query\
.join(Game.genres, aliased=True)\
.filter(or_(*[id.like(gid)for gid in genres2])).all().order_by(Game.id).offset(offset).limit(limit).all()]}
This is the corresponding Genre Model
class Genre(db.Model):
__tablename__ = "genres"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
def __init__(self, name):
self.name = name
def __repr__(self):
return f'<Genre {self.name}>'
#property
def to_json(self):
return {
'id': self.id,
'name': self.name,
}
The routes look like this
GAME_PARSER = reqparse.RequestParser()
GAME_PARSER.add_argument('offset', type=int)
GAME_PARSER.add_argument('limit', type=int)
GAME_PARSER.add_argument('genres')
class AllGames(Resource):
def get(self):
args = GAME_PARSER.parse_args()
offset = 0 if args.offset is None else args.offset
limit = 100 if args.limit is None else args.limit
genres = args.genres
return Game.return_bygenres(offset, limit, genres.split(","))
I've found the current query on a different post but it throws an error.
The error I'm getting is "'buildtin_function_or_method' object has no attribute 'like'"
I've tried different other ways, like using any, has, or in function but all of them return the above error.
Using self.query.join(Game.genres, aliased=True).filter_by(id==1).order_by(Game.id).offset(offset).limit(limit).all() works in returning only games that have the genre-id 1
I'm not sure if I make a mistake in the query or the model is not implemented properly for that kind of query.
This is my first time coding with flask and sqlalchemy so I would appreciate any form of input!
Methods filter and filter_by take different types of arguments. Please refer to this question/answers 'Difference between filter and filter_by in SQLAlchemy
' for an explanation.
As your Genre id field is an integer I would use the in_ operator to filter on this field. e.g.
#classmethod
def return_bygenres(self, offset, limit, genres2):
# genres2 must be a list of integers, coerce if neccassary, e.g.
# .filter(Genre.id.in_([int(s) for s in genres2]))
_query = self.query\
.join(Game.genres, aliased=True)\
.filter(Genre.id.in_(genres2))\
.order_by(Game.id)\
.offset(offset)\
.limit(limit)\
return {'games': [g.to_json for g in _query.all()]}

SqlAlchemy Help: Trying to add a child to a child

I have this database schema:
class Profile_Eval(Base):
__tablename__ = 'profile_eval'
id = Column(Integer, primary_key=True)
url = Column(String(), unique=True)
job_descriptions = relationship("Job_Description", back_populates='profile')
def __init__(self, url=None):
self.url = url
class Job_Description(Base):
__tablename__ = 'job_description'
id = Column(Integer, primary_key=True)
data = Column(String())
profile_eval_id = Column(Integer, ForeignKey('profile_eval.id'))
profile = relationship("Profile_Eval", back_populates='job_descriptions')
job_predictions= relationship("Job_Prediction", back_populates='job_description')
def __init__(self, data=None, profile_eval_id=None):
self.data = data
class Job_Prediction(Base):
__tablename__ = 'job_prediction'
id = Column(Integer, primary_key=True)
label = Column(String())
score = Column(String())
job_description_id = Column(Integer, ForeignKey('job_description.id'))
job_description = relationship("Job_Description", back_populates="job_predictions")
def __init__(self, label=None, score=None, job_description_id=None):
self.label = label
self.score = score
The structure is that each profile has many jobs. Each job has many predictions.
I am able to add a profile like:
profile = Profile_Eval(url=url)
and then I am able to add the job descriptions to the profile like:
profile_record.job_descriptions.append(Job_Description(description))
but now I'm a little confused as to how I can add the predictions to each description.
Should I break it out into their own tables and get rid of the relationship? Or is there a way to add them?
Thanks!
Should I break it out into their own tables and get rid of the relationship?
The Job_Prediction is already its own table.
Or is there a way to add them?
yes, add predictions to the descriptions at the same time you're adding the descriptions to the jobs:
job_description = Job_Description(description)
job_description.job_predictions.append(...)
# ^^^ whetever you need, here
profile_record.job_descriptions.append(job_description)

Saving duplicate entries in a Flask SQLAlchemy association table

I am designing a schema to audit which user has which monitors.
For a given audit, we have users. And each user can have zero or many monitors.
Also, a user can have many of the same monitor.
Here is my User class:
class User(db.Model):
id = db.Column(db.Integer, db.Sequence('user_id_seq'),
autoincrement=True, primary_key=True)
login = db.Column(db.String(140), unique=True)
def __init__(self, login):
self.login = login
def __repr__(self):
return '<User %r>' % self.login
Here is my Audit class:
class Audit(db.Model):
id = db.Column(db.Integer, db.Sequence('audit_id_seq'),
autoincrement=True, primary_key=True)
start_date = db.Column(db.DateTime)
end_date = db.Column(db.DateTime)
def __init__(self):
self.start_date = datetime.now()
def __repr__(self):
return '<Audit %r>' % self.id
Here is my Monitor class:
class Monitor(db.Model):
id = db.Column(db.Integer, db.Sequence('monitor_id_seq'),
autoincrement=True, primary_key=True)
description = db.Column(db.String(140), unique=True)
def __init__(self, description):
self.description = description
def __repr__(self):
return '<Monitor %r>' % self.description
Here is my UserAudit class:
class UserAudit(db.Model):
id = db.Column(db.Integer, db.Sequence('user_audit_id_seq'),
autoincrement=True, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('user',
backref=db.backref('user_audits', lazy='dynamic'))
audit_id = db.Column(db.Integer, db.ForeignKey('audit.id'))
audit = db.relationship('Audit')
monitors = db.relationship('Monitor',
secondary=userAuditMonitor.association_table,
backref='user_audit_monitors')
def __init__(self, user, audit):
self.user = user
self.audit = audit
def __repr__(self):
return '<UserAudit %r>' % self.id
And finally, here is my UserAuditMonitor class which glues the whole thing together:
class UserAuditMonitor():
association_table = Table('user_audit_monitor', db.Model.metadata,
Column('user_audit_id', db.Integer, db.ForeignKey('user_audit.id')),
Column('monitor_id', db.Integer, db.ForeignKey('monitor.id'))
)
The above association table is super useful as I can simply use the .append() method and add more monitors to a UserAudit sqlalchemy object.
Example:
>>> u = UserAudit.query.get(1)
>>> monitors = [Monitor.query.get(3), Monitor.query.get(4)]
>>> u.append(monitors)
>>> print u.monitors
[<Monitor u'HP'>, <Monitor u'Dell'>]
>>> db.session.commit()
>>> print u.monitors
[<Monitor u'HP'>, <Monitor u'Dell'>]
However, if I try and append more than one of the same monitors to a UserAudit object, only one monitor gets stored.
Example:
>>> u = UserAudit.query.get(2)
>>> monitors = [Monitor.query.get(3), Monitor.query.get(3)]
>>> u.append(monitors)
>>> print u.monitors
[<Monitor u'HP'>, <Monitor u'HP'>]
>>> db.session.commit()
>>> print u.monitors
[<Monitor u'HP'>] # ONLY ONE MONITOR GOT SAVED HERE!!!
How do I configure the UserAuditMonitor class to save duplicates?
Thanks!
My guess is that UserAuditMonitor doesn't have an explicitly defined primary key, so SQLA is using user_audit_id x monitor_id as the key and de-duplicates the relationship. Perhaps try adding an autoincrement primary key to the association_table?

Count number of rows in a many-to-many relationship (SQLAlchemy)

I have a many-to-many relationship between say blog entries and tags. Now I want to know how many entries a specific tag has.
Imagine the following models (simplified):
rel_entries_tags = Table('rel_entries_tags', Base.metadata,
Column('entry_id', Integer, ForeignKey('entries.id')),
Column('tag_id', Integer, ForeignKey('tags.id'))
)
class Entry(Base):
__tablename__ = 'entries'
id = Column(Integer, primary_key=True)
title = Column(String(80))
text = Column(Text)
tags = relationship('Tag', secondary=rel_entries_tags, backref=backref('entries'))
def __init__(self, title, text):
self.title = title
self.text = text
self.tags = tags
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
name = Column(String(80), unique=True, nullable=False)
def __init__(self, name):
self.name = name
My approach to count the amount of entries for a tag is len(db_session.query(Tag).get(1).entries). The problem is that when it gets db_session.query(Tag).get(1).entries SQLAlchemy selects all the entries with all their columns for a tag, however, I want only the amount of the entries, not the entries themselves. Is there a more optimal approach for this problem?
Thanks.
session.query(Entry).join(Entry.tags).filter(Tag.id==1).count()
or if you have a Tag already
session.query(Entry).with_parent(mytag, "entries").count()

SQLAlchemy: retrieve all episodes from favorite_series of specific user

I have user who can have his favorite series and there are episodes which have series as foreign key and I am trying to retrieve all episodes from favorite series of user.
I am using Flask-SQLAlchemy.
Database:
db = SQLAlchemy(app)
# cross table for user-series
favorite_series = db.Table('favorite_series',
db.Column('user_id', db.Integer, db.ForeignKey('user.id')),
db.Column('series_id', db.Integer, db.ForeignKey('series.id'))
)
# user
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
favorite_series = db.relationship('Series', secondary=favorite_series,
backref=db.backref('users', lazy='dynamic'))
# series
class Series(db.Model):
__tablename__ = 'series'
id = db.Column(db.Integer, primary_key=True)
# episode
class Episode(db.Model):
__tablename__ = 'episode'
id = db.Column(db.Integer, primary_key=True)
series_id = db.Column(db.Integer, db.ForeignKey('series.id'))
series = db.relationship('Series',
backref=db.backref('episodes', lazy='dynamic'))
Friend helped me with SQL
select user_id,series.name,episode.name from (favorite_series left join series on favorite_series.series_id = series.id) left join episode on episode.series_id = series.id where user_id=1;
Altough, I want it in SQLAlchemy API, but can't manage to get it working.
EDIT:
My final working result:
episodes = Episode.query.filter(Episode.series_id.in_(x.id for x in g.user.favorite_series)).filter(Episode.air_time!=None).order_by(Episode.air_time)
First of all you don't seem to be declaring your table names?
Also, the whole point of bothering with orm is so you never have to write sql queries:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import orm
import sqlalchemy as db
Base = declarative_base()
favorite_series = db.Table('favorite_series', Base.metadata,
db.Column('user_id', db.Integer, db.ForeignKey('User.id')),
db.Column('series_id', db.Integer, db.ForeignKey('Series.id'))
)
class Episode(Base):
__tablename__ = 'Episode'
id = db.Column(db.Integer, primary_key=True)
season = db.Column(db.Integer)
episode_num = db.Column(db.Integer)
series_id = db.Column(db.Integer, db.ForeignKey('Series.id'))
def __init__(self, season, episode_num, series_id):
self.season = season
self.episode_num = episode_num
self.series_id = series_id
def __repr__(self):
return self.series.title + \
' S' + str(self.season) + \
'E' + str(self.episode_num)
class Series(Base):
__tablename__ = 'Series'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
episodes = orm.relationship('Episode', backref='series')
def __init__(self, title):
self.title = title
def __repr__(self):
return self.title
class User(Base):
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
favorite_series = orm.relationship('Series',
secondary=favorite_series, backref='users')
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
Now you can just access the attributes of your objects and let sql alchemy deal with keeping you DB in sync and issuing queries.
engine = db.create_engine('sqlite:///:memory:')
session = orm.sessionmaker(bind=engine)()
Base.metadata.create_all(engine)
lt = User('Ludovic Tiako')
the_wire = Series('The Wire')
friends = Series('Friends')
session.add_all([lt, the_wire, friends])
session.commit() # need to commit here to generate the id fields
tw_s01e01 = Episode(1,1,the_wire.id)
tw_s01e02 = Episode(1,2,the_wire.id)
f_s01e01 = Episode(1,1,friends.id)
f_s01e02 = Episode(1,2,friends.id)
f_s01e03 = Episode(1,3,friends.id)
session.add_all([tw_s01e01, tw_s01e02,
f_s01e01, f_s01e02, f_s01e03])
session.commit()
the_wire.episodes # > [The Wire S1E1, The Wire S1E2]
friends.episodes # > [Friends S1E1, Friends S1E2, Friends S1E3]
Finally, to answer your question:
lt.favorite_series.append(the_wire)
session.commit()
lt.favorite_series # > [The Wire]
[s.episodes for s in lt.favorite_series] # >> [[The Wire S1E1, The Wire S1E2]]
I don't know about Flask, but from the docs of Flask-SQLAlchemy, it seems it uses declarative, so the ORM. And so, you should have a session. I think it is accessible to you from db.session.
Anyway, if those assumptions are true, this is how you should do it:
query = db.session.query(User.id, Series.name, Episode.name).filter((Episode.series_id == Series.id) & \
(User.id == favorite_series.c.user_id) & (Series.id == favorite_series.c.id) & \
(User.id == 1))
results = query.all();
It might not be the exact query you provided, but should do the same.
UPDATE: I just checked Flask-SQLALchemy code on github, it seems that db is an instance of SQLAlchemy, which has a session attribute, created by self.session = self.create_scoped_session(session_options) which returns a session object. So this should work.
Also, not that by doing that, you won't be using their BaseQuery, although I don't know what that would mean...
Check the documentation to know what to do exactly.

Categories