how to translate inside a loop using flask-babel - python

I'm having a hard time figuring out how to use flask-babel to translate variables inside a loop. Give this (non-working) example what would I need to change for each iteration to have it's own translation?
{% for key, values in products.items() %}
<h4>{{ _('%(name)s', name=values['name']) }}</h4>
<p>{{ _('%(caption)s', caption=values['caption']) }}</p>
{% endfor %}
With this example the PO is generated like:
msgid "%(name)s"
msgstr ""
This, as far as I know, only allows me to insert a single value for the translation

So you have static and dynamic texts. The Babel-way to translate texts is only effective for static texts. For dynamically generated texts it would be very ineffective: Babel does not know about the dynamic texts, you would have to add manually the news text to the PO each time when a new product appears. I don't recommend it.
You should use a different approach for dynamic texts. I guess that you periodically import the products into your DB through a 3rd party API so you have a Product model. If there are only a few languages and texts to translate, it's still adequate to have only one model with many translated-fields (one for each language). For example:
class Product(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True)
category = db.Column(db.SmallInteger, index=True)
added = db.Column(db.DateTime, index=True)
name_en = db.Column(db.String(255))
name_eo = db.Column(db.String(255))
name_hu = db.Column(db.String(255))
description_en = db.Column(db.Text)
description_eo = db.Column(db.Text)
description_hu = db.Column(db.Text)
So after you import new products, you can translate their texts through an online interface. If you have many fields to translate you can separate the language dependent and independent part of the Product, and have separated models for them:
class Product(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True)
category = db.Column(db.SmallInteger, index=True)
added = db.Column(db.DateTime, index=True)
class ProductText(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True)
pid = db.Column(db.Integer, db.ForeignKey('product.id'))
language = db.Column(db.String(10), index=True)
name = db.Column(db.String(255))
description = db.Column(db.Text)
Thus when you want to show a product to the client, first check the active language, and load the corresponding translated ProductText with the current Product.

Related

SqlAlchemy association_proxy filter returned array (_AssociationList)

im using an association_proxy like this:
study_participantions = association_proxy("quests", "study_participant",creator=lambda sp: sp.questionnaire)
I my Db there is:
A Patient table
A StudyParticipant table
A Questionnaire table
Patient and Questionnaire are a Many-To-One relationship
A Questionnaire can be Part of a StudyParticiapnt via a One-To-One relationship
StudyParticipant and Patient are not directly linked since StudyParticipant can be annonymous.
So via getter and setter i can query the Patient trough the questionnaire.
Since im working with an existing codebase I have to keep the patient inside the questionnaire
The StudyParticipant can be find via the proxy from the Patient. getting and setting works but if the Questionnaire is not part of a StudyParticiapnt the returned array contains None values is it possible to filter them out so i would get a clean array? For sure it should still be an
sqlalchemy.ext.associationproxy._AssociationList so appending and removing to it would still work.
Simplified Classes:
class Patient(Model):
__tablename__ = 'patient'
id = Column(Integer, primary_key=True)
study_participantions = association_proxy("quests", "study_participant",creator=lambda sp: sp.questionnaire)
class StudyParticipant(Model): #better name would be participation
__tablename__ = "study_participant"
id = Column(Integer, primary_key=True)
pseudonym = Column(String(40), nullable = True)
questionnaire = relationship("Questionnaire", backref="study_participant",uselist=False) # why go via the StudyQuestionnaire
class Questionnaire(Model, metaclass=QuestionnaireMeta):
__tablename__ = 'questionnaire'
id = Column(Integer, primary_key=True)
patient_id = Column(Integer(), ForeignKey('patient.id'), nullable=True)
patient = relationship('Patient', backref='quests',
primaryjoin=questionnaire_patient_join)

Flask / SQLAlchemy - Request tables with many-to-many relationships

