Flask-SQLAlchemy join - python

Hello I have a problem with join at flask-sqlalchemy. I am a beginner at database and flask.
These are my classes:
class Shopping(db.Model):
__tablename__ = 'shoppings'
id = db.Column(db.Integer, primary_key=True)
product_name = db.Column(db.String(30), index=True, unique=False)
price=db.Column(db.Float(10), index=True)
date=db.Column(db.Date())
s_type_id = db.Column(db.Integer, db.ForeignKey('shopping_types.id'))
def __repr__(self):
return 'Alisveris yeri :{0} Tutar :{1} Tarih: {2}'.format(self.product_name,self.price,self.date)
def __list__(self):
return [self.product_name,self.price,self.date]
class Shopping_Type(db.Model):
__tablename__='shopping_types'
id=db.Column(db.Integer,primary_key=True)
type_name=db.Column(db.String(30), index=True, unique=True)
types = db.relationship('Shopping', backref = 'shopping_types', lazy = 'dynamic')
def __repr__(self):
return '{0}'.format(self.type_name)
when I try on python terminal and run:
select shoppings.product_name ,shoppings.price, shoppings.date, shopping_types.type_name from shoppings join shopping_types ON shoppings.s_type_id=shopping_types.id
query
I get what I want but when I run flask-sqlalchemy command:
rslt=db.session.query(spng).join(st)
spng:Shopping(class)
st:Shopping_Type(class)
I get only Shopping data.
I want to get Shopping + Shopping_Type data.
Thank you.

rslt = db.session.query(spng, st).join(st)
The result would be an enumerable of tuples of (Shopping, Shopping_Type)

rslt = db.session.query(spng, st).filter(spng.s_type_id == st.id).all()
for x, y in rslt:
print("Product ID: {} Product Name: {} Sopping Type: {}".format(x.id, x.product_name, y.tye_name))
type(rslt) is a tuple, contains elements as the number of tables joining.

Related

How to get data from multiple tables using flask-sqlalchemy

Here is my tables.
class maindevotee(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(225))
phonenumber = db.Column(db.String(225))
gothram = db.Column(db.String(225))
date = db.Column(db.String(50))
address = db.Column(db.String(250))
def json(self):
return {'id': self.id, 'name':self.name, 'phonenumber': self.phonenumber, 'gothram': self.gothram,
'date': self.date, 'address': self.address}
class relatives(db.Model):
id = db.Column(db.Integer, primary_key=True)
main_id = db.Column(db.Integer, db.ForeignKey('maindevotee.id'), nullable=False)
name = db.Column(db.String(225))
star = db.Column(db.String(225))
gender = db.Column(db.String(45))
relation = db.Column(db.String(45))
def json(self):
return {'main_id': self.main_id, 'name': self.name, 'star':self.star,
'gender': self.gender, 'relation': self.relation}
class services(db.Model):
id = db.Column(db.Integer, primary_key=True)
main_id = db.Column(db.Integer, db.ForeignKey('maindevotee.id'), nullable=False)
pooja = db.Column(db.String(225))
god = db.Column(db.String(225))
price = db.Column(db.Float)
donation = db.Column(db.String(225))
booking_fromdate = db.Column(db.String(50))
booking_todate = db.Column(db.String(50))
prasadam = db.Column(db.String(225))
def json(self):
return {'main_id': self.main_id, 'pooja': self.pooja, 'god': self.god,
'price': self.price, 'donation': self.donation, 'booking_fromdate': self.booking_fromdate,
'booking_todate': self.booking_todate, 'prasadam': self.prasadam}
How to get data from multiple tables in a single request. Here is my scource code to join the three tables.
If i am try to get data from database it will raise an error.
and the error is AttributeError: 'result' object has no attribute 'get_data'
can i get the data from database using foreign key.
data = db.session.query(maindevotee, relatives, services)\
.filter(maindevotee.phonenumber == 3251469870)\
.join(relatives, maindevotee.id == relatives.main_id)\
.join(services, maindevotee.id == services.main_id)\
.first()
def get_data():
return [data.json(get) for get in data.query.all()]
#app.route('/getdata/<phonenumber>',methods=['GET'])
def getdata():
return jsonify({'Devotee list': data.get_data()})
Correct
data = db.session.query(maindevotee, relatives, services)\
.filter(maindevotee.phonenumber == 3251469870)\
.join(relatives, maindevotee.id == relatives.main_id)\
.join(services, maindevotee.id == services.main_id)\
.first()
to
data = db.session.query(maindevotee, relatives, services)\
.filter(
(maindevotee.phonenumber == '3251469870')
& (maindevotee.id == relatives.main_id)
& (maindevotee.id == services.main_id)
).first()
for more clarifications, ask in the comments.
Upon comment
in
#app.route('/getdata/<phonenumber>',methods=['GET'])
def getdata():
return jsonify({'Devotee list': data.get_data()})
data contains the query results, that do not include the function get_data(), therefore you face the mentioned error.
Try the following modification, I think this is the result form you may want:
#app.route('/getdata/<phonenumber>',methods=['GET'])
def getdata():
return jsonify({**data.maindevotee.json(),**data.relatives.json(),**data.services.json()})
Good Luck

