SQL Alchemy - Avoiding recursion in a one-to-many relationship - python

I apologize in advanced for any lack of explanation, as well as the length of this post. I think the issue is much more simple than I'm making it out to be. I have two models utilizing a one to many relationship. For my InsightModel, I have the json() method displaying the following:
{
name: "insightname",
start: 1,
end: 3,
podcast_id: 1,
podcast: {
name: "podcast1",
wave_data: 1,
length: 2,
host: "Hosterman",
category: "entertain",
pub_date: "11/1",
cover_art_url: "google.com"
}
}
And for my PodcastModel, the json() method displays the following:
{
name: "podcast1",
wave_data: 1,
length: 2,
host: "Hosterman",
category: "entertain",
pub_date: "11/1",
cover_art_url: "google.com",
insights: [
{
name: "insightname",
start: 1,
end: 3,
podcast_id: 1
}
]
}
This works as I need it to, but in order to make it work, I had to create two json() methods for each class, in order to avoid recursion in the PodcastModel that would look like the following:
{
name: "podcast1",
wave_data: 1,
length: 2,
host: "Hosterman",
category: "entertain",
pub_date: "11/1",
cover_art_url: "google.com",
insights: [
{
name: "insightname",
start: 1,
end: 3,
podcast_id: 1,
podcast: {
name: "podcast1",
wave_data: 1,
length: 2,
host: "Hosterman",
category: "entertain",
pub_date: "11/1",
cover_art_url: "google.com",
}
}
]
}
My code for the PodcastModel is:
from db import db
from datetime import datetime
class PodcastModel(db.Model):
__tablename__ = 'podcasts'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
wave_data = db.Column(db.Float(precision=2))
length = db.Column(db.Float(precision=2))
host = db.Column(db.String(80))
category = db.Column(db.String(80))
pub_date = db.Column(db.String(50))
cover_art_url = db.Column(db.String(200))
insights = db.relationship('InsightModel', backref='podcast', lazy='dynamic')
def __init__(self, name, wave_data, length, host, category, pub_date, cover_art_url):
self.name = name
self.wave_data = wave_data
self.length = length
self.host = host
self.category = category
self.pub_date = pub_date
self.cover_art_url = cover_art_url
def json(self):
return {'name': self.name, 'wave_data': self.wave_data, 'length': self.length, 'host': self.host, 'category': self.category, 'pub_date': self.pub_date, 'cover_art_url': self.cover_art_url, 'insights': [insight.json_no_podcast() for insight in self.insights.all()]}
def json_no_insight(self):
return {'name': self.name, 'wave_data': self.wave_data, 'length': self.length, 'host': self.host, 'category': self.category, 'pub_date': self.pub_date, 'cover_art_url': self.cover_art_url}
#classmethod
def find_by_name(cls, name):
# Select * FROM items WHERE name=name LIMIT 1
return cls.query.filter_by(name=name).first()
#classmethod
def find_by_id(cls, _id):
return cls.query.filter_by(id=_id)
And the InsightModel is the following:
from db import db
from models.podcast import PodcastModel
class InsightModel(db.Model):
__tablename__ = 'insights'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
start = db.Column(db.Float(precision=2))
end = db.Column(db.Float(precision=2))
podcast_id = db.Column(db.Integer, db.ForeignKey('podcasts.id'))
#podcast = db.relationship('PodcastModel')
def __init__(self, name, start, end, podcast_id):
self.name = name
self.start = start
self.end = end
self.podcast_id = podcast_id
def json(self):
podcast = PodcastModel.find_by_id(self.podcast_id).first().json_no_insight()
return {'name': self.name, 'start': self.start, 'end': self.end,
'podcast_id': self.podcast_id, 'podcast': podcast}
def json_no_podcast(self):
return {'name': self.name, 'start': self.start, 'end': self.end,
'podcast_id': self.podcast_id}
#classmethod
def find_by_name(cls, name):
# Select * FROM items WHERE name=name LIMIT 1
return cls.query.filter_by(name=name).first()
As you can see, I added the json_no_insights() and json_no_podcast() methods to prevent recursion from happening. However, I'm sure reading this code has already given you a pitted feeling in your stomach and I'm desperate for a better way to write it. Thank you very much for any insight and once again, I apologize the for the length of this post or any lack of explanation.