I used Flask and SQLAlchemy to create an application based on a database. Here is the classes that I have defined:
models.py
class HasTag(db.Model):
tagged_document_id = db.Column(db.Integer, db.ForeignKey('Document.id'), primary_key=True)
document_tag_id = db.Column(db.Integer, db.ForeignKey('Tag.id'), primary_key=True)
class Document(db.Model):
id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True, autoincrement=True)
title = db.Column(db.Text)
tag = db.relationship("Tag",
secondary=HasTag,
back_populates="tagged_document",
lazy="dynamic")
class Tag(db.Model):
id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True, autoincrement=True)
label = db.Column(db.String, nullable=False)
tagged_document = db.relationship("Document",
secondary=HasTag,
back_populates="tag",
lazy="dynamic")
In the application, I have an advanced search form where it is possible to do a full text search through the different fields of the Document table.
routes.py
#app.route("/search")
def search():
keyword = request.args.get("keyword", None)
query = Document.query
if keyword:
query = Document.query.filter(or_(
Document.title.like("%{}%".format(keyword)),
...
))
The thing is, I'd like to be able to search the keyword given by the user also in the label of the tag. I tried something like:
if keyword:
query = Document.query.join(Tag).filter(or_(
Document.title.like("%{}%".format(keyword)),
...,
Tag.label.like("%{}%".format(keyword))
))
But I get this error: AttributeError: 'HasTag' object has no attribute 'foreign_keys'
Can you help me? Thanks!
I have a similar structure in one of my projects, and this is how I define relatioship:
leagues = db.relationship("League",
secondary=LeagueTeamAssociation.__tablename__,
back_populates="teams")
So, You need to provide table name to secondary parameter, either using above syntax (You'll need to add __tablename__ to your HasTag class) or using string "has_tag" (provided that this is the name of the table in the database).

Flask has many through model associations

Ruby on Rails has a has-many-through association. For example,
store has_many :shelves
shelf has many :books
store has_many :books, through: :shelves
This would allow us to call store.books and get all the books in the store. I am looking to replicate this functionality in Flask, but can't seem to find the information.
For example, here are the one to many relationships written in Python. Store has many shelves, shelves has many books.
class Store(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
shelves = db.relationship('Shelf', backref='store')
class Shelf(db.Model):
id = db.Column(db.Integer, primary_key=True)
location = db.Column(db.String(120), nullable=False)
store_id = db.Column(db.Integer, db.ForeignKey('store.id'), nullable=False)
books = db.relationship('Book', backref='shelf')
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
shelf_id = db.Column(db.Integer, db.ForeignKey('shelf.id'), nullable=False)
With the code currently as written, I can call store.shelves and shelf.books. However, to get to books, I would have to loop through the shelves and call .books.
Is there a way to call store.books here?
One option is to use Shelf as a "secondary" table and create a one-sided "many-to-many" relationship, though really a one-to-many relationship with a table in between:
class Store(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
shelves = db.relationship('Shelf', backref='store')
books = db.relationship('Book', secondary='shelf', viewonly=True)
It is better to treat the relationship as a view only, since there is no way to decide which shelf a book should go to, if appended to the Store.books list. This comes fairly close to how Rails handles defining the relationship.
Looping through shelves would be very inefficient. Leave it to your database engine.
You need to create a custom query to achieve that. If you want to call it with store.books you can create property in Store class:
class Store(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False)
shelves = db.relationship('Shelf', backref='store')
#property
def books(self):
query = Book.query.join(Shelf).join(Store).filter(Store.id == self.id)
return query.all()

"has_many :through" construct in sqlalchemy

In rails we can simply define relationships with the has_many :through syntax in order to access 2nd, 3rd .. nth degree relations.
In SQLAlchemy however, this seems to be more difficult. I'm trying to avoid going down the route of writing joins, as I find them to be anti-patterns in trying to keep a clean code base.
My tables look like following:
class Message(db.Model):
__tablename__ = 'message'
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String())
user_id = db.Column(db.ForeignKey("user.id"))
user = db.relationship('User', backref="messages")
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
class Level(db.Model):
__tablename__ = 'level'
number = db.Column(db.Integer, nullable=False, primary_key=True)
name = db.Column(db.String(), nullable=False, primary_key=True)
users = db.relationship(
"User",
secondary="user_level",
backref="levels")
class UserLevel(db.Model):
__tablename__ = 'user_level'
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
number = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), primary_key=True)
__table_args__ = (
db.ForeignKeyConstraint(
['number', 'name'],
['level.number', 'level.name']
),
)
The idea is that a user can have multiple authorisation levels (e.g. a user can be at level 1, 3 and 6 at the same time). As the data I have does not contain unique sequence numbers for available levels, I had to resort to the use of composite keys to keep the data consistent with future updates.
To get all messages for a level I can currently do something like this:
users = Level.query[0].users
for user in users:
results.append(user.messages)
return results
This gives me all users on a level. But in order to get all messages for a certain level, I have to loop through these users and append them to a results list.
What I'd like to do is:
return Level.query[0].users.messages
This is more like the syntax I am used to from rails. How would one accomplish this in flask-SQLAlchemy?

How to use many-to-many fields in Flask view?

I am trying to create a blog using Flask. Every post can have multiple tags also every tag could be associated with multiple posts. So I created a many-to-many relationship. My questions is how do i save multiple tags when creating a new post. And since every post can have different number of tags how do i show this is in the form? Also, how can i create new tags along with the post and then use those tags with other posts?
This is models.py -
postcategory = db.Table('tags',
db.Column('posts_id', db.Integer, db.ForeignKey('posts.id')),
db.Column('categories_id', db.Integer, db.ForeignKey('categories.id'))
)
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
content = db.Column(db.Text)
slug = db.Column(db.String, unique=True)
published = db.Column(db.Boolean, index=True)
timestamp = db.Column(db.DateTime, index=True)
categories = db.relationship('Category', secondary=postcategory, backref='posts' )
def __init__(self, title, content):
self.title = title
self.content = content
class Category(db.Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, index=True)
This is the view i am working on -
def create_article():
if request.method == 'POST':
if request.form.get('title') and request.form.get('content') and request.form.get('slug') and request.form.get('published'):
post = Post(request.form['title'], request.form['content'], request.form['slug'], request.form['published'])
I am sure there is a easy solution and i am just complicating this, but i am new to web development, so please help.
You can pull the categories out of the form with getlist and add them to the Post object. If you have checkboxes like the following:
<form>
<input type="checkbox" name="categories" value="foo">
<input type="checkbox" name="categories" value="bar" checked>
</form>
In your view method you can just do:
categories_from_form = request.form.getlist('categories') # ['bar']
# create Category objects with the form data
categories = [Category(title=title) for title in categories_from_form]
post = Post(request.form['title'], request.form['content'], request.form['slug'], request.form['published'])
post.categories = categories # attach the Category objects to the post
...

Categories