How to "resolve" foreign keys in SQLAlchemy query results? - python

I've written a small tool that creates SQLAlchemy code from a written specification. The tool can create code like this:
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
address_id = db.Column(db.Integer, db.ForeignKey('address.id'))
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
I can insert new records, I can query them, that works like a charm, but I would really like to not having to manually look for the Address of a Person.
I want to be able to access the address belonging to a specific person through the Person object. If I understand the examples correctly I need to do it like this (example based on http://flask-sqlalchemy.pocoo.org/2.1/models/#one-to-many-relationships)?
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
address = db.relationship('Address', backref='person',
lazy='dynamic')
class Address(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50))
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
This looks to me all backwards, I have to give every Address a "link" to a Person and then backref so I can have an addresses for a person? Is this not possible to do directly from Person?
Also, creating code for this is much more complicated, so I'd really like to avoid that.

In the first example the Person can have exactly 1 address, but each address can be used by multiple Persons.
In your second solution Person can now have multiple addresses, but each address is unique to a given Person (note that the ForeignKey moved tables).
So you went from N:1 to 1:N. By saying you want a list of the N-side, rather than the instance on the 1-side, you now have to specify more.

Related

How do I associate two foreign keys to separate instances of the same parent model in SQLAlchemy?

I am working on a user to user 'Challenge' model in SQLAlchemy that needs to be linked to both the sending player, as well as the receiving player. I.e one user sends a challenge to another user, and users can see all of their sent challenges along with their received challenges. I initially attempted to solve this using an association table with no luck. I have since realized that an association table is unnecessary, however, I am unable to join the two tables as desired without receiving this error: Could not determine join condition between parent/child tables on relationship User.sent_challenges - there are multiple foreign key paths linking the tables.
I have read through the documentation and all similar problems that I could find through these forums but none seem to fix my problem. Below is the current implementation of my code. It is far from the only attempt I have made, however, I believe that it most accurately portrays what I am attempting to accomplish.
Challenge model:
class Challenge(db.Model):
__tablename__ = 'challenges'
id = Column(Integer, primary_key=True)
time = Column(Integer)
player_1_score = Column(Integer, nullable=True)
player_2_score = Column(Integer, nullable=True)
sender_id = Column(Integer, ForeignKey('users.id'), nullable=False)
receiver_id = Column(Integer, ForeignKey('users.id'), nullable=False)
sender = relationship("User", back_populates="sent_challenges")
receiver = relationship("User", back_populates="received_challenges")
User model:
class User(db.Model):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
user_name = Column(String(14), nullable=False)
level = Column(Integer)
high_score = Column(Integer)
points = Column(Integer)
sent_challenges = relationship("Challenge", back_populates="sender", cascade="all, delete")
received_challenges = relationship("Challenge", back_populates="receiver", cascade="all, delete")
Any insight would be greatly appreciated.
Add the foreign_keys param so sqlalchemy knows which foreign key belongs to which relationship:
sender = relationship("User", foreign_keys=[sender_id], back_populates="sent_challenges")
receiver = relationship("User", foreign_keys=[receiver_id], back_populates="received_challenges")
This is explained here with addresses that mirror your sender/receiver ambiguity:
handling-multiple-join-paths

Returning value from other class with help of relationship in flask

I'm struggling to set up a very simple relationship in flask.
I'm having one class called Transaction, and one called Instrument. Transaction has an attribute called name, and Instrument has two: name and name_ver2.
Now, when calling Transaction.name, I want it to return name_ver2 through the following process:
Name to look for: Transaction.name
Find: Instrument.name
Return: Instrument.name_ver2
where Instrument.name and Instrument.name are the keys that share the same value, and thus is used as the "shared" (mapping?) value.
The following does not work.
class Transaction(db.Model):
id = db.Column(db.Integer(), primary_key=True)
...
name = db.relationship('Instrument', lazy=True)
class Instrument(Transaction):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(length=10), nullable=False, unique=False)
name_ver2 = db.Column(db.String(length=10), nullable=False, unique=False)
Is this possible using relationships, or is there another type of functionality that I'm missing?

Working with back_populates fields in flask-admin and sqlalchemy

Consider a simple many-to-one model like this:
class Entity(Base):
__tablename__ = 'entity'
id = Column(Integer, Sequence('entity_seq'), primary_key=True)
name = Column(String(50), nullable=False)
persons = relationship('Person', back_populates='entity')
def __str__(self):
return self.name
class Person(Base):
__tablename__ = 'person'
id = Column(Integer, Sequence('person_seq'), primary_key=True)
entity_key = Column(ForeignKey('entity.id'), nullable=False)
last_name = Column(String(30), nullable=False)
first_name = Column(String(30), nullable=False)
entity = relationship('Entity', back_populates='persons')
def __str__(self):
return f'{self.first_name} {self.last_name}'
In other words, many persons belong to one entity.
If you use a flask-admin view like this:
admin.add_view(ModelView(Entity, db.session))
You might get a list like this:
Editing one of these entries can produce this output:
This presents some problems:
The persons field can be very large and take a long time to fill and probably needs to be paginated, but I can't find a way in flask-admin to cause that pagination.
Individual persons can be deleted (via the "x") but that violates the database nullable constraint on the column. It seems like flask-admin shouldn't allow that by default, or there should be a way to control it.
The persons are formatted via the __str__ attribute, but it may be necessary to format them some other way, but I can't find a way in flask-admin to do that.
What do you do in flask-admin to address these problems?

sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers

This error happened when I tried to get access to the page. I didn't get errors when I created the tables, but seems like there are problems still.
The models are like this:
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
sell_items = db.relationship('Item', backref='user')
class Item(db.Model):
id = db.Column(db.Integer, primary_key=True)
item_name = db.Column(db.String(64), index=True)
item_image = db.Column(db.String(200), index=True)
price = db.Column(db.Float(10), index=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User', backref='sell_items')
The whole error message is this
Triggering mapper: 'Mapper|User|user'. Original exception was: Error creating backref 'user' on relationship 'User.sell_items': property of that name exists on mapper 'Mapper|Item|item'
How can I fix this? What I want to do is to refer to username who sells the item, but I cannot. There is a problem with the relationships between the models.
When you use backref the backwards relationship is automatically created, so it should only be used in one side of the relationship. In your case, you can remove the sell_items in the User model and the User model will automatically get a relationship from Item.
To declare the relationshiop on both sides (in case you want to customize its name, for example, use back_populates='name_of_relationship_on_other_model'.
in your Item class, replace this line
user = db.relationship('User', backref='sell_items')
with this line
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
it should work that way, from there you can query like this item = Item.query.first(), then item.sell_items... to get the user who posted the item.
i hope it helps.

Why do I need to supply any arguments to Flask-SQLAlchemy's Column constructor?

In the basic examples of Flask-SQLAlchemy usage to define data models, type and other attributes of each column are specified; but much of this seems redundant and appears in fact to be ignored.
For example I seem to be able to replace
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
with nothing but the specification of the primary_key
class User(db.Model):
id = db.Column(primary_key=True)
username = db.Column()
email = db.Column()
or even values that don't correspond to the underlying database table
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.Integer, unique=False)
email = db.Column(db.Integer, unique=False)
and still get a model that corresponds to the database (as specified in the first example).
Are any of the arguments to Column necessary for an existing database? If not, what purpose do they serve (especially since they seem to be ignored)?

Categories

Resources