Set server_default value for relationship table - postgresql

I have two tables for administrators and roles, connected vía the third table assignments (many-to-many relationship) with the fields role_id, administrator_id and some extra fields created_at and updated_at, which I would like to populate automatically:
assignments = db.Table('assignments',
db.Column('role_id', db.Integer, db.ForeignKey('roles.id')),
db.Column('administrator_id', db.Integer,
db.ForeignKey('administrators.id')),
db.Column('created_at', db.DateTime, server_default=db.func.now()),
db.Column('updated_at', db.DateTime, server_default=db.func.now(),
onupdate=db.func.now()),
db.ForeignKeyConstraint(['administrator_id'], ['administrators.id']),
db.ForeignKeyConstraint(['role_id'], ['roles.id'])
)
class Administrator(db.Model, UserMixin):
__tablename__ = 'administrators'
id = Column(Integer, primary_key=True, server_default=text("nextval('administrators_id_seq'::regclass)"))
email = Column(String(255), nullable=False, unique=True, server_default=text("''::character varying"))
name = Column(String(255))
surname = Column(String(255))
roles = db.relationship('Role', secondary=assignments,
backref=db.backref('users', lazy='dynamic'))
class Role(db.Model):
__tablename__ = 'roles'
id = Column(Integer, primary_key=True, server_default=text("nextval('roles_id_seq'::regclass)"))
name = Column(String(255))
But when I assign a role to an administrator
admin.roles = [role1]
db.session.add(admin)
db.session.commit()
it breaks with the following error:
IntegrityError: (psycopg2.IntegrityError) null value in column "created_at" violates not-null constraint
DETAIL: Failing row contains (1265, 19, 3, null, null).
[SQL: 'INSERT INTO assignments (role_id, administrator_id) VALUES (%(role_id)s, %(administrator_id)s)'] [parameters: {'administrator_id': 19, 'role_id': 3}]
Is there any way to set a default value for created_at and updated_at fields in assignments table?

It worked using default and onupdate parameters instead of server_default and server_onupdate:
db.Column('created_at', db.DateTime, default=db.func.now()),
db.Column('updated_at', db.DateTime, default=db.func.now(),
onupdate=db.func.now()),

Try this
db.Column('created_at', db.DateTime, server_default=text("now()"))

Related

Conflicts with relationship between tables

I've been constantly getting a warning on the console and I'm going crazy from how much I've been reading but I haven't been able to resolve this:
SAWarning: relationship 'Book.users' will copy column user.uid to column user_book.uid, which conflicts with relationship(s): 'User.books' (copies user.uid to user_book.uid). If this is not intention, consider if these relationships should be linked with back_populates, or if viewonly=True should be applied to one or more if they are read-only. For the less common case that foreign key constraints are partially overlapping, the orm.foreign() annotation can be used to isolate the columns that should be written towards. The 'overlaps' parameter may be used to remove this warning.
The tables the console cites in this notice are as follows:
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)
I use the user_book table to show the user the books he has added.
What am I missing? I take this opportunity to ask, semantically the relationship between tables and foreign keys is being done correctly?
As the warning message suggests, you are missing the back_populates= attributes in your relationships:
class User(db.Model):
# …
books = db.relationship('Book', secondary=user_book, back_populates="users")
# …
class Book(db.Model):
# …
users = db.relationship('User', secondary=user_book, back_populates="books")
# …
I kind of figure this out.
As the code in official tutorial.
from sqlalchemy import Column, ForeignKey, Integer, String, Table
from sqlalchemy.orm import declarative_base, relationship
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
name = Column(String(64))
kw = relationship("Keyword", secondary=lambda: user_keyword_table)
def __init__(self, name):
self.name = name
class Keyword(Base):
__tablename__ = "keyword"
id = Column(Integer, primary_key=True)
keyword = Column("keyword", String(64))
def __init__(self, keyword):
self.keyword = keyword
user_keyword_table = Table(
"user_keyword",
Base.metadata,
Column("user_id", Integer, ForeignKey("user.id"), primary_key=True),
Column("keyword_id", Integer, ForeignKey("keyword.id"), primary_key=True),
)
Doesn't it make you wander why the relationship only exists in User class rather than both class ?
The thing is, it automatically creates the reverse relationship in Keyword class (a "backref='users' liked parameter is required I supposed ?)

How to handle generated columns in the declarative model when inserting data into a postgres table?

If the table in postgres is as follows:
CREATE TABLE user (
id integer PRIMARY KEY,
email text UNIQUE,
height_cm numeric,
height_in numeric GENERATED ALWAYS AS (height_cm / 2.54) STORED
);
And the sqlalchemy model is:
from sqlalchemy.ext.declarative import declarative_base
class User(declarative_base()):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True, nullable=False)
height_cm = Column(Numeric)
height_in = Column(Numeric)
How to correctly deal with the generated height_in column is sqlalchemy?
The idea is to only insert id, email and height_cm using sqlalchemy however by specifying the column height_in sqlalchemy automatically inserts NULL into height_in when inserting a row into the table - and postgres then errors out as this is not allowed.
Declare the column as Computed:
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True, nullable=False)
height_cm = Column(Numeric)
height_in = Column(Numeric, Computed("height_cm / 2.54"))
def __repr__(self):
return (
f"<User(id={self.id}, email='{self.email}', "
f"height_cm={self.height_cm}, height_in={self.height_in})>"
)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
"""DDL generated:
CREATE TABLE "user" (
id SERIAL NOT NULL,
email VARCHAR NOT NULL,
height_cm NUMERIC,
height_in NUMERIC GENERATED ALWAYS AS (height_cm / 2.54) STORED,
PRIMARY KEY (id)
)
"""
with Session(engine) as session:
foo = User(email="foo#bar.baz", height_cm=175)
session.add(foo)
session.commit()
print(foo)
# <User(id=1, email='foo#bar.baz', height_cm=175, height_in=68.8976377952755906)>

