Flask models: access extra properties of many to many relationship - python

In my models.py in a flask app, I have the following tables:
class PackageBuild(db.Model, helpers.Serializer):
__tablename__ = "package_build"
package_id = db.Column(db.Integer, db.ForeignKey('package.id'))
build_id = db.Column(db.Integer, db.ForeignKey('build.id'))
git_hash = db.Column(db.Text)
version = db.Column(db.Text)
class Build(db.Model):
id = db.Column(db.Integer, primary_key=True)
packages = db.relationship(
'Package',
secondary='package_build',
back_populates='builds'
)
class Package(db.Model):
id = db.Column(db.Integer, primary_key=True)
builds = db.relationship(
'Build',
secondary='package_build',
back_populates='packages'
)
I know I can access properties of the related table:
build.packages[0].id
But how can I access properties (e.g. git_hash) of the intermediate table (ideally by using build instance)?

Related

SQLAlchemy: How to describe a many to many relationship with a bidirectional association object and composite foreign key?

I want to define a Many-to-Many Relationship with an Association Object like it's described in the SQLAlchemy Documentation. Additionally, my Project model uses a composite primary key.
I also referred to the various answers at another Stackoverflow Question and came up with this minimal example:
class User(Base):
__tablename__ = "user"
user_id = Column(String, primary_key=True)
subscriptions = relationship(
"Subscription", back_populates="user"
)
class Subscription(Base):
__tablename__ = "subscription"
__table_args__ = (
ForeignKeyConstraint(
['project_id', 'repo'],
['project.project_id', 'project.repo'],
),
)
user_id = Column(ForeignKey("user.user_id"), primary_key=True)
project_id = Column(ForeignKey("project.project_id"), primary_key=True)
repo = Column(ForeignKey("project.repo"), primary_key=True)
extra = Column(String(50))
user = relationship("User", back_populates="subscriptions")
project = relationship("Project", back_populates="subscribers", foreign_keys="[User.user_id, Project.project_id, Project.repo]")
class Project(Base):
__tablename__ = 'project'
project_id = Column(String, primary_key=True)
repo = Column(String, primary_key=True)
subscribers = relationship(
"Subscription", back_populates="project"
)
However it gives me a NoForeignKeysError at the Subscription.project relationship. What am I doing wrong here?

Many to many relationship in flask SQLAlchemy

