SqlAlchemy error: Foreign key could not find table - mysql

So I'm building an app and I'm trying to save new changes to my database but when I try to commit the changes in the flask using db.session.commit() it returns me the following error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'products.country_id' could not find table 'countries' with which to generate a foreign key to target column 'id'
In my models.py I have the following:
from app import db
from . import db
from datetime import datetime
def now():
return datetime.now()
class Countries(db.Model):
__tablename__ = 'countries'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
code = db.Column(db.String(45))
def __repr__(self):
return f'Id {self.id}'
class Categories(db.Model):
__tablename__ = 'categories'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
def __repr__(self):
return f'Id {self.id}'
class Brands(db.Model):
__tablename__ = 'brands'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
logo = db.Column(db.String(5000))
feed = db.Column(db.String(5000))
feed_type = db.Column(db.String(45))
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
country_id = db.Column(db.Integer, db.ForeignKey('countries.id'))
awinmid = db.Column(db.Integer)
def __repr__(self):
return f'Id {self.id}'
class Products(db.Model):
__tablename__ = 'products'
__table_args__ = {'schema': 'products_data'}
id = db.Column(db.Integer, primary_key=True, unique=True, autoincrement=True)
name = db.Column(db.String(255))
url = db.Column(db.Text)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
country_id = db.Column(db.Integer, db.ForeignKey('countries.id'))
price = db.Column(db.Float)
currency = db.Column(db.String(45))
discount_price = db.Column(db.Float)
shipping = db.Column(db.Float)
brand_id = db.Column(db.Integer, db.ForeignKey('brands.id'))
Am I doing anything wrong when associating a column in products with a foreign key? This is the first time I encounter this error so I'm really lost on what to do right now.

To fix I just added the schema to the db.ForeignKey and it worked
Example:
db.ForeignKey('products_data.countries.id')
PS:
Not my idea. Just wanted to post the answer in case someone visits the post later with the same problem.
Gord Thompson thanks for the help!

First of all, I don t see any table Categories. Secondly, you copy pasted your schema from the Products table into your Countries one.
PS: By default sqlalchemy gives the tables the name of the class (lower cased). So your __tablename__='products' does nothing actually.
EDIT:
The problem with your code lies in how you set the __table_args__ attribute. You assign an object to it, which by their specifications is wrong.
Take a look at the following example and modify your code accordingly
__table_args__ = ({'schema': 'products_data'})
Also for further reference, take a look at this https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/table_config.html

Related

SQLAlchemy - how to make column unique per ForeignKey relationship?

In the Todo model below there is a unique constraint put on text column.
How can I narrow this constraint to validate uniqueness per "foreign-keyed" user only, not per all users as it is now?
I use SQLite.
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.orm import relationship
from database import Base
class Todo(Base):
__tablename__ = 'todos'
id = Column(Integer, primary_key=True, index=True)
text = Column(String, unique=True)
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="todos")
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
todos = relationship("Todo", back_populates="user")
If you use postgresql, you can use the Partial Index to implement this:
class Todo(Base):
__tablename__ = 'todos'
id = Column(Integer, primary_key=True, index=True)
text = Column(String) # ! removed the unique from here
user_id = Column(Integer, ForeignKey('users.id'))
user = relationship("User", back_populates="todos")
# ! added unique index with
__table_args__ = (
Index(
"todo_text_uc",
"text",
unique=True,
postgresql_where=(user_id != None),
# postgresql_where=(~user_id.is_(None)), # equivalent to the row above, but no linting warnings
),
)
For sqlite just replace postgresql_where with sqlite_where.

Import data from a joined table as a current (readable) column in SQLAlchemy?