Make your life easier - use marshmallow.
from marshmallow import Schema, fields
from flask import jsonify
class PodcastSchema(Schema):
name = fields.Str()
wave_data = fields.Float()
length = fields.Float()
host = fields.Str()
category = fields.Str()
pub_date = fields.Str()
cover_art_url = fields.Str()
insights = fields.Nested('InsightSchema')
class InsightSchema(Schema):
name = fields.Str()
start = fields.Float()
end = fields.Float()
podcast_id = fields.Integer()
Then simply dump your data like this:
podcast_schema = PodcastSchema() # for dict (single)
podcasts_schema = PodcastSchema(many=True) # for list (array)
jsonify(podcast_schema.dumps(your_json)
Notice lack of a podcast field in the PodcastSchema - that would cause (without tweaking) an infinite recursion. In case you would need that field, you might try as follows:
class PodcastSchema(Schema):
name = fields.Str()
wave_data = fields.Float()
length = fields.Float()
host = fields.Str()
category = fields.Str()
pub_date = fields.Str()
cover_art_url = fields.Str()
# dump insights without podcast field
insights = fields.Nested('InsightSchema', exclude=('podcast', ))
class InsightSchema(Schema):
name = fields.Str()
start = fields.Float()
end = fields.Float()
podcast = fields.Nested('PodcastSchema')

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

split comma separated JSON

I am trying to implement a search functionaltiy for a restaurants, currently in my database the the field menu_type is a varchar, the values are separated by commas
"Burger, Cafe, Italian, American,Irish"
how can I make it so that when somebody searches for "Burger" it will show him all the results which have Burger only as menu_type OR "Burger" is a part of their menu_type
#app.route('/api/restaurants/search/',methods=['GET'])
def get_restaurant():
menu = request.json.get('menu')
restaurant = Restaurant.query.filter_by(menu_type=menu).all()
return jsonify(json_list=[i.serialize for i in restaurant])
my restaurant model:
class Restaurant(db.Model):
__tablename__ ='Restaurant'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
address1 =db.Column(db.String(128))
address2 = db.Column(db.String(32))
phone =db.Column(db.Integer)
lat = db.Column(db.Float(precision='12,10'))
lng = db.Column(db.Float(precision='12,10'))
cost = db.Column(db.Integer)
menu_type = db.Column(db.String(64))
rate =db.Column(db.Float(precision='3,2'))
offer=db.Column(db.String(128))
#property
def serialize(self):
"""Return object data in easily serializeable format"""
return {
'id' : self.id,
'name': self.name,
'address1' : self.address1,
'address2': self.address2,
'phone' : self.phone,
'lat': self.lat,
'lng' : self.lng,
'cost': self.cost,
'menu_type' : self.menu_type,
'rate': self.rate,
'offer' : self.offer
}
Use the following for filtering:
Restaurant = Restaurant.query.filter(Restaurant.menu_type.like('% {0}%').format(menu)).all()
This will give you the result because you are doing a like query.

Filter on relationships

I'm trying to filter upon a relationship. In my db I've got awards and awards categories. I want retrieve only the featured and not nominated awards.
This is my model:
class AwardsAwardsCategory(Base):
__tablename__ = 'awards_awards_categories'
id = Column(Text, primary_key=True, default=generate_unique_id)
awards = relationship("Award")
award_id = Column(Text, ForeignKey("awards.id", ondelete='cascade'))
nomination = Column(Boolean)
def __json__(self, request):
return {
"id": self.id,
"nomination": self.nomination
}
class Award(Base):
__tablename__ = 'awards'
id = Column(Text, primary_key=True, default=generate_unique_id)
type = Column(Text)
featured = Column(Integer)
awards = relationship("AwardsAwardsCategory", lazy='joined')
def __json__(self, request):
return {
"id": self.id,
"type": self.type,
"number_of_awards": len(self.awards),
"featured": self.featured,
}
This is the call I make:
query = self.session.query(Award)
query = query.join(AwardsAwardsCategory.awards)
query = query.filter(Award.featured != 0)
query = query.filter(AwardsAwardsCategory.nomination != True)
q_results = query.all()
This results in the following query:
SELECT awards.id AS awards_id, awards.type AS awards_type, awards.featured AS awards_featured, awards_awards_categories_1.id AS awards_awards_categories_1_id, awards_awards_categories_1.award_id AS awards_awards_categories_1_award_id, awards_awards_categories_1.nomination AS awards_awards_categories_1_nomination
FROM awards_awards_categories JOIN awards ON awards.id = awards_awards_categories.award_id LEFT OUTER JOIN awards_awards_categories AS awards_awards_categories_1 ON awards.id = awards_awards_categories_1.award_id
WHERE awards.featured != 0 AND awards_awards_categories.nomination != true
It is almost correct except the WHERE clause is missing a condition:
AND awards_awards_categories_1.nomination != true
How can I change my code so that it adds the last condition to the WHERE clause.
I ended up filtering on the nomination on the application layer. It's nasty, but it works.
query = self.session.query(Award)
query = query.join(AwardsAwardsCategory.awards)
query = query.filter(Award.featured != 0)
query = query.filter(AwardsAwardsCategory.nomination != True)
q_results = query.all()
# remove all objects from the session in order to keep them in the db.
self.session.expunge_all()
for award_category in q_results:
# keep a separate list of the awards, in order to keep the iteration going as desired
awards = list(award_category)
for award in award_category.awards:
if award.nomination:
awards.remove(award)
def __json__(self, request):
return {
"id": self.id,
"type": self.type,
"number_of_awards": self.number_of_awards,
"featured": self.featured,
}
award_category.number_of_awards = len(awards)
award_category.__json__ = types.MethodType( __json__, a )
If anyone knows a how to do it better in SQLAlchemy please tell me!

Filter Django GenericRelation based on function on foreignkey

I want to find attributes associated to my character that fit in various categories. Eventually I want this output:
"Attributes": {
"Physical": {
"Level": 1,
"Strength": 1,
"Dexterity": 1,
"Stamina": 1
},
"Mental": {
"Level": 2,
"Intelligence": 1,
"Wits": 1,
"Resolve": 1
},
"Social": {
"Level": 3,
"Presence": 1,
"Manipulation": 1,
"Composure": 1
}
},
I have an Class/Enum(AutoNumber) with the attributes in, and a method of finding which is which:
class AttributeAbility(models.Model):
class Attributes(AutoNumber):
INTELLIGENCE = () # Mental, Power
WITS = () # Mental', 'Finesse
RESOLVE = () # Mental', 'Resistance
STRENGTH = () # Physical', 'Power
DEXTERITY = () # Physical', 'Finesse
STAMINA = () # Physical', 'Resistance
PRESENCE = () # Social', 'Power
MANIPULATION = () # Social', 'Finesse
COMPOSURE = () # Social', 'Resistance
attribute = EnumField(Attributes)
#property
def attribute_type(self):
attribute_group = lambda attribute: (
int((attribute.value - 1) / 8)) + 1 % 3
return Category(attribute_group(self.attribute))
class Category(AutoNumber):
MENTAL = ()
PHYSICAL = ()
SOCIAL = ()
I connect the AttributeAbility with my character using these classes:
class CrossCharacterMixin(models.Model):
cross_character_types = models.Q(app_label='mage', model='mage')
content_type = models.ForeignKey(ContentType, limit_choices_to=cross_character_types,
null=True, blank=True)
object_id = models.PositiveIntegerField(null=True)
content_object = GenericForeignKey('content_type', 'object_id')
class Meta:
abstract = True
class CharacterAttributeLink(Trait, CrossCharacterMixin):
MIN = 1
PRIORITY_CHOICES = (
(0, 'Unassigned'), (1, 'Primary'), (2, 'Secondary'), (3, 'Tertiary')
)
attribute = models.ForeignKey('AttributeAbility')
priority = models.PositiveSmallIntegerField(
choices=PRIORITY_CHOICES, default=0
)
def __str__(self):
return self.attribute.attribute.label
And then on the Mage I have:
attributes = GenericRelation('CharacterAttributeLink')
#property
def physical_attributes(self):
type_id = Category['PHYSICAL']
return self.attributes.filter(attribute_type=type_id)
But I'm getting the error that: Cannot resolve keyword 'attribute_type' into field. Choices are: attribute, attribute_id, content_type, content_type_id, current_value, id, maximum_value, object_id, priority
And with my function like so:
#property
def physical_attributes(self):
type_id = Category['PHYSICAL']
return self.attributes.filter(attribute__attribute_type=type_id)
I get this error: Related Field got invalid lookup: attribute_type Which makes some sense (though I've seen this in the docs: >>> Entry.objects.filter(blog_id=4)).
Adding __exact on the end, gives me this: Relation fields do not support nested lookups....at which point I'm lost. Do I need a custom manager? Do I need to move my physical_attribute function elsewhere?
I ended up creating a custom manager:
class CategoryManager(models.Manager):
'''
Class to manage instances that rely on the category enum
'''
def physical(self):
return [categorised_item for categorised_item in super(CategoryManager, self).get_queryset().all()
if categorised_item.category == Category['PHYSICAL']]
def mental(self):
return [categorised_item for categorised_item in super(CategoryManager, self).get_queryset().all()
if categorised_item.category == Category['MENTAL']]
def social(self):
return [categorised_item for categorised_item in super(CategoryManager, self).get_queryset().all()
if categorised_item.category == Category['SOCIAL']]
And then adding this to my AttributeAbility model:
objects = CategoryManager()
And defining this property on my character model:
#property
def social_skills(self):
return [self.skills.filter(skill=skill) for skill
in SkillAbility.objects.social()]

SQLAlchemy - Writing a hybrid method for child count

I'm using Flask-SQLAlchemy, and I'm trying to write a hybrid method in a parent model that returns the number of children it has, so I can use it for filtering, sorting, etc. Here's some stripped down code of what I'm trying:
# parent.py
from program.extensions import db
from sqlalchemy.ext.hybrid import hybrid_method
class Parent(db.Model):
__tablename__ = 'parents'
parent_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80))
children = db.relationship('Child', backref='parent', lazy='dynamic')
def __init__(self, name):
self.name = name
#hybrid_method
def child_count(self):
return self.children.count()
#child_count.expression
def child_count(cls):
return ?????
# child.py
from program.extensions import db
from program.models import Parent
class Child(db.Model):
__tablename__ = 'children'
child_id = db.Column(db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, db.ForeignKey(Parent.parent_id))
name = db.Column(db.String(80))
time = db.Column(db.DateTime)
def __init__(self, name, time):
self.name = name
self.time = time
I'm running into two problems here. For one, I don't know what exactly to return in the "child_count(cls)", which has to be an SQL expression... I think it should be something like
return select([func.count('*'), from_obj=Child).where(Child.parent_id==cls.parent_id).label('Child count')
but I'm not sure. Another issue I have is that I can't import the Child class from parent.py, so I couldn't use that code anyway. Is there any way to use a string for this? For example,
select([func.count('*'), from_obj='children').where('children.parent_id==parents.parent_id').label('Child count')
Eventually, I'll want to change the method to something like:
def child_count(cls, start_time, end_time):
# return the number of children whose "date" parameter is between start_time and end_time
...but for now, I'm just trying to get this to work. Huge thanks to whoever can help me with this, as I've been trying to figure this out for a long time now.
The code below shows it all.
class Parent(Base):
__tablename__ = 'parents'
# ...
#hybrid_property
def child_count(self):
#return len(self.children) # #note: use when non-dynamic relationship
return self.children.count()# #note: use when dynamic relationship
#child_count.expression
def child_count(cls):
return (select([func.count(Child.child_id)]).
where(Child.parent_id == cls.parent_id).
label("child_count")
)
#hybrid_method
def child_count_ex(self, stime, etime):
return len([_child for _child in self.children
if stime <= _child.time <= etime ])
#child_count_ex.expression
def child_count_ex(cls, stime, etime):
return (select([func.count(Child.child_id)]).
where(Child.parent_id == cls.parent_id).
where(Child.time >= stime).
where(Child.time <= etime).
label("child_count")
)
# usage of expressions:
stime, etime = datetime.datetime(2012, 1, 1), datetime.datetime(2012, 1, 31)
qry = session.query(Parent)
#qry = qry.filter(Parent.child_count > 2)
qry = qry.filter(Parent.child_count_ex(stime, etime) > 0)

Categories

Resources