Working with back_populates fields in flask-admin and sqlalchemy - python

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?

Related

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?

SQLAlchemy one-to-many relationship - how to properly get the 'many' collection

I'm not sure I properly understand how to get the collection part of the one-to-many relationship.
class ProjectReport(db.Model):
__tablename__ = "project_reports"
id = db.Column(UUID, primary_key=True, default=uuid.uuid4)
project_id = db.Column(UUID, db.ForeignKey("projects.id"), nullable=False)
entries = db.relationship("ProducerEntry", backref="project_report", lazy="dynamic")
class ProducerEntry(Entry):
__tablename__ = "producer_entries"
__mapper_args__ = {"polymorphic_identity": "Entry"}
id = db.Column(UUID, db.ForeignKey("entries.id"), primary_key=True)
project_id = db.Column(UUID, db.ForeignKey("projects.id"), nullable=False)
project_report_id = db.Column(UUID, db.ForeignKey("project_reports.id"), nullable=True)
My problem is that I can't just access the entries field.
for entry in self.entries:
do_something(entry)
This returns NotImplementedError
I managed to get the data via hybrid property but that seems a bit of an overkill since already have the relationship, also it'd get a bit complex for further logic later on.
#hybrid_property
def entries(self):
return ProducerEntry.query.filter_by(project_report_id=self.id)
Ab additional information is that the ProjectReport is basically the common columns of the Entry and Project models, and the project_report_id is nullable, because the entries and projects are generated first and then I can generate the project reports from them. This is how I create the reports:
...
project_report = ProjectReport(date_order=entry.date_order, project_id=entry.project.id)
project_report.entries.append(entry)
...
As far as I know I don't have to add the project_report_id to the producer entry after this.
What am I missing here?
Well yeah, that relationship field returns a query, so I simply should have called:
self.entries.all()
Or anything else which is handling a query.

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.

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

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.

SQLAlchemy: Dynamically loaded backreference to another module

Let's suppose that I have a User model in one module.
class User(Model):
id = Column(Integer, primary_key=True)
Then I want to add a dynamically-loaded, many-to-one relationship towards User from a Post model in another module. Also, I don't want to 'pollute' the User's model definition with relationships from this other module.
Is there a cleaner way of doing this other than adding a field to the User class from outside of the Post model, like this?
class Post(Model):
user_id = Column(Integer, ForeignKey('user.id'))
User.posts = relationship('Post', backref='user', lazy='dynamic')
Thanks
Well, you can define it in the Post model (see below)
class Post(Model):
user_id = Column(Integer, ForeignKey('user.id'))
user = relationship('User', backref=backref('posts', lazy='dynamic'))

Categories

Resources