Stuck with Flask-Sqlalchemy Relationship system - python

I want to create database, that consist user info(sqlite db)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
login = db.Column(db.String(10), unique = True)
email = db.Column(db.String(20), unique = True)
psw = db.Column(db.String(500), nullable=True)
def __repr__(self):
return f"<User {self.id}>"
This is what i already did.
I want to create fields upcoming_friends, incoming_friends, friends, i think that i need to create a new class that will extends user , but I did not find the documentation and don't understand how to do it.

The User table stores the information that you need about a particular user. If you want to find out what friends this user might have in your application, then you can create another table called Friends.
class Friend(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
upcoming_friends = db.Column(db.String(64), unique = True)
incoming_friends = db.Column(db.String(64), unique = True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return f"<Friends: {self.id}>"
To create a relationship between these two database structures, we will do as follows:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
login = db.Column(db.String(10), unique = True)
email = db.Column(db.String(20), unique = True)
friends = db.relationship('Friend', backref='<give-a-reference>', lazy='dynamic')
def __repr__(self):
return f"<User {self.id}>"
The user_id field was initialized as a foreign key to user.id, which means that it references an id value from the user's table. In this reference the user part is the name of the database table for the model.
There is a bit of inconsistency when it comes to referring to the user table in db.ForeignKey. Here, you can see that the user table starts with a lower case, whereas when it comes to referencing Friend table in db.relationship we begin with an upper case.

Related

Flask SQLAlchemy: adding third column to joining table

Context: I'm making an auctioning website for which I am using Flask-SQLAlchemy. My tables will need to have a many-to-many relationship (as one artpiece can have many user bids and a user can bid on many artpieces)
My question is: it is possible to add another column to my joining table to contain the id of the user bidding, the id of artpiece that they are bidding on and also how much they bid? Also if yes, how would I include this bid in the table when I add a record to said table?
bid_table = db.Table("bid_table",
db.Column("user_id", db.Integer, db.ForeignKey("user.user_id")),
db.Column("item_id", db.Integer, db.ForeignKey("artpiece.item_id"))
)
class User(db.Model):
user_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
username = db.Column(db.Integer, unique=True, nullable=False)
email = db.Column(db.String(50), unique =True, nullable=False)
password = db.Column(db.String(60), nullable=False)
creation_date = db.Column(db.DateTime, default=str(datetime.datetime.now()))
bids = db.relationship("Artpiece", secondary=bid_table, backref=db.backref("bids", lazy="dynamic"))
class Artpiece(db.Model):
item_id = db.Column(db.Integer, unique=True, primary_key=True, nullable=False)
artist = db.Column(db.String(40), nullable=False)
buyer = db.Column(db.String(40), nullable=False)
end_date = db.Column(db.String(40))
highest_bid = db.Column(db.String(40))
It is possible to do this with SQL Alchemy, but it's very cumbersome in my opinion.
SQLAlchemy uses a concept called an Association Proxy to turn a normal table into an association table. This table can have whatever data fields you want on it, but you have to manually tell SQLAlchemy which columns are foreign keys to the other two tables in question.
This is a good example from the documentation.
In your case, the UserKeyword table is the association proxy table that you want to build for your user/bid scenario.
The special_key column is the arbitrary data you would store like the bid amount.
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.orm import backref, declarative_base, relationship
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
name = Column(String(64))
# association proxy of "user_keywords" collection
# to "keyword" attribute
keywords = association_proxy('user_keywords', 'keyword')
def __init__(self, name):
self.name = name
class UserKeyword(Base):
__tablename__ = 'user_keyword'
user_id = Column(Integer, ForeignKey('user.id'), primary_key=True)
keyword_id = Column(Integer, ForeignKey('keyword.id'), primary_key=True)
special_key = Column(String(50))
# bidirectional attribute/collection of "user"/"user_keywords"
user = relationship(User,
backref=backref("user_keywords",
cascade="all, delete-orphan")
)
# reference to the "Keyword" object
keyword = relationship("Keyword")
def __init__(self, keyword=None, user=None, special_key=None):
self.user = user
self.keyword = keyword
self.special_key = special_key
class Keyword(Base):
__tablename__ = 'keyword'
id = Column(Integer, primary_key=True)
keyword = Column('keyword', String(64))
def __init__(self, keyword):
self.keyword = keyword
def __repr__(self):
return 'Keyword(%s)' % repr(self.keyword)
Check out the full documentation for instructions on how to access and create this kind of model.
Having used this in a real project, it's not particularly fun and if you can avoid it, I would recommend it.
https://docs.sqlalchemy.org/en/14/orm/extensions/associationproxy.html

How to remove 1 record from a db.Table using Flask_SQLAlchemy?

I have a very simple many-to-many table structure and I'm having problems removing records from the table that makes the association between the other two:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
user_book = db.Table('user_book',
db.Column('uid', db.Integer, db.ForeignKey('user.uid'), primary_key=True),
db.Column('bid', db.Text, db.ForeignKey('book.bid'), primary_key=True),
db.Column('date_added', db.DateTime(timezone=True), server_default=db.func.now())
)
class User(db.Model):
__tablename__ = 'user'
uid = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(25), nullable=False)
hash = db.Column(db.String(), nullable=False)
first_name = db.Column(db.String(30), nullable=True)
last_name = db.Column(db.String(80), nullable=True)
books = db.relationship('Book', secondary=user_book)
class Book(db.Model):
__tablename__ = 'book'
bid = db.Column(db.Text, primary_key=True)
title = db.Column(db.Text, nullable=False)
authors = db.Column(db.Text, nullable=False)
thumbnail = db.Column(db.Text, nullable=True)
users = db.relationship('User', secondary=user_book)
To make it even clearer, here is an excerpt from the table with some records:
In the function that removes a record I did it this way:
def remove(book_id):
# get the user id (uid)
user_id = db.session.query(User).filter_by(email=session['email']).first().uid
# match the user id with the book id on table 'user_book'
book_rm = db.session.query(user_book).filter_by(uid=user_id, bid=book_id).one()
db.session.delete(book_rm)
db.session.commit()
When I call this function I get the following error on the console:
Class 'sqlalchemy.engine.row.Row' is not mapped
So after some research on Stack and documentation, I tried to do it like this:
db.session.execute(user_book.delete(user_id).where(bid=book_id))
db.session.commit()
And in this case I have received the following:
SQL expression for WHERE/HAVING role expected, got 2.
I really don't know how to go about solving this. I would like to delete only 1 record from the user_book table. Does anyone know how to do this?
Given the a User instance and the Book instance to be deleted from the User's books, the Book can be removed like this:
user_instance.books.remove(book_instance)
db.session.commit()
So the remove function would look like this:
def remove(book_id):
# get the user
user = db.session.query(User).filter_by(email=session['email']).first()
# Find the book by its id
book_rm = Book.query.get(book_id)
user.books.remove(book_rm)
db.session.commit()
See the SQLAlchemy docs for more information.

SqlAlchemy returns wrong data for association table with association proxy

I defined a role / permission model using the following model structure:
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(120), unique=True, nullable=False)
permissions = db.relationship("Permission", secondary=Role2Permission.__tablename__)
#classmethod
def find_by_id(cls, id):
return cls.query.filter_by(id=id).first()
class Role2Permission(db.Model):
__tablename__ = 'role_2_permission'
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'), primary_key=True)
permission_id = db.Column(db.Integer, db.ForeignKey('permissions.id'), primary_key=True)
bool_value = db.Column(db.Boolean, default=False)
class Permission(db.Model):
__tablename__ = 'permissions'
id = db.Column(db.Integer, primary_key=True)
action = db.Column(db.String(255))
role2permission = db.relationship('Role2Permission', lazy='joined')
bool_value = association_proxy('role2permission', 'bool_value')
When I fetch a role I want to have the permission value (bool_value) to be set via the association proxy coming from the association table. However using cls.query.filter_by(id=id).first() (where cls is the Role) returns the wrong bool_values for the queried role. I think the reason can be seen when looking at the generated SQL:
SELECT permissions.id AS permissions_id,
permissions.action AS permissions_action,
role_2_permission_1.role_id AS role_2_permission_1_role_id,
role_2_permission_1.permission_id AS role_2_permission_1_permission_id,
role_2_permission_1.bool_value AS role_2_permission_1_bool_value
FROM role_2_permission,
permissions
LEFT OUTER JOIN role_2_permission AS role_2_permission_1 ON permissions.id = role_2_permission_1.permission_id
WHERE 1 = role_2_permission.role_id AND permissions.id = role_2_permission.permission_id
I think this is fetching too many rows because it's selecting from the permissions table instead of just joining it to the role_2_permission table but then for some reason joining role_2_permission again. Somehow flask / sqlalchemy is then reducing the returned rows in a bad way: It's not actually so instead of the bool_values that belong to e.g. role 1, it returns the bool_values that belong to role 2.
How do I have to fix my model to get the correct permission data when querying the role?

