NoForeignKeysError when creating object in SQLAlchemy Flask - python

I am a beginner with SQLAlchemy and I just did my first modules.py file for a Flask application. However, in the main app, when I try to create two objects of type user :
from models import user_presence,User,Activity_Presence
db.create_all()
u1 = User()
u2 = User()
I get the error that: sqlalchemy.exc.NoForeignKeysError: Can't find any foreign key relationships between 'activity_presence' and 'User_Presence'. I tried following the official tutorials, but I don't understand why there is an issue with the foreign key relationship. I also tried adding more fields, adding objects to the relationship, but I just can't figure out what the problem is. If you have any idea I would be very thankful. Sorry if the question is too much of a beginner one.
from api import db
user_presence = db.Table('User_Presence',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('presence_id', db.Integer, db.ForeignKey('Activity_Presence.id'), primary_key=True)
)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
presence_activ= db.relationship('Activity_Presence', secondary=user_presence, lazy='subquery',
backref=db.backref('users', lazy=True))
class Activity_Presence(db.Model):
id = db.Column(db.Integer, primary_key=True)

The error lies in the reference to the wrong table name.
The usual naming of the classes that represent the model is done in camelcase. This name is then converted to snakecase to provide the table name. If you should use an underscore in your class name, it will be retained and another one will be added for any subsequent capital letters.
As an an example:
class ActivityPresence(db.Model):
__tablename__ = 'activity_presence'
class Activity_Presence(db.Model):
__tablename__ = 'activity__presence'
So the working code is as follows.
user_presence = db.Table('user_presence',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('presence_id', db.Integer, db.ForeignKey('activity_presence.id'), primary_key=True)
)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
presence_activ= db.relationship('ActivityPresence',
secondary=user_presence,
lazy='subquery',
backref=db.backref('users', lazy=True))
class ActivityPresence(db.Model):
id = db.Column(db.Integer, primary_key=True)

Related

Flask-SQLAlchemy -- One model having two different relationships

I have three models: Role, User, and Post. Role -> User is one to many, and User -> Post is one to many. When I just had User and Post, everything worked. However, my website will need varying degrees of authorization.
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
users = db.relationship('User', backref='role', lazy=True)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
role_id = db.Column(db.Integer, db.ForeignKey('role.id'), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
Here is the error message I get:
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError)
no such column: user.role_id
Thanks!
You don't need to explicitly define the role_id field - the relationship defined in Role (and the backref parameter you specified) has already created a backwards relationship on User objects for you. (The SQLAlchemy documentation around backrefs may be helpful to you.)
As such, if you have an instantiated User object u, you should be able to get details about the user's role via u.role (which will give you a Role instance) and the ID of the role as u.role.id.
Thus, your full set of model definitions only needs to look like this:
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
users = db.relationship('User', backref='role', lazy=True)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
posts = db.relationship('Post', backref='author', lazy=True)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
If you do this, then for a Post object p, you can get the id of the role of the author of the post as p.author.role.id (for example).

Simple Many-to-Many issue in Flask-Admin

