sqlalchemy, violation foreign key constraint - postgresql

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.

Related

How to stop SqlAlchemy from generating SQL that inserts or updates the primary key?

I have a model:
class Contact(db.Model):
__tablename__ = 'contact'
contactid = db.Column(db.BigInteger, primary_key=True, server_default=db.text("nextval('contact_contactid_seq'::regclass)"))
firstname = db.Column(db.Text)
lastname = db.Column(db.Text)
and a form
class ContactForm(FlaskForm):
contactid = HiddenField('contactid')
firstname = StringField('First Name', validators=[Optional()], filters = [lambda x: x or None])
lastname = StringField('Last Name', validators=[Optional()], filters = [lambda x: x or None])
submit = SubmitField('Save')
I use code similar to the following to insert and update records in the database using the above model and form
#blueprint.route('/create', methods=['GET', 'POST'])
def create():
form = ContactForm()
if form.validate_on_submit():
r = Contact()
form.populate_obj(r)
db.session.add(r)
db.session.commit()
flash("saved new record", "success")
return root()
return render_template('contact/create.html', form=form)
#blueprint.route('/edit/<id>', methods=['GET', 'POST'])
def edit(id: int):
r = Contact.query.get_or_404(id)
form = ContactForm(obj=r)
if form.validate_on_submit():
form.populate_obj(r)
db.session.commit()
flash("updated record", "success")
return redirect(url_for('blueprint.root'))
return render_template('contact/edit.html', form=form)
The Problem
SQLAlchemy appears to be generating SQL like this:
INSERT INTO contact (contactid, firstname, lastname) VALUES (%(contactid)s, %(firstname)s, %(lastname)s)
UPDATE contact set contactid=%(contactid)s, firstname=%(firstname)s, lastname=%(lastname)s WHERE contactid = %(contactid)s
I want it to do it like this:
INSERT INTO contact (firstname, lastname) VALUES (%(firstname)s, %(lastname)s)
SELECT last_contactid_somehow
UPDATE contact set firstname=%(firstname)s, lastname=%(lastname)s WHERE contactid = %(contactid)s
What am I doing wrong?
I would like a solution where SqlAlchemy:
handles the auto-incrementing primary key without sticking it into the insert statement
doesn't set the primary key in the update statement
I am using Postgres
I think the first problem will be solved by setting autoincrement=True
class Contact(db.Model):
__tablename__ = 'contact'
contactid = db.Column(db.BigInteger, primary_key=True, autoincrement=True, server_default=db.text("nextval('contact_contactid_seq'::regclass)"))
...
the second problem is actually working properly because you defined the form class like that.
In addition, the query you want and the query generated by SQLAlchemy are the same because the contractid in the where clause and the contractid in the set clause is same.
Nevertheless, if you want to change the query, change the value of the model object.
r = Contact.query.get_or_404(id)
form = ContactForm(obj=r)
if form.validate_on_submit():
r.firstname = form.firstname.data
r.lastname = form.lastname.data
db.session.commit()
...

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)>

How do I refer to a foreign key table twice?

I get the following error:
u'detail': u"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 Vote.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."
Table A is being defined as:
class User(postgres.Model):
def __init__(self,
name
):
self.name = name
id = postgres.Column(postgres.Integer , primary_key=True , autoincrement=True)
name = postgres.Column(postgres.String(32) , nullable=False , unique=True)
Table B is being defined as:
class Vote(postgres.Model):
def __init__(self,
user_id,
responder_id,
#timestamp_request,
#timestamp_respond,
value
):
self.user_id = user_id
self.responder_id = responder_id
#self.timestamp_request = timestamp_request
#self.timestamp_respond = timestamp_respond
self.value = value
id = postgres.Column(postgres.Integer , primary_key=True , autoincrement=True)
user_id = postgres.Column(postgres.Integer , postgres.ForeignKey('user.id'))
user = postgres.relationship(User , backref=postgres.backref('votes_user'))
responder_id = postgres.Column(postgres.Integer , postgres.ForeignKey('user.id'))
responder = postgres.relationship(User , backref=postgres.backref('votes_responder'))
timestamp_request = postgres.Column(postgres.DateTime , default=datetime.datetime.utcnow , nullable=False , unique=False)
timestamp_respond = postgres.Column(postgres.DateTime , default=datetime.datetime.utcnow , onupdate=datetime.datetime.utcnow , nullable=False , unique=False)
value = postgres.Column(postgres.Enum('up' , 'down' , name='vote_value_enum') , nullable=True)
SQLAlchemy is unable to discover the relationship path.
user_id = Column(ForeignKey('user.id'))
user = relationship(User, backref=backref('votes_user'))
responder_id = Column(ForeignKey('user.id'))
responder = relationship(User, backref=backref('votes_responder'))
Do the responder relationship must join using responder_id or user_id? I know it is obvious to us, but SQLAlchemy don't consider column names here. You can rename responder_id as foobar and it'll make no difference.
Define the foreign keys you want to use for each relationship.
user = relationship(User, foreign_keys=[user_id], backref=backref('votes_user'))
responder = relationship(User, foreign_keys=[responder_id], backref=backref('votes_responder'))

Set server_default value for relationship table

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()"))