I have this schema:
class Company(db.Model):
__tablename__ = 'companies'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(250), nullable=True, default=None)
domain = db.Column(db.String(250), nullable=True, default=None)
organization_id = db.Column(db.Integer, db.ForeignKey('organizations.id'), nullable=False)
class Contact(db.Model):
__tablename__ = 'contacts'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(250), nullable=True, default=None)
email = db.Column(db.String(250), nullable=False)
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=True, default=None)
company = relationship('Company')
organization_id = db.Column({Import Company.organization_id as eager})
The last line is of course garbage, but it's to show the idea:
I'd like to have the value "organization_id" available in Contact, even though it's not present in the table "contacts", but since it's present in "companies", is there a way to ask SQLAlchemy to load the value from "companies" via a JOIN, and affect it to "contacts" as a read-only value?
That way, when I search for a contact, for instance :
contact = Contact.query.filter(Contact.email = 'test#test.com').first()
print(contact.organization_id) # => 1
Thank you.
You can use the hybrid_property decorator to define an attribute on your class:
class Contact(db.Model):
...
#hybrid_property
def organization_id(self):
return self.company.organization_id if self.company else None
Using contact.organization_id will load the company using the foreign key relationship.

duplicate records in my results

my model defines like this
class UserAppWeekStatistics(db.Model):
__tablename__ = 'user_app_week_statistics'
id = db.Column(db.Integer, primary_key=True)
imei = db.Column(db.String(15), primary_key=True, index=True)
year = db.Column(db.Integer, primary_key=True,
default=int(datetime.now().strftime('%Y')), index=True)
week = db.Column(db.Integer, primary_key=True,
default=int(datetime.now().strftime('%W')), index=True)
count = db.Column(db.Integer, default=0)
def __repr__(self):
return '<UserAppWeekStatistics %r-%r-%r>' % (self.imei, self.year, self.week)
and in my views
user_app_week_statistics = UserAppWeekStatistics.query.filter_by(imei=json_req['imei'],
year=int(datetime.now().strftime('%Y')),
week=int(datetime.now().strftime('%W'))).first()
if user_app_week_statistics is None:
user_app_week_statistics = UserAppWeekStatistics()
user_app_week_statistics.imei = json_req['imei']
user_app_week_statistics.count = 1
else:
user_app_week_statistics.count += 1
db.session.add(user_app_week_statistics)
db.session.commit()
and bellow is capture my results in my database.
In my opinion, there would be just one record with same imei, year and week, but in my project there may be many duplicate records with same imei ,year and week. I am really confused.My database is mysql 5.6.
1) Fix Your model, there can be only 1 primary_key.
2) No need for index on every field, especially on statistics tables - it makes inserts, updates slower, cuz every time db engine does re-indexing.
3) add unique composite index to prevent duplications.
finally here is Your model:
class UserAppWeekStatistics(db.Model):
__tablename__ = 'user_app_week_statistics'
id = db.Column(db.Integer, primary_key=True)
imei = db.Column(db.String(15), index=True)
year = db.Column(db.Integer, default=int(datetime.now().strftime('%Y')))
week = db.Column(db.Integer, default=int(datetime.now().strftime('%W')))
count = db.Column(db.Integer, default=0)
# add this
__table_args__ = db.UniqueConstraint('imei', 'year', 'week', name='uq_user_app_week_statistics_imei_year_week')
def __repr__(self):
return '<UserAppWeekStatistics %r-%r-%r>' % (self.imei, self.year, self.week)

Flask-Migrate sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column

