I'm using Flask and Peewee to create models for Users and Appointments, and each of them have a ForeignKeyField referencing each other. The problem is, if I define one above the other, Flask will give me x is not defined.
For example:
class User(Model):
appointments = ForeignKeyField(Appointment, related_name='appointments')
class Appointment(Model):
with_doctor = ForeignKeyField(User, related_name='doctor')
This would return 'User' is not defined. How am I able to fix this problem?
You generally don't define the foreign key on both sides of the relationship, especially when the relationship isn't a one-to-one.
You should also set the related_name to something that makes sense on the related model. user.appointments is probably a better name for accessing a User's Appointments than user.doctor.
class User(Model):
pass
class Appointment(Model):
with_doctor = ForeignKeyField(User, related_name='appointments')
You can define it as a string as far as i know:
class User(Model):
appointments = ForeignKeyField("Appointment", related_name='appointments')
class Appointment(Model):
with_doctor = ForeignKeyField(User, related_name='doctor')
if the declaration is before the creation, you have the option to write it as a string.
I'm not sure, i think that the bind is at runtime.
try some changes like this, an example for Names & Addresses :
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
addresses = db.relationship('Address', backref='person', lazy='dynamic')
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
as far as i know, you should use db.ForeignKey() for a relationship like this ... ForeignKeyField() defined in django
Related
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.
I have two declarative sqlalchemy models.
class Users(Base):
__tablename__ = 'Users'
ID = Column(INTEGER, primary_key=True)
_Activities = relationship('Activities', lazy='subquery')
class UserCourseActivities(Base):
__tablename__ = 'Activities'
ActivityID = Column(INTEGER, primary_key=True)
UserID = Column(INTEGER, ForeignKey('Users.ID'))
ActivityCount = Column(INTEGER)
Is there a way to have each instance of Users have a total (activity count) in their __dict__? I've tried adding other class attributes, but I fear I might have to use classical mappings. The Users table has a lot of relations that make the declarative method much more attractive. Is there any way to accomplish this?
Can I use the #column_property decorator? I have no idea how to actually use it though.
Turns out that column property isn't a decorator.
activity_total = column_property(
select(
[func.sum(
Activities.ActivityCount
)
]).\
where(Activities.UserID==PK1).\
correlate_except(Activities)
) #This is officially the ugliest thing I have ever seen
This 'column' shows up in the User instances __dict__ too.
I am currently trying to create the following database schema with SQLAlchemy (using ext.declarative):
I have a base class MyBaseClass which provides some common functionality for all of my publicly accessible classes, a mixin class MetadataMixin that provides functionality to query metadata from imdb and store it.
Every class that subclasses MetadataMixin has a field persons which provides a M:N relationship to instances of the Person class, and a field persons_roles which provides a 1:N relationship to an object (one for each subclass) which stores the role a concrete Person plays in the instance of the subclass.
This is an abbreviated version of what my code looks like at the moment:
from sqlalchemy import Column, Integer, Enum, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class MyBaseClass(object):
"""Base class for all publicly accessible classes"""
id = Column(Integer, primary_key=True)
class Person(MyBaseClass):
"""A Person"""
name = Column(Unicode)
movies = association_proxy('movie_roles', 'movie',
creator=lambda m: _PersonMovieRole(movie=m))
shows = association_proxy('show_roles', 'show',
creator=lambda s: _PersonShowRole(show=s=))
class _PersonMovieRole(Base):
"""Role for a Person in a Movie"""
__tablename__ = 'persons_movies'
id = Column(Integer, primary_key=True)
role = Column(Enum('none', 'actor', 'writer', 'director', 'producer'),
default='none')
person_id = Column(Integer, ForeignKey('persons.id'))
person = relationship('Person', backref='movie_roles')
movie_id = Column(Integer, ForeignKey('movies.id'))
movie = relationship('Movie', backref='persons_roles')
class _PersonShowRole(Base):
"""Role for a Person in a Show"""
__tablename__ = 'persons_shows'
id = Column(Integer, primary_key=True)
role = Column(Enum('none', 'actor', 'writer', 'director', 'producer'),
default='none')
person_id = Column(Integer, ForeignKey('persons.id'))
person = relationship('Person', backref='show_roles')
show_id = Column(Integer, ForeignKey('shows.id'))
show = relationship('Episode', backref='persons_roles')
class MetadataMixin(object):
"""Mixin class that provides metadata-fields and methods"""
# ...
persons = association_proxy('persons_roles', 'person',
creator= #...???...#)
class Movie(Base, MyBaseClass, MetadataMixin):
#....
pass
What I'm trying to do is to create a generic creator function for association_proxy that creates either a PersonMovieRole or a PersonShowRole object, depending on the class of the concrete instance that a Person is added to. What I'm stuck on at the moment is that I don't know how to pass the calling class to the creator function.
Is this possible, or is there maybe even an easier way for what I'm trying to accomplish?
By the time your persons field is defined, you cannot really know what class it will end up in. Python takes up ready dictionaries of class members and creates classes out of them (via type.__new__), but when it happens, those members are already fully defined.
So you need to provide the required information directly to the mixin, and tolerate the small duplication it will create in your code. I'd opt for interface similar to this one:
class Movie(Base, MyBaseClass, MetadataMixin('Movie')):
pass
(You cannot have MetadataMixin(Movie) either, for the exact same reasons: Movie requires its base classes to be completely defined by the time the class is created).
To implement such "parametrized class", simply use a function:
def MetadataMixin(cls_name):
"""Mixin class that provides metadata-fields and methods"""
person_role_cls_name = 'Person%sRole' % cls_name
person_role_cls = Base._decl_class_registry[person_role_cls_name]
class Mixin(object):
# ...
persons = association_proxy('persons_roles', 'person',
creator=person_role_cls)
return Mixin
This works because what we're looking up in Base._decl_class_registry - the registry of all classes descending from your declarative base - is not the final class (e.g. Movie), but the association object (e.g. PersonMovieRole).
It will be simplest to explain with code example, in Python I can do so to achieve model inheritance:
"""Image model"""
from sqlalchemy import Column, ForeignKey
from sqlalchemy.types import Integer, String, Text
from miasto_3d.model.meta import Base
class Image(Base):
__tablename__ = "image"
image_id = Column(Integer, primary_key=True)
path = Column(String(200))
def get_mime(self):
#function to get mime type from file
pass
"""WorkImage model"""
class WorkImage(Image, Base):
__tablename__ = "work_images"
image_id = Column(Integer, ForeignKey("image.image_id"), primary_key=True)
work_id = Column(Integer, ForeignKey("work.id"))
work = relation("Work", backref=backref('images',order_by='WorkImage.work_id'))
"""UserAvatar model"""
class UserAvatar(Image, Base):
__tablename__ = "user_avatars"
image_id = Column(Integer, ForeignKey("image.image_id"), primary_key=True)
user_id = Column(Integer, ForeignKey("user.id"))
user = relation("User", backref=backref('images',order_by='UserAvatar.user_id'))
How I do similar things in Rails? Or maybe there is another, better way to do it?
I know paperclip, but I don't like it's conception to use shared table to store photo and model data.
It looks like you're wanting either a polymorphic association or perhaps single table inheritance.
Since you don't define database fields in the model, you cannot inherit database schema in this way - all your fields will need to be specified per table in a migration. You probably should use paperclip, if only because reinventing the wheel is a pain. It works really well, and abstracts away from the actual database structure for you.
In Rails, rather than model inheritance, shared functionality tends to be implemented in modules, like so:
http://handyrailstips.com/tips/14-drying-up-your-ruby-code-with-modules
I'm new to python(sqlalchemy), and I'm learning to build web site with pylons and sqlalchemy.
I have a problem when I declare the relationship between models. I've tried it several hours, but failed. But I think it should be a basic question.
I have two classes: User and Article, user can create articles, and modified the other people's article(like wiki).
So a user has created-articles and edited-articles.
class Article(Base):
__tablename__ = 'articles'
id = Column(Integer, primary_key=True)
title = ...
user_id = Column(Integer, ForeignKey('users.id'))
editor_id = Column(Integer, ForeignKey('users.id'))
# relations
user = relationship('User', backref='articles') # -> has error
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String(20))
def __init__(self):
pass
But there is an error displayed:
InvalidRequestError: One or more mappers failed to compile. Exception was probably suppressed within a hasattr() call. Message was: Could not determine join condition between parent/child tables on relationship Article.user. Specify a 'primaryjoin' expression. If this is a many-to-many relationship, 'secondaryjoin' is needed as well.
I tried to add primaryjoin to the line('has error'), but don't know what it should be. I tried some codes, but none works.
Thank you in advance!
Ah, thats obvious one.
Article class has two references to User, user_id and editor_id, so SQLA does not know which one of them to use for your relation. Just use explicit primaryjoin:
user = relation('User', backref='articles', primaryjoin="Article.user_id==User.id")