SQLAlchemy multiple joins to single table

I am making a wishlist app and I want to have db schema like bellow, but I can't figure out how to make the joins in sqlalchemy (this is the first time I am using sqlalchemy).
DB schema
(user : wish = 1 : N)
When I select a user, I want to get a list of wishes and each wish may contain a different user (an arranger of the wish)
So I could do something like this
first_user = User.query.get(1)
user_wishes = first_user.wishes.all()
for wish in user_wishes:
if wish.arranger is not None:
print(wish.id, wish.owner.id, wish.arranger.id)
else:
print(wish.id, wish.owner.id)
I have looked up some tutorials, but I only found simple relations.
I need a relation from User to Wish and in the Wish, back to both the UserWishOwner (the user from which I got here) a UserWishArranger (if there is any).
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
wishes = db.Column(db.relationship('Wish', backref='owner', lazy='dynamic'))
class Wish(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
arranger_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
arranger = relationship("User", foreign_keys=[arranger_id])
I have come up with some code, but am a bit confused, because owner_id and arranger_id are the same...
What do I need to do, to make this work?
Just like this
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
wishes = db.relationship('Wish', backref='owner', lazy='dynamic', foreign_keys="[Wish.owner_id]")
class Wish(db.Model):
id = db.Column(db.Integer, primary_key=True)
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
arranger_id = db.Column(db.Integer, db.ForeignKey('user.id'), index=True)
arranger = db.relationship("User", foreign_keys=[arranger_id])

Flask foreign_keys still shows AmbiguousForeignKeysError

I have two foreign keys in an entity refering to another entity.
Here is how it looks
class Review(db.Model):
__tablename__ = 'Review'
id = db.Column(db.Integer, primary_key = True)
user_id = db.Column(db.Integer, db.ForeignKey('User.id'), nullable=False)
business_user_id = db.Column(db.Integer, db.ForeignKey('User.id'), nullable=False)
user = db.relationship('User', foreign_keys=[user_id])
business_user = db.relationship('User', foreign_keys=[business_user_id])
and
class User(db.Model):
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key = True)
reviews = db.relationship('Review', backref='user',
lazy='dynamic')
However, it still shows me an error saying
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
The above workaround is what I get from some other posts. I have checked and changed many times, and still no luck. I wonder if it's already correct or there is something I miss. Need help
Finally, I got the workaround after trying to figure out. In my case, I don't have to put backref in Review class. Instead, I should put the User backref in User class itself. So, it should look like below
class Review(db.Model):
__tablename__ = 'Review'
id = db.Column(db.Integer, primary_key = True)
user_id = db.Column(db.Integer, db.ForeignKey('User.id'), nullable=False)
business_user_id = db.Column(db.Integer, db.ForeignKey('User.id'), nullable=False)
user = relationship('User', backref='user_reviews', foreign_keys=user_id)
business_user = relationship("User", backref='business_user_reviews', foreign_keys=[business_user_id])
class User(db.Model):
__tablename__ = 'User'
id = db.Column(db.Integer, primary_key = True)
Here, both types of User have many Reviews. Then, when I need to get the list of reviews of both User, what I can do is
user = User.query.get(id)
user_reviews = User.user_reviews
business_user_reviews = user.business_user_reviews
And I am no longer running across this error.

Categories

Resources