flask + sqlAlchemy COUNT, AVG and SUM in one query

Checked the sqlAlchemy docs but cannot see example of multiple columns query with filter and using FUNC.
How to compose a query based on my model to return result like this:
SELECT
COUNT(amount)a_cnt,
SUM(amount)a_sum,
AVG(amount)a_avg
FROM public.transaction
WHERE acc_id = 1
AND "traDate" >= '2019-11-20'
AND "traDate" <= '2019-12-01'
******************
a_cnt || a_sum || a_avg
------------------------
3 || 12 || 4
Please see below my model, and query functions, one with Class other with session, still unsure which one I should be using in this case. Both result in printing the query syntax.
Model:
class Transaction(db.Model):
id = db.Column(db.Integer, primary_key=True)
traDate = db.Column(db.Date, nullable=False)
amount = db.Column(db.Float, nullable=False)
desc = db.Column(db.String, nullable=False)
card = db.Column(db.String(1), nullable=False)
tag_id = db.Column(db.Integer, db.ForeignKey('tag.id'), nullable=True)
acc_id = db.Column(db.Integer, db.ForeignKey('account.id'), nullable=False)
uplDate = db.Column(db.DateTime, nullable=False, default=datetime.now)
### this?
def sum_filtered(account_id, date_from, date_to):
return db.session.query(db.func.count(Transaction.amount).label('a_cnt'), db.func.sum(Transaction.amount).label('a_sum'), db.func.avg(Transaction.amount).label('a_avg')).filter_by(acc_id = account_id).filter(Transaction.traDate >= date_from, Transaction.traDate <= date_to)
### OR this?
def sum_filtered(account_id, date_from, date_to):
return Transaction.query.with_entities(func.sum(Transaction.amount).label('a_sum')).filter_by(acc_id = account_id).filter(Transaction.traDate >= date_from, Transaction.traDate <= date_to)
app:
#app.route(...)
templateData = {
...
'total_amnt' : model.Transaction.sum_filtered(accountid, f_from, f_to),
...
}
return render_template('/list.html', **templateData)
html:
...
<span class="input-group-text">Total £{{ total_amnt }}</span><!-- shows the query syntax-->
<span class="input-group-text">Total £{{ total_amnt.a_sum }}</span><!-- shows nothing-->
...
What am I missing?
Found this Docs. If no better answer provided, I will accept this.
def sum_filtered(account_id, date_from, date_to):
result = db.session.execute('SELECT COUNT(amount)a_cnt, AVG(amount)a_avg, SUM(amount)a_sum FROM transaction WHERE acc_id = :p1 AND "traDate" BETWEEN :p2 AND :p3',{'p1' : account_id, 'p2' : date_from, 'p3' : date_to})
return result.fetchone()
App:
'sum_avg_cnt' : model.Transaction.sum_filtered(accountid, f_from, f_to),
Then html:
{{ sum_avg_cnt.a_cnt }}