I'm adding Flask-Admin to an existing Flask app (using Python 3, and MySQL with SQLAlchemy), and I simply cannot figure out how to get a many-to-many relationship to render correctly. I've read a number of questions about this here, and it looks like I am following the right practices.
I have a Quotation table, a Subject table, and a QuotationSubject table, which has to be an actual class rather than an association table, but I don't care about the extra columns in the association table for this purpose; they're things like last_modified that I don't need to display or edit. The relationships seem to work in the rest of the application.
Trimming out the fields and definitions that don't matter here, I have:
class Quotation(db.Model):
__tablename__ = 'quotation'
id = db.Column(db.Integer, primary_key=True)
word = db.Column(db.String(50))
description = db.Column(db.Text)
created = db.Column(db.TIMESTAMP, default=db.func.now())
last_modified = db.Column(db.DateTime, server_default=db.func.now())
subject = db.relationship("QuotationSubject", back_populates="quotation")
def __str__(self):
return self.word
class Subject(db.Model):
__tablename__ = 'subject'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
created = db.Column(db.TIMESTAMP, default=db.func.now())
last_modified = db.Column(db.DateTime, server_default=db.func.now())
quotation = db.relationship("QuotationSubject", back_populates="subject")
def __str__(self):
return self.name
class QuotationSubject(db.Model):
__tablename__ = 'quotation_subject'
id = db.Column(db.Integer, primary_key=True)
quotation_id = db.Column(db.Integer, db.ForeignKey('quotation.id'), default=0, nullable=False)
subject_id = db.Column(db.Integer, db.ForeignKey('subject.id'), default=0, nullable=False)
created = db.Column(db.TIMESTAMP, default=db.func.now())
last_modified = db.Column(db.DateTime, server_default=db.func.now())
quotation = db.relationship('Quotation', back_populates='subject', lazy='joined')
subject = db.relationship('Subject', back_populates='quotation', lazy='joined')
In my admin.py, I simply have:
class QuotationModelView(ModelView):
column_searchable_list = ['word', 'description']
form_excluded_columns = ['created', 'last_modified']
column_list = ('word', 'subject')
admin.add_view(QuotationModelView(Quotation, db.session))
And that's it.
In my list view, instead of seeing subject values, I get the associated entry in the QuotationSubject table, e.g.
test <QuotationSubject 1>, <QuotationSubject 17>, <QuotationSubject 18>
book <QuotationSubject 2>
Similarly, in my create view, instead of getting a list of a dozen or so subjects, I get an enormous list of everything from the QuotationSubject table.
I've looked at some of the inline_models stuff, suggested by some posts here, which also hasn't worked, but in any case there are other posts (e.g. Flask-Admin view with many to many relationship model) which suggest that what I'm doing should work. I'd be grateful if someone could point out what I'm doing wrong.
First of all, I fear there's something missing from your question because I don't see the Citation class defined. But that doesn't seem to be the problem.
The most classic example of many-to-many relationships in Flask is roles to users. Here is what a working role to user M2M relationship can look like:
class RolesUsers(Base):
__tablename__ = 'roles_users'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
role_id = db.Column(db.Integer(), db.ForeignKey('role.id'))
class Role(RoleMixin, Base):
__tablename__ = 'role'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(80), unique=True)
def __repr__(self):
return self.name
class User(UserMixin, Base):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), index=True, unique=True)
roles = db.relationship('Role', secondary='roles_users',
backref=db.backref('users', lazy='dynamic'))
And in Flask-Admin:
from user.models import User, Role
admin.add_view(PVCUserView(User, db.session))
admin.add_view(PVCModelView(Role, db.session))
Note that the relationship is only declared once, with a backref so it's two-way. It looks like you're using back_populates for this, which I believe is equivalent.
For the case you're describing, it looks like your code declares relationships directly to the M2M table. Is this really what you want? You say that you don't need access to the extra columns in the QuotationSubject table for Flask-Admin. But do you need them elsewhere? It seems very odd to me to have a call to quotation or subject actually return an instance of QuotationSubject. I believe this is why Flask-Admin is listing all the QuotationSubject rows in the create view.
So my recommendation would be to try setting your relationships to point directly to the target model class while putting the M2M table as the secondary.
If you want to access the association model in other places (and if it really can't be an Association Proxy for some reason) then create a second relationship in each model class which explicitly points to it. You will then likely need to exclude that relationship in Flask-Admin using form_excluded_columns and column_exclude_list.

Define Many to Many without a foreignKey in SQLAlchemy

I have existing data that I want to model. Its essentially:
class Investor(db.Model):
id = db.Column(id, primary_key=True)
investments = db.relationship('Investments', backref='investor')
class Round(db.Model):
id = db.Column('id', primary_key=True)
investments = db.Table(
'investments',
db.Column('investor_id', db.ForeignKey('investor.id')),
db.Column('round_id', db.ForeignKey('round.id')),
)
Now, every time I try execute this little model, I get the following error:
expression 'Investments' failed to locate a name
I understand, that investments, needs to be a class, but I've tried making a dummy class with db.model, and it hasn't really worked. In that version I get problems with asking for a primary join or a mapper. I'm quite confused, and a little guidance would help greatly.
If the many to many relationship is between Investor and Round, You can define the model as follows:
class Investor(db.Model):
id = db.Column(db.Integer, primary_key=True)
rounds = db.relationship('Round', secondary=investments, backref='investor')
class Round(db.Model):
id = db.Column(db.Integer, primary_key=True)
investments = db.Table(
'investments',
db.Column('investor_id', db.Integer, db.ForeignKey('investor.id')),
db.Column('round_id', db.Integer, db.ForeignKey('round.id')))

How do I write a query to get sqlalchemy objects from relationship?

I am learning python and using the framework pyramid with sqlalchemy as the orm. I can not figure out how relationships work. I have 2 tables, offices and users. the foreign key is on the users table 'offices_id'. I am trying to do a query that will return to me what office a user is a part of.
This is how I have my models set up.
DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True, nullable=False)
trec_number = Column(Unicode(255), unique=True, nullable=False)
office_id = Column(Integer, ForeignKey('offices.id'))
class Office(Base):
__tablename__ = 'offices'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255), unique=True, nullable=False)
address = Column(Unicode(255), unique=True, nullable=False)
members = relationship("User", backref='offices')
In my view how would I write a query that would return the the office information for a given user?
I am trying this:
for user in DBSession.query(User).join(Office).all():
print user.address
but I think I am misunderstanding how the queries work because I keep getting errors
AttributeError: 'User' object has no attribute 'address'
when I do this:
for user in DBSession.query(User).join(Office).all():
print user.name
it prints out the users name fine since name is an attribute of the User class.
I also can not get the inverse to work
for offices in DBSession.query(Office).join(User).all():
print offices.users.name
You need to use the name you used in the backref argument to access the Office model. Try user.offices.address

How to specify relations using SQLAlchemy declarative syntax?

I can't find any proper documentation on how to specify relations
using the declarative syntax of SQLAlchemy.. Is it unsupported? That is, should I use the "traditional" syntax?
I am looking for a way to specify relations at a higher level, avoiding having to mess with foreign keys etc.. I'd like to just declare "addresses = OneToMany(Address)" and let the framework handle the details.. I know that Elixir can do that, but I was wondering if "plain" SQLA could do it too.
Thanks for your help!
Assuming you are referring to the declarative plugin, where everything I am about to say is documented with examples:
class User(Base):
__tablename__ = 'users'
id = Column('id', Integer, primary_key=True)
addresses = relation("Address", backref="user")
class Address(Base):
__tablename__ = 'addresses'
id = Column('id', Integer, primary_key=True)
user_id = Column('user_id', Integer, ForeignKey('users.id'))
Look at the "Configuring Relations" section of the Declarative docs. Not quite as high level as "OneToMany" but better than fully specifying the relation.
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
email = Column(String(50))
user_id = Column(Integer, ForeignKey('users.id'))

Categories