Foreign Key could not be found with SqlAlchemy and PostgreSQL

I just started a Flask - SqlAlchemy project and am having some trouble with Foreign Keys.
I have the tables User and Portfolio. Portfolio has a foreign key to user, using username. I set up my model like this.
class User(db.Model):
__tablename__ = 'portfolio_users'
__table_args__ = {"schema":"keldan"}
username = Column(String(), primary_key=True)
date_added = Column(DateTime())
class Portfolio(db.Model):
__tablename__ = 'portfolios'
__table_args__ = {"schema":"keldan"}
id = Column('pid', Integer(), Sequence('portfolios_pid_seq'), primary_key=True)
date_added = Column(DateTime())
name = Column(String())
username = Column(String(), ForeignKey('portfolio_users.username'))
user = relationship('User', backref=backref('portfolios', cascade='save-update, merge, delete, delete-orphan'))
The error I get when I try to run a simple select all query is:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'portfolios.username' could not find table 'portfolio_users' with which to generate a foreign key to target column 'username'
The tables are created like this:
CREATE TABLE keldan.portfolio_users
(
username text NOT NULL,
date_added date NOT NULL,
CONSTRAINT users_pk PRIMARY KEY (username)
)
WITH (
OIDS=FALSE
);
CREATE TABLE keldan.portfolios
(
pid serial NOT NULL,
username text NOT NULL,
date_added date NOT NULL,
name text NOT NULL,
CONSTRAINT portfolios_pk PRIMARY KEY (pid),
CONSTRAINT portfolios_fk FOREIGN KEY (username)
REFERENCES keldan.portfolio_users (username) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
I have spent the better part of a day trying to figure this out or making workarounds using primaryjoin but nothing seems to work.
I finally found the answer I was looking for here
If you are not using the default schema (public) then it's not enough to specify the schema for each class, but I need to specify it in the foreign key as well.
username = Column(String(), ForeignKey('keldan.portfolio_users.username'))

Return set of unique values from multiple rows of arrays in sqlalchemy

I have 2 tables, one called Tasks and another called TaskUpdates (one to many relation). TaskUpdates has a column called tags, which is an array. I am trying to get back an array that has only unique values from the TaskUpdates.tags
class Task(BASE):
__tablename__ = 'tasks'
id = Column(Integer, primary_key=True)
# One Task to Many Updates
updates = relationship("TaskUpdate")
class TaskUpdate(BASE):
__tablename__ = 'task_updates'
# Columns
id = Column(Integer, primary_key=True)
tags = Column(ARRAY(String(255)))
task_id = Column(Integer, ForeignKey('tasks.id'))
task = relationship('Task', back_populates="updates")

sqlalchemy, violation foreign key constraint

I am new to sqlalchemy. I want to create a class which has two foreign key for different tables. Why I get next error?
sqlalchemy.exc.IntegrityError: (IntegrityError) insert or update on table "event" violates foreign key constraint "event_user_fkey"
DETAIL: Key (user)=(U) is not present in table "user".
'INSERT INTO event (id, "user", item) VALUES (%(id)s, %(user)s, %(item)s)' {'item': 'I', 'user': 'U', 'id': 'E'}
My code is next:
class User(Base):
__tablename__ = 'user'
id = Column(String, primary_key=True)
def __init__(self, id):
self.id = id
class Item(Base):
__tablename__ = 'item'
id = Column(String, primary_key=True)
def __init__(self, id):
self.id = id
class Event(Base):
__tablename__ = 'event'
id = Column(String, primary_key=True)
user = Column(String, ForeignKey('user.id'))
item = Column(String, ForeignKey('item.id'))
def __init__(self, id, user_id, item_id):
self.id = id
self.user = user_id
self.item = item_id
I use postgresql as a back end.
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
usr = User('U')
it = Item('I')
event = Event('E', usr.id, it.id)
session.add(usr)
session.add(it)
session.add(event)
The error seems pretty clear:
Key (user)=(U) is not present in table "user".
So it's trying to insert the Event row before the User has been committed to the database, which breaks the ForeignKey constraint, causing this error. Try committing the User and Item to the database before committing the Event, which depends on them and the problem should evaporate.