How to solve the dict return error when it is used in join table?

I have two models in which these model both return by toDict: as follows,
Tableone Model
class Base(db.Model):
__abstract__ = True
class Tableone(Base):
__tablename__ = 'tableone'
zxc = db.Column(db.VARCHAR(20), nullable=False)
asd = db.Column(db.VARCHAR(20), nullable=False)
qwe = db.Column(db.Integer, nullable=False)
tabletwo = db.relationship("Tabletwo", primaryjoin="foreign(Tabletwo.asd) == Tableone.bnm", uselist=True)
def __init__(zxc, asd, qwe):
self.zxc = zxc
self.asd = asd
self.qwe = qwe
def toDict(self):
return { c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs }
Tabletwo Model
class Base(db.Model):
__abstract__ = True
class Tabletwo(Base):
__tablename__ = 'tabletwo'
iop = db.Column(db.VARCHAR(20), nullable=False)
jkl = db.Column(db.VARCHAR(20), nullable=False)
bnm = db.Column(db.Integer, nullable=False)
def __init__(iop, jkl, bnm ):
self.iop = iop
self.jkl = jkl
self.bnm = bnm
def toDict(self):
return { c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs }
Both model has toDict function. When I access it via controller:
def index():
tre = session.query(Tableone, Tabletwo).outerjoin(Tableone.tabletwo).all()
treArr = []
for tr in tre:
treArr.append(tr.toDict())
return jsonify(treArr)
it gives me error of .... is not JSON serializable
It works good if I only use it without join method like this:
tre = session.query(Tableone).all()
Or if I mention the join one by one is also working :
tre = session.query(Tableone.iop, Tableone.jkl, Tableone.bnm, Tabletwo.zxc).outerjoin(Tableone.tabletwo).all()
Even though the result is only multidimensional array. [['iop1','jkl1', 'bnm1', 'zxc1'],[...],[...],[...],]
But I need to fetch all data in join table without have to mention it one by one what to select, and also need the key to access it in response.
Please help me how to do it correctly. I am very new in python. Many thanks in advance.
[UPDATE 1]
I am just aware if I access it like this:
for tr in tre:
treArr.append(tr.Tabletwo.toDict()) # here
return jsonify(treArr)
It throws error saying AttributeError: 'NoneType' object has no attribute 'toDict'
But if I access it like this
for tr in tre:
treArr.append(tr.Tableone.toDict()) # here
return jsonify(treArr)
It gives me the result of TableOne only
[UPDATE 2]
I was going to try it with merge function :
reference merge-two-dictionaries
for tr in tre:
merge_two_dicts(sim.Simses.toDict(), sim.Carrier.toDict())
...
def merge_two_dicts(x, y):
z = x.copy() # start with x's keys and values
z.update(y) # modifies z with y's keys and values & returns None
return z
But the error is mention in [UPDATE 1]

SQLalchemy/Flask - nested one to one

