I've got a Flask app with the following models:
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
city_id = db.Column(db.Integer, db.ForeignKey('cities.id'))
class City(db.Model):
__tablename__ = 'cities'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
user_ids = db.relationship('User', backref='city', lazy='dynamic')
I've run a migration to specify my indices and foreign key constraints:
def upgrade():
op.create_foreign_key('fk_user_city', "users", "cities", ["city_id"], ["id"])
op.create_index('city_idx', 'users', ['city_id'])
However, any time I create another new migration Alembic seems to want to drop my indexes.
Is there a way to freeze Alembic's autogeneration at the current DB/Model schema?
Check this page. You will need to change env.py under migrations folder.
EnvironmentContext.configure.include_object
or
EnvironmentContext.configure.include_schemas
should be what you are looking for.
Related
Error:
sqlalchemy.orm.exc.UnmappedColumnError
sqlalchemy.orm.exc.UnmappedColumnError: Can't execute sync rule for source column 'user.id'; mapper 'mapped class Group->group' does not map this column. Try using an explicit `foreign_keys` collection which does not include destination column 'group_followers.group_followed_id' (or use a viewonly=True relation).
I'm trying to build a system that users can follow groups.
Here is the error parts of code:
group_followers = db.Table('group_followers',
db.Column('group_follower_id', db.Integer, db.ForeignKey('group.id')),
db.Column('group_followed_id', db.Integer, db.ForeignKey('group.id'))
)
class User(UserMixin, db.Model):
...
group_followed = db.relationship(
'Group', secondary = group_followers,
primaryjoin = (group_followers.c.group_follower_id == id),
secondaryjoin = (group_followers.c.group_followed_id == id),
backref = db.backref('group_followers', lazy='dynamic'), lazy='dynamic')
...
def group_follow(self, group):
if not self.is_following(group):
self.group_followed.append(group)
You need to clearly define the schema of your two tables.
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
groups = db.relationship('Group', backref='user', lazy='dynamic')
def __repr__(self):
return f'User {self.username}'
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
# You can add any other field you want
# ...
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
Above, I have shown how to create a relationship from the perspective of a user using db.relationship(). To make this relationship, you will need to add a ForeignKey() in the table you want to reference. user in db.ForeingKey(user.id) refers to the User table.
The association table you have created above (group_followers) is not part of any models so add it outside any of the classes User and Group:
group_followers = db.Table('group_followers',
db.Column('group_follower_id', db.Integer, db.ForeignKey('group.id')),
db.Column('group_followed_id', db.Integer, db.ForeignKey('group.id'))
)
Then, declare the many-to-many relationship in the User's table:
class User(UserMixin, db.Model):
...
group_followed = db.relationship(
'Group', secondary = group_followers,
primaryjoin = (group_followers.c.group_follower_id == id),
secondaryjoin = (group_followers.c.group_followed_id == id),
backref = db.backref('group_followers', lazy='dynamic'), lazy='dynamic')
def group_follow(self, group):
if not self.is_following(group):
self.group_followed.append(group)
Run your migrations to update and apply these changes:
(venv)$ flask db migrate -m 'group followers'
(venv)$ flask db upgrade
This should work for a user who wants to follow a group. You can define other functions to unfollow a group or check whether a user is already following a group.
I am new to using Flask SQLAlchemy and I have a few questions on how can how can I link separate databases together to form a query.
I have 3 databases: User, Homework, Questions, where 1 user can have many homework and 1 homework can have many questions. I have made 3 separate .py files for the 3 classes and each file calls simple functions like query.all(), and some simple filtering.
I have previously learned MySQL and I am wondering how can I create an SQLAlchemy equivalent of the following query:
SELECT * FROM user, homework, questions
WHERE user.user_id = homework.user_id
AND homework.homework_id = questions.homework_id
My question is how do I go about achieving this? Do I make a new file and find a way to bind these databases together or is there a more optimal way of doing it?
Also, how do I go about retrieving aggregated function values? For instance, the average marks one would get for each homework.
User.py
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(20), nullable=False)
Homework.py
class Homework(db.Model):
__tablename__ = 'homework'
homework_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id= db.Column(db.Integer, nullable=False)
subject = db.Column(db.String(20), nullable=False)
Question.py
class Question(db.Model):
__tablename__ = 'question'
question_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
homework_id = db.Column(db.Integer, nullable=False)
marks = db.Column(db.Integer, nullable=False)
You can create relationships amongst your tables using the following
class User(db.Model):
__tablename__ = 'user'
user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(20), nullable=False)
class Homework(db.Model):
__tablename__ = 'homework'
homework_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.user_id'), nullable=False)
subject = db.Column(db.String(20), nullable=False)
class Question(db.Model):
__tablename__ = 'question'
question_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
homework_id = db.Column(db.Integer, db.ForeignKey('homework.homework_id'), nullable=False)
marks = db.Column(db.Integer, nullable=False)
Then you can access info from a given table using the following
insert your desired user_id in replace for xxx
user_object = User.query.filter_by(user_id=xxx).first()
username = user_object.username
user_id_value = user_object.user_id
homework_object = Homework.query.filter_by(user_id=user_id_value).first()
subject = homework_object.subject
homework_id_value = homework_object.homework_id
question_object = Question.query.filter_by(homework_id=homework_id_value).first()
marks = question_object.marks
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])
I want to design such an application which has users and projects, user can be a candidate of projects, and can be chosen as participant of projects. So I use the code below:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
class Participate(db.Model):
__tablename__ = 'participates'
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'),
primary_key=True)
candidate_id = db.Column(db.Integer, db.ForeignKey('users.id'),
primary_key=True)
participant_id = db.Column(db.Integer, db.ForeignKey('users.id'),
primary_key=True)
candidate_timestamp = db.Column(db.DateTime, default=datetime.utcnow)
participate_timestamp = db.Column(db.DateTime, default=datetime.utcnow)
class Project(db.Model):
__tablename__ = 'projects'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
candidates = db.relationship('Participate',
foreign_keys=[Participate.candidate_id],
backref=db.backref('candidate_projects', lazy='joined'),
lazy='dynamic')
participants = db.relationship('Participate',
foreign_keys=[Participate.participant_id],
backref=db.backref('participate_projects', lazy='joined'),
lazy='dynamic')
then I tried to create some data in shell:
# python manage.py shell
>>> db
<SQLAlchemy engine='mysql://connection_uri?charset=utf8&use_unicode=0'>
>>> Project
<class 'app.models_.Project'>
>>> User
<class 'app.models_.User'>
>>> Participate
<class 'app.models_.Participate'>
>>> jerry = User(username='jerry')
I got this exception:
NoForeignKeysError: Could not determine join condition between parent/child
tables on relationship Project.candidates - there are no foreign keys linking
these tables. Ensure that referencing columns are associated with a ForeignKey
or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
I'm new to sqlalchemy , what is the right way to design a database like what I want?
The candidate_id and participant_id attributes are User foreign keys. You are setting up relationships with the Project table. You need to move those to the User table and then they'll work.
Then if you need to get all the candidates or participants of a project, you can use these relationships and filter them by the project you are interested in.
I need to have a post associated to two users. The author and the moderator. I am trying without success this code
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
...
post = db.relationship('Post', foreign_keys=['posts.id'], backref='post_user', lazy='dynamic')
post_blame = db.relationship('Post', foreign_keys=['posts.moderated_by'], backref='post_blame', lazy='dynamic')
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
...
author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
moderated_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
Error:
ArgumentError: Column-based expression object expected for argument 'foreign_keys'; got: 'posts.id', type <class 'str'>
One of the issues here is that you are currently trying to use table columns in the relationship foreign_keys, rather than class attributes.
That is, instead of using posts.id, you should be using Post.id. (In fact, to refer to a table column, you would need to use posts.c.id).
So, it is possible that your original code will work if you correct it to:
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
...
post = db.relationship('Post', foreign_keys='Post.id', backref='post_user', lazy='dynamic')
post_blame = db.relationship('Post', foreign_keys='Post.moderated_by', backref='post_blame', lazy='dynamic')
If it does not, then there several other options. First, you could establish these relationships in the Post class, where it is less ambiguous for sqlalchemy to find the foreign key relationship. Something like
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
...
author_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
moderated_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
post_user = db.relationship(User, foreign_keys=author_id, backref='post', lazy='dynamic')
post_blame = db.relationship(User, foreign_keys=moderated_by, backref='post_blame', lazy='dynamic')
Note in this version, we don't need to pass the foreign_keys value as a string, we can just refer directly to the column in scope.
Alternatively, if you wish to establish these relationships within User, you may you need to give sqlalchemy more information, by using primaryjoin ... perhaps something like:
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
...
post = db.relationship('Post', primaryjoin='User.id == Post.id', backref='post_user', lazy='dynamic')
post_blame = db.relationship('Post', foreign_keys='User.id == Post.moderated_by', backref='post_blame', lazy='dynamic')