Flask-Migration with one-to-many relationship - python

I have a working blog system. I want to add it to comment system. I completed migrations with post model with id, title and body.
Now I add comments and create new model named Comment. When I run migrations:
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.env] No changes in schema detected.
from run import db
class Post(db.Model):
__tablename__ = 'blog.post'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
body = db.Column(db.Text, nullable=False)
comments = db.relationship('Comment', backref='blog.post')
from run import db
class Comment(db.Model):
__tablename__ = 'blog.comment'
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.Text, nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('blog.post.id'), nullable=False)
I dont know what is wrong with my code. I get relationship from documentation and edit it. There aren't any comment table in db before.
EDIT 1:
I call comment inside run like below:
from model.comment import Comment
After that I can create migration but migration got error like below:
sqlalchemy.exc.InternalError: (pymysql.err.InternalError) (1005, 'Can\'t create table blog_db.blog.comment (errno: 150 "Foreign key constraint is incorrectly formed")') [SQL: '\nCREATE TABLE blog.comment (\n\tid INTEGER NOT NULL AUTO_INCREMENT, \n\tname VARCHAR(255) NOT NULL, \n\tbody TEXT NOT NULL, \n\tcreated DATETIME DEFAULT now(), \n\tstatus INTEGER NOT NULL, \n\tpost_id INTEGER NOT NULL, \n\tPRIMARY KEY (id), \n\tFOREIGN KEY(post_id) REFERENCES blog.post (id)\n)\n\n'] (Background on this error at: http://sqlalche.me/e/2j85)

The error Foreign key constraint is incorrectly formed happens because the primary key and the foreign key have different types.
In the Post model you defined the id column as:
id = db.Column(db.Integer, primary_key=True)
But the post_id foreign key that you added in the Comment model is defined differently:
post_id = db.Column(db.Integer, db.ForeignKey('blog.post.id'), nullable=False)
I think if you remove the nullable clause from the foreign key you'll get the migration accepted.

Related

SQLAlchemy autoincrement

I have created a table using using SQLAlchemy. The column 'id' is set as the primary key and autoincrement=True is set for that column. The underlying DB is SQLite.
When I inserting the rows using SQLAlchemy with my python program, I can see that id is getting auto-incremented correctly. However on checking the table schema from the SQLite terminal I can see that column does not have AUTOINCREMENT property.
The model schema is as below.
class User(Base):
__tablename__ = 'users'
id = Column(Integer(), primary_key=True, autoincrement=True)
username = Column(String(20), unique=True, nullable=False)
name=Column(String(50), nullable=True)
email = Column(String(40), unique=True, nullable=False)
created_at = Column(DateTime(), default=datetime.utcnow)
The schema output from sqlite terminal:
sqlite> .schema users
CREATE TABLE users (
id INTEGER NOT NULL,
username VARCHAR(20) NOT NULL,
name VARCHAR(50),
email VARCHAR(40) NOT NULL,
created_at DATETIME,
PRIMARY KEY (id),
UNIQUE (username),
UNIQUE (email)
);

flask-sqlalchemy : how to manage SQL Views?

I am discovering Flask-SqlAlchemy, and currently facing an issue with the views : how can I properly manage them in the model of SqlAlchemy? Can I differenciate them in the code ?
For example if I have these two SQL Table :
CREATE TABLE Account (
[username] VARCHAR(13) NOT NULL,
[email] VARCHAR(255) NULL,
CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED ([username] ASC))
CREATE TABLE Address (
[username] VARCHAR(13) NOT NULL,
[address] CHAR(58) NOT NULL,
CONSTRAINT [PK_Address] PRIMARY KEY CLUSTERED ([username] ASC, [address] ASC))
and the view
CREATE VIEW [AccountView] AS
SELECT [Account].[username]
,[Address].[address]
FROM [Account]
INNER JOIN [Address] ON [Account].[username] = [Address].[username]
In python I have build the two tables like :
class Account(db.Model):
username = db.Column(db.String(13), primary_key=True)
email = db.Column(db.String(255), nullable=True)
class Address(db.Model):
username = db.Column(db.String(13), primary_key=True)
address = db.Column(db.String(255), primary_key=True)
what is the best solution to create the view model? I have try :
class AccountView(db.Model):
username = db.Column(db.String(13), primary_key=True)
address = db.Column(db.String(255), primary_key=True)
But here I don't see how SqlAlchemy can know it refer to a view, and so it is readonly and also should be update if I update Account or Address table.
Any advice here? I am working the right way?

Flask/SQLAlchemy - error creating models with relationships

I created a table users with this mysql statement in the shell:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(40) NOT NULL,
`email` varchar(120) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB;
and a table posts with this statement:
CREATE TABLE `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`text` varchar(140) DEFAULT NULL,
`user_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_user_id` (`user_id`),
CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB;
I want to use these tables in my Flask app. I created a foreign key connecting posts to users. It's supposed to be a one-to-many relationship with the user having many posts.
In my python code I have the following models:
class Users(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(40), unique=True)
email = db.Column(db.String(120), unique=True)
posts = db.relationship('Posts', backref='user', lazy='dynamic')
def __init__(self, username, email):
self.username = username
self.email = email
def __repr__(self):
return '<User {}>'.format(self.username)
class Posts(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(140))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
When I try running my Flask app however, I keep getting the error:
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to
initialize - can't proceed with initialization of other mappers.
Original exception was: Could not determine join condition between
parent/child tables on relationship Users.posts - there are no foreign
keys linking these tables.
But I created the foreign key connecting posts to users in the mysql statement. When I take out the line posts = db.relationship('Posts', backref='user', lazy='dynamic'), everything works just fine, so the issue lies in that piece of code. What part of my setup is wrong?
Not sure what's wrong with the initial creation of the tables, but i would use sqlalchemy's create_all. See here and in the docs. This way you know any issue is not a matter of different definitions of the initial SQL creation query vs the models' definitions. Stick with SQLAlchemy all the way.
Your model definitions seem OK, except for the backref. I compared it with my models definitions, and my backrefs are always the table names. You have 'user' as a backref, which doesn't really appear anywhere else in the code, so i'm assuming that's where things go wrong.
To forcibly define table names add tablename to the class definition; e.g.
class User(db.model):
__tablename__ = 'Users'
id = db.Column(..
.. more fields ..
posts = db.relationship('Post', backref='Users', lazy='dynamic')
I use the convention of the classes in singular, and table names in plural, which makes it easier to see how things are defined, what a backref points to, etc. For example, note that the above relationship points to the class Post, while the backref to the table Users. Now there's no ambivalence in the definitions.

After adding relation instrumentedlist causes query-invoked flush on sqlalchemy [duplicate]

I'm trying to insert the role data in this association table:
class Association(db.Model):
__tablename__ = 'associations'
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), primary_key=True)
role = db.Column(db.String)
user = db.relationship("User", back_populates="teams")
team = db.relationship("Team", back_populates="users")
It's linked to 2 other tables:
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String)
username = db.Column(db.String, nullable=False, unique=True, index=True)
password_hash = db.Column(db.String)
teams = db.relationship("Association", back_populates="user")
class Team(db.Model):
__tablename__ = 'teams'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String)
player = db.Column(db.String)
users = db.relationship("Association", back_populates="team")
However, when I try to insert the role in the association table:
t = Team.query.filter_by(id=team_name1_id).first()
a = Association(role=form.role.data)
a.user = User.query.filter_by(id=current_user.id).first()
t.users.append(a)
db.session.add(t)
db.session.commit()
I get this error:
IntegrityError: (raised as a result of Query-invoked autoflush;
consider using a session.no_autoflush block if this flush is occurring
prematurely) (sqlite3.IntegrityError) NOT NULL constraint failed:
association.team_id [SQL: u'INSERT INTO association (user_id, role)
VALUES (?, ?)'] [parameters: (1, u'point guard')]
Do you know how can I fix it?
The line that causes the autoflush is
t.users.append(a)
You've not defined your relationship loading strategies, so they default to "select". Since you're accessing the Team.users relationship attribute for the first time, it will emit a SELECT in order to fetch the related objects.
You've also made changes to the session indirectly in
a.user = User.query.filter_by(id=current_user.id).first()
which due to save-update cascade, on by default, places the new Association instance to the session as well.
In order to keep the DB's and session's state consistent SQLAlchemy has to flush your changes to the DB before the query is emitted, and so the half initialized Association is sent to the DB before being associated with the Team instance.
Possible solutions:
Temporarily disable the autoflush feature for that particular append because you know you're adding a new Association:
with db.session.no_autoflush:
t.users.append(a)
The SELECT will still be emitted, though.
Reverse the operation. Instead of appending the Association instance to the Team.users collection, assign the Team to the Association.team:
# Instead of t.users.append(a):
a.team = t # Mr.
A less obvious solution is to eager load the contents of the relationship when you fetch the Team instance, so that no SELECT is necessary:
t = db.session.query(Team).options(db.joinedload(Team.users)).first()
or not load them at all using the db.noload(Team.users) option.
Note that
db.session.add(t)
is redundant. The fetched Team instance is already in the session – otherwise the lazy load of Team.users could not have proceeded in the first place.

How can "on delete restrict" be used in a model?

I have figured out how to use "on delete cascade", but am unclear on how to do "on delete restrict" constraints. What I would like to achieve is to not be able to delete a parent that has a child or children records.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
fullname = db.Column(db.String)
password = db.Column(db.String)
posts = db.relationship("Post", backref='user', cascade="all, delete, delete-orphan")
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
description = db.Column(db.String, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('users.id', onupdate="CASCADE", ondelete="CASCADE"))
CREATE TABLE posts (
id INTEGER NOT NULL,
title VARCHAR NOT NULL,
description VARCHAR NOT NULL,
user_id INTEGER,
PRIMARY KEY (id),
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE TABLE users (
id INTEGER NOT NULL,
name VARCHAR,
fullname VARCHAR,
password VARCHAR,
PRIMARY KEY (id)
);
Replacing "delete" with "restrict" just allows me to delete the parents and retain the orphaned rows.
How do I properly specify the "restrict" behavior?
SQLite does not support foreign key constraints by default. They must be enabled at compile time and enabled at run time, otherwise they are silently ignored.
You can check if foreign keys are enabled by running pragma foreign_keys in a sqlite shell. If it returns 1, they are enabled. If it returns 0, they are disabled. If it does not return anything, they are not supported and sqlite must be recompiled to support them.
If foreign keys are disabled, you can instruct SQLAlchemy to enable them when creating connections.
from sqlalchemy import event
from sqlalchemy.engine import Engine
from sqlite3 import Connection as SQLite3Connection
#event.listens_for(Engine, "connect")
def _set_sqlite_pragma(dbapi_connection, connection_record):
if isinstance(dbapi_connection, SQLite3Connection):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON;")
cursor.close()
source: https://stackoverflow.com/a/15542046/400617
SQL foreign key cascades are different than SQLAlchemy's relationship cascades (scroll down that second link to see a detailed comparison of the two). SQLAlchemy has no "restrict" cascade. You should specify that on the foreign key. Be sure to recreate/migrate the database if you change a foreign key that already exists.
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.id, ondelete='RESTRICT'), nullable=False)

Categories

Resources