I'm trying to use Flask+SQLAlchemy to create a procedural universe. Each System has planets, and each planet has cities etc. etc.
It all seems pretty straight forward (famous last words) but I'm getting the following error:
' Traceback (most recent call last): line 75, in
print system.planets.cities
AttributeError: 'InstrumentedList' object has no attribute 'cities' '
when I run the below:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import libtcodpy as libtcod
import random
app = Flask(__name__)
db = SQLAlchemy(app)
class System(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
planets = db.relationship('Planet', backref='system')
def __repr__(self):
return '<System:{}>'.format(self.name)
class Planet(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
system_id = db.Column(db.Integer, db.ForeignKey('system.id'))
cities = db.relationship('City', backref='planet')
def __repr__(self):
return '<Planet:{}>'.format(self.name)
class City(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
pop = db.Column(db.Integer)
planet_id = db.Column(db.Integer, db.ForeignKey('planet.id'))
def __repr__(self):
return '<City:{}>'.format(self.name)
with app.app_context():
db.create_all()
def init_systems():
sysnum = random.randint(5,10)
#init namegenerator
libtcod.namegen_parse('data/systemnames.txt')
while sysnum > 0:
plannum = random.randint(3,8)
sname = libtcod.namegen_generate('Systems')
gensys = System(name=sname)
gensys.planets = []
while plannum > 0:
pname = libtcod.namegen_generate('Planets')
genplan = Planet(name=pname)
genplan.cities = []
citynum = random.randint(4,12)
while citynum > 0:
startpop = random.randint(10,1000)
cname = libtcod.namegen_generate('Cities')
gencit = City(name=cname, pop=startpop)
genplan.cities.append(gencit)
citynum -= 1
gensys.planets.append(genplan)
plannum -= 1
db.session.add(gensys)
db.session.commit()
sysnum -= 1
init_systems()
system = System.query.first()
print system.name
print system.planets
print system.planets.cities
Any ideas? I know there are other ways of structuring things, but I'm looking to have a pretty straightforward model of straight inheritance.
You can not access the cities by calling print system.planets.cities. As system.planets is a list of planets you need to access the cities for each planet separately.
for planet in system.planets:
print planet.cities

Abstraction in SQLAlchemy conditional filtering

I've created models for my database:
class Album(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128))
year = db.Column(db.String(4))
tracklist = db.relationship('Track', secondary=tracklist,
backref=db.backref('albums',
lazy='dynamic'), lazy='dynamic')
class Track(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128))
class Artist(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))
releases = db.relationship('Track', secondary=releases,
backref=db.backref('artists',
lazy='dynamic'), lazy='dynamic')
They are many-to-many related Album <--> Track <--> Artist
Next, I have this form:
class SearchForm(FlaskForm):
search_by_album = StringField('Album', validators=[Optional()])
search_by_artist = StringField('Artist', validators=[Optional()])
search_track = StringField('Track', validators=[Optional()])
year = StringField('Year', validators=[Optional(), Length(max=4)])
My idea is to give the user freedom in filling desired combination of forms (but at least one is required), so I've got this function, which recieves SearchForm().data (an immutable dict 'field_name': 'data'):
def construct_query(form):
query = db.session.query(*[field.label.text for field in form if field.data and field.name != 'csrf_token'])
if form.search_by_album.data:
query = query.filter(Album.title == form.search_by_album.data)
if form.search_by_artist.data:
query = query.filter(Artist.name == form.search_by_artist.data)
if form.search_track.data:
query = query.filter(Track.title == form.search_track.data)
if form.year.data:
query = query.filter(Album.year == form.year.data)
result = query.all()
return result
My question is if there is a more abstract way of adding filters in the function above? If one day I decide to add more columns to my tables (or even create new tables), I will have to add more monstrous ifs to constrcut_query(), which will eventually grow enormous. Or such an abstractions is not a pythonic way because "Explicit is better than implicit"?
PS
I know about forms from models, but I don't think that they are my case
One way would be associating the filter-attribute with the fields at some place, e.g. as a class attribute on the form itself:
class SearchForm(FlaskForm):
search_by_album = StringField('Album', validators=[Optional()])
search_by_artist = StringField('Artist', validators=[Optional()])
search_track = StringField('Track', validators=[Optional()])
year = StringField('Year', validators=[Optional(), Length(max=4)])
# map form fields to database fields/attributes
field_to_attr = {search_by_album: Album.title,
search_by_artist: Artist.name,
search_track: Track.title,
year: Album.year}
When building the query, you could then build the where clause in a pretty comfortable way:
def construct_query(form):
query = db.session.query(*[field.label.text for field in form if field.data and field.name != 'csrf_token'])
for field in form:
if field.data:
query = query.filter(form.field_to_attr[field] == field.data)
# or:
# for field, attr in form.field_to_attr.items():
# if field.data:
# query = query.filter(attr == field.data)
result = query.all()
return result
Adding new fields and attributes to filter on would then only translate to the creating the field and its mapping to an attribute.

Categories

Resources