Problem
I am building an app on Flask, Flask-SQLAlchemy, and Flask-Restless. I have used restless to generate an API for a parent-child-grandchild relationship*. A GET on my child will correctly fetch the grandchild, but a GET on the parent will not fetch the grandchild for each child.
*In fact, the parent-child relationship is a many-to-many, but same premise.
Models
class Grandchild(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
parent = db.relationship('Child', backref='grandchild')
parent_child = db.Table('parent_child',
db.Column('parent_id', db.Integer, db.ForeignKey('parent.id')),
db.Column('child_id', db.Integer, db.ForeignKey('child.id')),
db.Column('number', db.SmallInteger)
)
class Child(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
grandchild_id = db.Column(db.Integer, db.ForeignKey('grandchild.id'), nullable=False)
class Parent(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
children = db.relationship('Child', secondary=parent_child)
api.create_api(Child, exclude_columns=['grandchild_id'])
api.create_api(Parent)
GET: /api/child response
{
"num_results": 1,
"objects": [
{
"id": 1,
"name": "test"
"grandchild": {
"id": 1,
"name": "test"
}
}
],
"page": 1,
"total_pages": 1
}
GET: /api/parent response
{
"num_results": 1,
"objects": [
{
"children": [
{
"id": 1,
"name": "test",
"grandchild_id": 1
}
],
"id": 1,
"name": "test"
}],
"page": 1,
"total_pages": 1
}
postprocessors can be used to fetch grandchild.
def parent_post_get_many(result=None, search_params=None, **kw):
for object in result['objects']:
for child in object['children']:
grandchild = Grandchild.query.get(child['grand_child_id'])
child.update({'grandchild': grandchild})
api.create_api(Parent, postprocessors={'GET_MANY': [parent_post_get_many]})
After looking at this for a few hours I'm going to give the best answer I have at the moment. I've tried a number of approaches and haven't been able to get anything to successfully render the grandchild, so I turned to the flask-restless issues tracker to see what I could find:
https://github.com/jfinkels/flask-restless/pull/222#issuecomment-31326359 and #jfinkels response seem to indicate that what you want is currently not possible in flask-restless.
Assuming my assessment of the state of issues is correct, you may want to look into either designing around this issue, or using a different package to serve your API (perhaps Flask-restful, though I admit I haven't used it and don't know if it's suitable).
FWIW, I've been using Flask-Classy to build a json API for a project I'm working on. The process is a little more involved than I suspect you want, but it also comes with enough freedom to better control what queries are used and how the results are serialized.
Related
I'm using Redis OM for Python and my models look like below:
from typing import List
from pydantic import BaseModel
from redis_om import EmbeddedJsonModel, Field, JsonModel, Migrator
class FeedItem(EmbeddedJsonModel):
id: str = Field(index=True)
s_score: str = Field()
i_score: str = Field()
factors: List[str]
class Feed(JsonModel):
user_id: str = Field(index=True, primary_key=True)
feed_items: List[FeedItem] = Field(default=[])
which will then result in a data structure like this:
{
"user_id": "john_001",
"feed_items": [
{
"pk": "01GS2N47G8WK2831GNHMGRVDJT",
"id": "63e8c53825e41aca93229eac",
"s_score": "0.5082375478927202",
"i_score": "0.04626620037029417",
"factors": ["2nd"],
},
{
"pk": "01GS2N557FTV0SCK5TP2KENVAF",
"id": "63e8c5d31e033af45abfb64d",
"s_score": "0.7",
"i_score": "0.37718576424604",
"factors": ["2nd", "computer", "laptop"],
},
{
"pk": "01GS2N63S6VM1HZ6RJVH6M1XQJ",
"id": "63e8c743414c482153e332e6",
"s_score": "0.5082375478927202",
"i_score": "0.24141123225673727",
"factors": ["2nd", "thumbdrive", "portables"],
},
],
}
This is going to be a feed of a user and if he has viewed the first item (with "pk": "01GS2N47G8WK2831GNHMGRVDJT"), we will need to delete this item from his feed.
Currently, what I am having to do is to find the key with "user_id": "john_001", retrieve the feed_items to a Python list and remove the item with that pk, then reassign the feed_items and save the item. It's as the following:
feed = Feed.find(Feed.user_id = "john_001").first()
feed_items = feed.feed_items
new_feed_items = [i in feed_items if i["pk"] != "01GS2N47G8WK2831GNHMGRVDJT"]
feed.feed_items = new_feed_items
feed.save()
Is there any better way to do this? Because right now the process is taking quite long to complete (we have dozens of thousands of users' feed and there are several deletion processes like this every seconds.
I am querying my Database to then return the values and JSONIFY them into JSON format so that the "fullcalendar js" can parse them and show them on my front end.
My model of the DB is as follows :
class database(db.Model):
id = db.Column(db.Integer, primary_key=True)
userid = db.Column(db.Integer, nullable=False)
user = db.Column(db.String(200), nullable=False)
startdate = db.Column(db.String(10), nullable=False)
enddate = db.Column(db.String(10), nullable=False)
duration = db.Column(db.Integer, nullable=False)
approved = db.Column(db.Boolean, nullable=False)
The JSON format i need them to be is :
[
{
"title": "Long Event",
"start": "2014-09-07",
"end": "2014-09-10"
}
]
What i have tried ("And failed!")
alldata = database.query.filter(database.approved == True).all()
events = {"title": [], "start": [], "end": []};
for row in alldata:
events["title"].append(row.user.strip("[]"))
events["start"].append(datetime.datetime.strptime(row.startdate,'%d/%m/%Y').strftime('%Y-%m-%d').strip("[]"))
events["end"].append(datetime.datetime.strptime(row.enddate, '%d/%m/%Y').strftime('%Y-%m-%d').strip("[]"))
# Serializing json
json_object = json.dumps(events, indent=4)
jsontoreturn="["+json_object+"]"
return jsontoreturn
The reason this fails is the resulting JSON is in the format of :
[{
"title": [
"Ben"
],
"start": [
"2021-12-29"
],
"end": [
"2021-12-31"
]
}]
As you can see the Dictionary is adding Sqaure brackets and as a result the FullCalendar JS (https://fullcalendar.io/docs/events-json-feed) doesnt parse it correctly and fails.
Anyone got any ideas?
I don't have the ability to actually test this against your value for alldata but I suspect the following will work:
alldata = database.query.filter(database.approved == True).all()
def process_row(row):
# func which returns a dictionary like:
# {"title": "Long Event", "start": "2014-09-07", "end": "2014-09-10" }
return dict (title = row.user.strip("[]"),
start = datetime.datetime.strptime(row.startdate,'%d/%m/%Y').strftime('%Y-%m-%d').strip("[]"),
end = datetime.datetime.strptime(row.enddate, '%d/%m/%Y').strftime('%Y-%m-%d').strip("[]"),
)
# Make a list of dictionaries based on the input, `alldata`
events = [process_row(r) for r in alldata]
Then simply:
json_object = json.dumps(events, indent=4)
return json_object
OR, if you want to use Flask's jsonify function which also sets the Content-Type header:
return jsonify(events)
I have the following two classes in my app.models and i'm using the wagtail APIs to get the data as json
class AuthorMeta(Page):
author=models.OneToOneField(User)
city = models.ForeignKey('Cities', related_name='related_author')
class Cities(Page):
name = models.CharField(max_length=30)
So, when I try /api/v1/pages/?type=dashboard.AuthorMeta&fields=title,city, it returns the following data:
{
"meta": {
"total_count": 1
},
"pages": [
{
"id": 11,
"meta": {
"type": "dashboard.AuthorMeta",
"detail_url": "http://localhost:8000/api/v1/pages/11/"
},
"title": "Suneet Choudhary",
"city": {
"id": 10,
"meta": {
"type": "dashboard.Cities",
"detail_url": "http://localhost:8000/api/v1/pages/10/"
}
}
}
]
}
In the city field, it returns the id and meta of the city. How can I get the name of the city in the response here, without making an extra query? :/
I couldn't find any solution in the Documentation. Am I missing something?
Use Django model property to return through the ForeignKey:
class AuthorMeta(Page):
author=models.OneToOneField(User)
city = models.ForeignKey('Cities', related_name='related_author')
city_name = property(get_city_name)
def get_city_name(self):
return self.city.name
Check Term Property to better understand the concept
In case you have the foreign key in a Streamfield, e.g. a PageChooserBlock, you can customize the api response by overwriting the get_api_representation of a block, as described in the example as provided here:
class CustomPageChooserBlock(blocks.PageChooserBlock):
""" Customize the api response. """
def get_api_representation(self, value, context=None):
""" Return the url path instead of the id. """
return value.url_path
I want to store locations in google's datastore. Each entry shall have got 'sys'-fields, which shall contain information set by the datastore.
I've got the class model below and the WebService JSON request/response looks ok, but I have to set the values manually. It looks like auto_current_user_add, auto_now_add, auto_current_user and auto_now does not trigger.
from google.appengine.ext import ndb
from endpoints_proto_datastore.ndb import EndpointsModel
class Created(EndpointsModel):
by = ndb.UserProperty(auto_current_user_add=True)
on = ndb.DateTimeProperty(auto_now_add=True)
class Updated(EndpointsModel):
by = ndb.UserProperty(auto_current_user=True)
on = ndb.DateTimeProperty(auto_now=True)
class Sys(EndpointsModel):
created = ndb.StructuredProperty(Created)
updated = ndb.StructuredProperty(Updated)
class Location(EndpointsModel):
name = ndb.StringProperty(required=True)
description = ndb.TextProperty()
address = ndb.StringProperty()
sys = ndb.StructuredProperty(Sys)
When I submit a create request (location.put()) I get the following response:
{
"id": "4020001",
"name": "asdf"
}
When I set it manually using:
location.sys = Sys(created=Created(on=datetime.datetime.now(),
by=current_user),
updated=Updated(on=datetime.datetime.now(),
by=current_user))
location.put()
I get the expected result:
{
"id": "4020002",
"name": "asdf",
"sys": {
"created": {
"by": {
"auth_domain": "gmail.com",
"email": "decurgia#XYZ"
},
"on": "2015-01-27T16:05:41.465497"
},
"updated": {
"by": {
"auth_domain": "gmail.com",
"email": "decurgia#XYZ"
},
"on": "2015-01-27T16:05:41.465577"
}
}
}
How can I get those fields (sys.created.on, sys.created.by, sys.updated.on, sys.updated.by) automatically set?
In my limited work with StructuredProperty, I found it to be slower and more difficult to use than simply inserting the properties directly into the model. NDB seems to store those properties separately and perform a "join" when retrieving them. My recommendation is to use a "flat" model:
class Location(EndpointsModel):
name = ndb.StringProperty(required=True)
description = ndb.TextProperty()
address = ndb.StringProperty()
created_by = ndb.UserProperty(auto_current_user_add=True)
created_on = ndb.DateTimeProperty(auto_now_add=True)
updated_by = ndb.UserProperty(auto_current_user=True)
updated_on = ndb.DateTimeProperty(auto_now=True)
This should cause the auto_ properties to be triggered automatically.
I am somewhat new to the google app engine models system(and all database models for that matter), and I am trying to figure out how make a model have a collection of model instances as one of the properties. Some how I feel that there has to be a better way to model the data I have.
So here is the example. I'm writing a journal webapp. I want to allow users to have multible journals as defined by the model Journals
#Represents a collection of journals
class Journals(db.Model):
user = db.UserProperty()
journals = db.ListProperty(int) #there has to be a better way of keeping track of journals then by a list of their id
In the property journals I keep a list of the id's of the Journal model(shown below).
class Journal(db.Model):
user = db.UserProperty()
name = db.StringProperty()
date_created = db.DateTimeProperty(auto_now_add=True)
last_modified = db.DateTimeProperty(auto_now=True)
journal_item = db.ListProperty(int)
In the Journal model I store a list of id's of JournalItem instances as a list of id's
class JournalItem(db.Model):
user = db.UserProperty()
name = db.StringProperty()
date_created = db.DateTimeProperty(auto_now_add=True)
last_modified = db.DateTimeProperty(auto_now=True)
content = db.StringProperty(multiline=True)
So I guess the question really is how should I model data with this structure(I omitted some properties to simplify it)(shown in json).
"Journals": {
"Journal": {
"user": "GAE user object",
"name": "journal 1",
"JournalItems": {
"JournalItem": {
"name": "entry 1",
"content": "some example content"
},
"JournalItem": {
"name": "entry 2",
"content": "some example content"
},
"JournalItem": {
"name": "entry n",
"content": "some example content"
}
}
}
"Journal": {
"user": "GAE user object",
"name": "journal 2",
"JournalItems": {
"JournalItem": {
"name": "entry 1",
"content": "some example content"
},
"JournalItem": {
"name": "entry 2",
"content": "some example content"
},
"JournalItem": {
"name": "entry n",
"content": "some example content"
}
}
}
}
I hope that wasn't to long-winded to be annoying. Any help on the matter would be greatly appreciated.
That's pretty much the way to do it, except I would use a ListProperty(db.Key) instead of int. Then you can do db.get(userjournals.journals) to get all the user's journals once you have the Journals instance belonging to that user (note that to avoid confusion, you should probably call that something like UserJournals).
Remember that GAE is a non-relational datastore, and sometimes you have to think a bit differently from how you would using SQL.
You can follow this guide which describes in an elegant fashion how you can model your entities relationships on Google Appengine.
The reversed approach you can take is to create a reference for example of a Journal in the JournalItem entity and assign a collection_name to it.
class JournalItem:
Journal = db.ReferencePoperty(Journal, collection_name='journal_items')
name = db....
content = db...
The you will be able to iterate through the JournalItems of the Journal in this fashion:
for item in journal.journal_items
logging.info(item.content)