I'm learning flask and i'm looking to implement a many to many realtionship. I searched on internet but there are different ways,
this is what i have tried so far
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
class Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
class Group_Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey(Group.id))
theory_id = db.Column(db.Integer, db.ForeignKey(Course.id))
I'm not sure this is the right way to do it. I'm having issues with delete endpoint. I think that i should add on delete cascade but i don't know how to do it.
there are some sites that add relationships to the table association, so the association table looks like that.
class Group_Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
group_id = db.Column(db.Integer, db.ForeignKey(Group.id))
course_id = db.Column(db.Integer, db.ForeignKey(Course.id))
course = db.relationship(Course, backref="course",cascade='all,
delete')
group = db.relationship(Group, backref="group", cascade='all,
delete`)
there are another examples where they are including a relationship field in both tables like that:
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
courses = db.relationship("Course", secondary=course_groups,
back_populates="courses")
class Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
groups = db.relationship("Group", secondary=course_groups,
back_populates="groups")
So i'm confused, which one is the most correct ?

Flask SQLAlchemy cannot find a related class

There are quite a few posts on this error but none of the answers apply to me, as far as I can see my code already conforms to the answers in the other posts.
I import my complete model and it has all classes, columns and relationships defined. In my code on the very first statement I execute I get this error:
InvalidRequestError: When initializing mapper mapped class Application->application, expression 'Version' failed to locate a name ("name 'Version' is not defined"). If this is a class name, consider adding this relationship() to the class after both dependent classes have been defined.
Interestingly the first statement I execute that "triggers" the error does not even involve the classes in the message. For the record it is user_mk = User.query.filter_by(user_name='mk').first().
The part of the model that errors is a simple 1-M relationship.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
...etc...
app = Flask(__name__)
app.config.from_object(TestConfig)
db = SQLAlchemy(app)
class User(UserMixin, db.Model)
__bind_key__ = 'app'
__tablename__ = 'user'
...etc...
class Version(db.Model):
__bind_key__ = 'app'
__tablename__ = 'version'
version_id = db.Column(db.Integer, primary_key=True)
version_number = db.Column(db.Integer, nullable=False)
release_number = db.Column(db.Integer, nullable=False)
modification_number = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
test_status = db.Column(db.Integer, index=True)
lifecycle = db.Column(db.Text, index=True, server_default=db.text("\"Current\""))
timestamp = db.Column(db.DateTime, server_default=db.text("CURRENT_TIMESTAMP"))
notes = db.Column(db.Text)
class Application(db.Model):
__bind_key__ = 'app'
__tablename__ = 'application'
application_id = db.Column(db.Integer, primary_key=True)
application_name = db.Column(db.Text)
version_id = db.Column(db.ForeignKey('version.version_id'), index=True)
description = db.Column(db.Text)
notes = db.Column(db.Text)
version = db.relationship('Version')
I cannot understand how SQLAlchemy cannot "see" the Version class from the Application class. What am I Doing wrong?
UPDATE
Following the suggestion in the answer below a different error occurs.
sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'mapped class Version->version'. Original exception was: Could not determine join condition between parent/child tables on relationship Version.applications - there are no foreign keys linking these tables. Ensure that referencing columns are associated with a ForeignKey or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
The code I tried is
class Version(db.Model):
__bind_key__ = 'app'
__tablename__ = 'version'
version_id = db.Column(db.Integer, primary_key=True)
version_number = db.Column(db.Integer, nullable=False)
release_number = db.Column(db.Integer, nullable=False)
modification_number = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
test_status = db.Column(db.Integer, index=True)
lifecycle = db.Column(db.Text, index=True, server_default=db.text("\"Current\""))
timestamp = db.Column(db.DateTime, server_default=db.text("CURRENT_TIMESTAMP"))
notes = db.Column(db.Text)
applications = db.relationship('Application',
backref='version',
primaryjoin='Version.version_id == Application.version_id',
)
class Application(db.Model):
__bind_key__ = 'app'
__tablename__ = 'application'
application_id = db.Column(db.Integer, primary_key=True)
application_name = db.Column(db.Text)
version_id = db.Column(db.ForeignKey('version.version_id'), index=True)
description = db.Column(db.Text)
notes = db.Column(db.Text)
ANSWER
Thanks #djnz. The answer that works is as follows, but it is not clear why. Observe that the primaryjoin clause under Version has been replaced with a foreign_keys clause.
class Version(db.Model):
__bind_key__ = 'app'
__tablename__ = 'version'
version_id = db.Column(db.Integer, primary_key=True)
version_number = db.Column(db.Integer, nullable=False)
release_number = db.Column(db.Integer, nullable=False)
modification_number = db.Column(db.Integer, nullable=False, server_default=db.text("0"))
test_status = db.Column(db.Integer, index=True)
lifecycle = db.Column(db.Text, index=True, server_default=db.text("\"Current\""))
timestamp = db.Column(db.DateTime, server_default=db.text("CURRENT_TIMESTAMP"))
notes = db.Column(db.Text)
applications = db.relationship('Application',
backref='version',
foreign_keys="Application.version_id",
)
class Application(db.Model):
__bind_key__ = 'app'
__tablename__ = 'application'
application_id = db.Column(db.Integer, primary_key=True)
application_name = db.Column(db.Text)
version_id = db.Column(db.ForeignKey('version.version_id'), index=True)
description = db.Column(db.Text)
notes = db.Column(db.Text)
This is likely where backref or back_populates is needed. These are used by SQLAlchemy to map the relationship.
Using backref (on your Version model):
applications = db.relationship("Version", backref="version")
This is the same as writing:
# Version model:
applications = db.relationship("Application", back_populates="version")
# Application model:
version = db.relationship("Version", backref="applications")
This mapping then adds the parent/child reference to the model, like my_version.applications, or my_application.version

Association table in flask-sqlalchemy

I want to design such an application which has users and projects, user can be a candidate of projects, and can be chosen as participant of projects. So I use the code below:
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True)
class Participate(db.Model):
__tablename__ = 'participates'
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'),
primary_key=True)
candidate_id = db.Column(db.Integer, db.ForeignKey('users.id'),
primary_key=True)
participant_id = db.Column(db.Integer, db.ForeignKey('users.id'),
primary_key=True)
candidate_timestamp = db.Column(db.DateTime, default=datetime.utcnow)
participate_timestamp = db.Column(db.DateTime, default=datetime.utcnow)
class Project(db.Model):
__tablename__ = 'projects'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
candidates = db.relationship('Participate',
foreign_keys=[Participate.candidate_id],
backref=db.backref('candidate_projects', lazy='joined'),
lazy='dynamic')
participants = db.relationship('Participate',
foreign_keys=[Participate.participant_id],
backref=db.backref('participate_projects', lazy='joined'),
lazy='dynamic')
then I tried to create some data in shell:
# python manage.py shell
>>> db
<SQLAlchemy engine='mysql://connection_uri?charset=utf8&use_unicode=0'>
>>> Project
<class 'app.models_.Project'>
>>> User
<class 'app.models_.User'>
>>> Participate
<class 'app.models_.Participate'>
>>> jerry = User(username='jerry')
I got this exception:
NoForeignKeysError: Could not determine join condition between parent/child
tables on relationship Project.candidates - there are no foreign keys linking
these tables. Ensure that referencing columns are associated with a ForeignKey
or ForeignKeyConstraint, or specify a 'primaryjoin' expression.
I'm new to sqlalchemy , what is the right way to design a database like what I want?
The candidate_id and participant_id attributes are User foreign keys. You are setting up relationships with the Project table. You need to move those to the User table and then they'll work.
Then if you need to get all the candidates or participants of a project, you can use these relationships and filter them by the project you are interested in.

flask-migrate wants to drop my indeces

I've got a Flask app with the following models:
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
password_hash = db.Column(db.String(128))
city_id = db.Column(db.Integer, db.ForeignKey('cities.id'))
class City(db.Model):
__tablename__ = 'cities'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
user_ids = db.relationship('User', backref='city', lazy='dynamic')
I've run a migration to specify my indices and foreign key constraints:
def upgrade():
op.create_foreign_key('fk_user_city', "users", "cities", ["city_id"], ["id"])
op.create_index('city_idx', 'users', ['city_id'])
However, any time I create another new migration Alembic seems to want to drop my indexes.
Is there a way to freeze Alembic's autogeneration at the current DB/Model schema?
Check this page. You will need to change env.py under migrations folder.
EnvironmentContext.configure.include_object
or
EnvironmentContext.configure.include_schemas
should be what you are looking for.

Categories

Resources