I am using Flask-Migrate in my application, with the following models:
listpull/models.py
from datetime import datetime
from listpull import db
class Job(db.Model):
id = db.Column(db.Integer, primary_key=True)
list_type_id = db.Column(db.Integer, db.ForeignKey('listtype.id'),
nullable=False)
list_type = db.relationship('ListType',
backref=db.backref('jobs', lazy='dynamic'))
record_count = db.Column(db.Integer, nullable=False)
status = db.Column(db.Integer, nullable=False)
sf_job_id = db.Column(db.Integer, nullable=False)
created_at = db.Column(db.DateTime, nullable=False)
compressed_csv = db.Column(db.LargeBinary)
def __init__(self, list_type, created_at=None):
self.list_type = list_type
if created_at is None:
created_at = datetime.utcnow()
self.created_at = created_at
def __repr__(self):
return '<Job {}>'.format(self.id)
class ListType(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=False)
def __init__(self, name):
self.name = name
def __repr__(self):
return '<ListType {}>'.format(self.name)
run.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from listpull import app, manager
manager.run()
listpull/__init__.py
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand
from mom.client import SQLClient
from smartfocus.restclient import RESTClient
app = Flask(__name__)
app.config.from_object('config')
db = SQLAlchemy(app)
migrate = Migrate(app, db)
manager = Manager(app)
manager.add_command('db', MigrateCommand)
...
import listpull.models
import listpull.views
I initialized the database using ./run.py db init and then I run ./run.py db migrate and I get the following error:
sqlalchemy.exc.NoReferencedTableError: Foreign key associated with column 'job.list_type_id' could not find table 'listtype' with which to generate a foreign key to target column 'id'
What am I doing wrong here?
You are letting Flask-SQLAlchemy choose the names for your tables. If I remember correctly, for a class called ListType the table name will be list_type (or something similar), not the listtype that you specified in your foreign key.
My recommendation is that you specify your own table names using __tablename__, that way they are explicit in the code and not magically determined for you. For example:
class Job(db.Model):
__tablename__ = 'jobs'
id = db.Column(db.Integer, primary_key=True)
list_type_id = db.Column(db.Integer, db.ForeignKey('list_types.id'),
nullable=False)
# ...
class ListType(db.Model):
__tablename__ = 'list_types'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=False)
# ...
I was looking for a solution for similar information problem you deal with for a long time as well... at the end, the solution i found was add into every "table-class" the line __tablename__ = '<your_table_name>'
it seems to help flask to find your table
In my case, the issue was that i accidentally called the foreign key by the class name vs the table name ...
Like: db.Column(db.Integer, db.ForeignKey('ClassListTypes.id') vs db.Column(db.Integer, db.ForeignKey('list_types.id')

how to save data in a many to many relationship using turbogears and sqlalchemy

hi i have a many to many relationship between a user and a group.and i will like to add a user with many groups in my database.how do i do that if my database is as follows
user_group_table = Table('tg_user_group', metadata,
Column('user_id', Integer, ForeignKey('tg_user.user_id',
onupdate="CASCADE", ondelete="CASCADE")),
Column('group_id', Integer, ForeignKey('tg_group.group_id',
onupdate="CASCADE", ondelete="CASCADE"))
)
class Group(DeclarativeBase):
"""
Group definition for :mod:`repoze.what`.1
Only the ``group_name`` column is required by :mod:`repoze.what`.
"""
__tablename__ = 'tg_group'
#{ Columns
group_id = Column(Integer, autoincrement=True, primary_key=True)
group_name = Column(Unicode(16), unique=True, nullable=False)
display_name = Column(Unicode(255))
created = Column(DateTime, default=datetime.now)
#{ Relations
users = relation('User', secondary=user_group_table, backref='groups')
#{ Special methods
def __repr__(self):
return '<Group: name=%s>' % self.group_name
def __unicode__(self):
return self.group_name
#}
class User(DeclarativeBase):
"""
User definition.
This is the user definition used by :mod:`repoze.who`, which requires at
least the ``user_name`` column.
"""
__tablename__ = 'tg_user'
#{ Columns
user_id = Column(Integer, autoincrement=True, primary_key=True)
user_name = Column(Unicode(16), unique=True, nullable=False)
email_address = Column(Unicode(255), unique=True, nullable=False,
info={'rum': {'field':'Email'}})
display_name = Column(Unicode(255))
_password = Column('password', Unicode(80),
info={'rum': {'field':'Password'}})
created = Column(DateTime, default=datetime.now)
doing it this way however gives me an error
#expose()
def user_save(self, **kw):
user = User()
user.user_name = kw['user_name']
user.display_name = kw['display_name']
user.email_address = kw['Email']
user._password = kw['password']
user.groups.extend(kw['groups'])
DBSession.add(user)
DBSession.flush()
flash("successfully saved...")
flash(user)
redirect("/user_new")
pls help me solve this.thanks in advance
I believe the answer is in the error message that you havn't posted in the question. user.groups is a list of Group objects, while you assign a list of strings(?) got from form to it. Also I see no explicit DBSession.commit() call. Are you sure TurboGears will do it for you?