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)
Related
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)
);
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?
I'm setting up some table objects for SQLAlchemy.
I have a user and checkout tables. I want to associate a user object with the checkin and the checkout, which are both recorded in the same checkout object, so I have an in_user and out_user associated with each checkout object.
I've run into a sqlalchemy.exc.AmbiguousForeignKeysError
To quote the exact error message:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Checkout.out_auth_user - 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.
I've done as the error message requests (see below), but the error still occurs.
I originally only specified user email because I wanted to be able to remove users in the future without corrupting historical data. However, I tried to add user id, but still got the same error.
There are many similar questions on StackOverflow, but I couldn't find one that addressed my problem and most of them are working with much older versions of sqlalchemy that did not support the foreign_keys argument to relationship. It seems like this often occurs with backreferences, but I'm not using those as far as I'm aware. This is a simple one-way link from a checkout object to two user objects.
Flask foreign_keys still shows AmbiguousForeignKeysError
sqlalchemy , AmbiguousForeignKeysError
The full code is on github at https://github.com/ACMWM/hwcheckout
Below is an MRE
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Boolean, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import relationship
db = "sqlite:///mre.db"
engine = create_engine(db, convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
autoflush=False,
bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String)
class HW(Base):
__tablename__ = "HW"
id = Column(Integer, primary_key=True)
class Checkout(Base):
__tablename__ = "Checkouts"
what = Column(Integer, ForeignKey(HW.id))
hardware = relationship(HW, foreign_keys=[what])
id = Column(Integer, primary_key=True)
out_auth_id = Column(Integer, ForeignKey(User.id))
out_auth_email = Column(String, ForeignKey(User.email))
out_auth_user = relationship(User, foreign_keys=[out_auth_id, out_auth_email])
in_auth_id = Column(Integer, ForeignKey(User.id))
in_auth_email = Column(String, ForeignKey(User.email))
in_auth_user = relationship(User, foreign_keys=[in_auth_id, in_auth_email])
Base.metadata.create_all(bind=engine, checkfirst=True)
u = User(email="test#example.com")
chk = Checkout(out_auth_user_id=u.id,out_auth_user_email=u.email)
I'm using SQLAlchemy 1.3.3
EDIT: Remove double import of models. Same error still occurs
EDIT again: Got the MRE to reproduce the error
Postgres EDIT: Don't know if this helps, but when I tried to move my code to a real database, I got this error:
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.InvalidForeignKey) there is no unique constraint matching given keys for referenced table "users"
[SQL:
CREATE TABLE "Checkouts" (
id SERIAL NOT NULL,
outdate TIMESTAMP WITHOUT TIME ZONE,
returndate TIMESTAMP WITHOUT TIME ZONE,
who VARCHAR,
reason VARCHAR,
quantity INTEGER,
what INTEGER,
out_auth_id INTEGER,
out_auth_email VARCHAR,
in_auth_id INTEGER,
in_auth_email VARCHAR,
PRIMARY KEY (id),
UNIQUE (id),
FOREIGN KEY(what) REFERENCES "HW" (id),
FOREIGN KEY(out_auth_id) REFERENCES users (id),
FOREIGN KEY(out_auth_email) REFERENCES users (email),
FOREIGN KEY(in_auth_id) REFERENCES users (id),
FOREIGN KEY(in_auth_email) REFERENCES users (email)
)
]
Try to change your Checkout model definition:
class Checkout(Base):
__tablename__ = "Checkouts"
what = Column(Integer, ForeignKey(HW.id))
hardware = relationship(HW, foreign_keys=[what])
id = Column(Integer, primary_key=True)
out_auth_id = Column(Integer, ForeignKey(User.id))
out_auth_email = Column(String, ForeignKey(User.email))
in_auth_id = Column(Integer, ForeignKey(User.id))
in_auth_email = Column(String, ForeignKey(User.email))
out_auth_user = relationship('User', foreign_keys=[out_auth_id])
in_auth_user = relationship('User', foreign_keys=[in_auth_id])
out_auth_user_by_email = relationship('User', foreign_keys=[out_auth_email])
in_auth_user_by_email = relationship('User', foreign_keys=[in_auth_email])
Documentation: https://docs.sqlalchemy.org/en/13/orm/join_conditions.html#handling-multiple-join-paths
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.
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.