I'm learning about Databases (Postgres) and ORM, and be blocked by this.
In the database, I have something like this
CREATE TABLE public.city (
city_id serial PRIMARY KEY,
city_name varchar(50) UNIQUE NOT NULL,
region_id integer NOT NULL REFERENCES public.region ON DELETE CASCADE
);
And, in my Flask app (python, Flask microframework, and Flask-SqlAlchemy) model definition I have:
class City(Base):
city_id = db.Column(db.Integer, primary_key=True)
city_name = db.Column(
db.String(50),
unique=True,
nullable=False)
region_id = db.Column(
db.Integer,
db.ForeignKey(
'region.region_id',
ondelete="CASCADE"),
nullable=False)
country = db.relationship(
'Region',
backref=db.backref('cities'))
My doubt is: Is all this unique and nullable necessary in my python model? Considering that the database table is created from an SQL script file and not by SqlAlchemy.
This information adds something usable to the ORM framework?
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 am currently working on a project with a pre-existing database. The Server is a clustered server with multiple Catalogs (database), and in each Catalog there are multiple Schemas with Tables. The table name format for the traditional SQL query would be [Catalog].[Schema].[Table]. This structure works for the traditional SQL.
The problem comes in when I try to flask db migrate to an sqlite database for testing. I get a number of errors depending on what I try.
I am using
Python 3.7
Flask 1.0.2
Flask-SQLAlchemy 2.4.0
Flask-Migrate 2.4.0
Windows 10 (not ideal, but its what I have)
I have tried the following with different results:
Schema only method:
class User(db.Model):
__tablename__ = 'user'
__table_args__ = (
db.PrimaryKeyConstraint('userid')
, db.ForeignKeyConstraint(('manageruserid',), ['CatalogA.SchemaA.userid'])
, {'schema': 'CatalogA.SchemaA'}
)
manager_user_id = db.Column('manageruserid', db.Integer())
user_id = db.Column('userid', db.Integer(), nullable=False)
class Tool(db.Model):
__tablename__ = 'tool'
__table_args__ = (
db.PrimaryKeyConstraint('toolid')
, db.ForeignKeyConstraint(('ownerid',), ['CatalogA.SchemaA.user.userid'])
, {'schema': 'CatalogB.SchemaB'}
)
tool_id = db.Column('toolid', db.Integer())
owner_id = db.Column('ownerid', db.Integer(), nullable=False)
when trying to upgrade it creates an error:
"sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) unknown database "CatalogA.SchemaA" [SQL: CREATE TABLE "CatalogA.SchemaA".user (
manageruserid INTEGER,
userid INTEGER NOT NULL,
PRIMARY KEY (userid),
FOREIGN KEY(manageruserid) REFERENCES user (userid) )"
Bind With Schema (binds are setup correctly)
class User(db.Model):
__bind_key__ = 'CatalogA'
__tablename__ = 'user'
__table_args__ = (
db.PrimaryKeyConstraint('userid')
, db.ForeignKeyConstraint(('manageruserid',), ['CatalogA.SchemaA.user.userid'])
, {'schema': 'SchemaA'}
)
manager_user_id = db.Column('manageruserid', db.Integer())
user_id = db.Column('userid', db.Integer(), nullable=False)
class Tool(db.Model):
__bind_key__ = 'CatalogB'
__tablename__ = 'tool'
__table_args__ = (
db.PrimaryKeyConstraint('toolid')
, db.ForeignKeyConstraint(('ownerid',), ['CatalogA.SchemaA.user.userid'])
, {'schema': 'SchemaB'}
)
tool_id = db.Column('toolid', db.Integer())
owner_id = db.Column('ownerid', db.Integer(), nullable=False)
when trying to migrate it creates an error:
"sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'user.manageruserid' could not find table 'CatalogA.SchemaA.user' with which to generate a foreign key to target column 'userid'"
If I do it the Schema only method way then I can run queries on the database, but it doesn't correctly setup my test-db.
I looked for multiple hours trying to find a solution, and would love someone to help me find the way forward (if you find a link to another solution, please tell me what you searched as well to increase my google-fu).
Main questions are:
What is the right way to have a model for this situation?
Was/Is there something in the documentation which I missed for this scenario?
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 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)