How to do a back-reference on Google AppEngine? - python

I'm trying to access an object that is linked to by a db.ReferenceProperty in Google app engine. Here's the model's code:
class InquiryQuestion(db.Model):
inquiry_ref = db.ReferenceProperty(reference_class=GiftInquiry, required=True, collection_name="inquiry_ref")
And I am trying to access it in the following way:
linkedObject = question.inquiry_ref
and then
linkedKey = linkedObject.key
but it's not working. Can anyone please help?

Your naming conventions are a bit confusing. inquiry_ref is both your ReferenceProperty name and your back-reference collection name, so question.inquiry_ref gives you a GiftInquiry Key object, but question.inquiry_ref.inquiry_ref gives you a Query object filtered to InquiryQuestion entities.
Let's say we have the following domain model, with a one-to-many relationship between articles and comments.
class Article(db.Model):
body = db.TextProperty()
class Comment(db.Model):
article = db.ReferenceProperty(Article)
body = db.TextProperty()
comment = Comment.all().get()
# The explicit reference from one comment to one article
# is represented by a Key object
article_key = comment.article
# which gets lazy-loaded to a Model instance by accessing a property
article_body = comment.article.body
# The implicit back-reference from one article to many comments
# is represented by a Query object
article_comments = comment.article.comment_set
# If the article only has one comment, this gives us a round trip
comment = comment.article.comment_set.all().get()

The back reference is just a query. You need to use fetch() or get() to actually retrieve the entity or entities from the datastore:
linkedObject = question.inquiry_ref.get()
should do the trick. Or, you would use fetch() if you were expecting the back ref to refer to more than one entity.
Actually, the way that your class is constructed makes it ambiguous as to what exactly is happening here.
If you have a GiftInquiry entity, it will get an automatic property called inquiry_ref that will be a query (as I described above) that will return all InquiryQuestion entities that have their inquiry_ref property set to that GiftInquiry's Key.
On the other hand, if you have an InquiryQuestion entity, and you want to get the GiftInquiry entity to which its inquiry_ref property is set, you would do this:
linkedObject = db.get(question.inquiry_ref)
as the inquiry_ref is just the Key of the referred-to GiftInquiry, but that is not technically a BackReference.
Check out the explanation of ReferenceProperty and back references from the docs.

Related

Initialization and changing ndb property in google-apps-engine

I created a datastore object accoring to the guestbook tutorial:
class myDS(ndb.Model):
a = ndb.StringProperty(indexed=True)
And I have an Handlers to access it and update is:
class Handler1:
my_ds = myDS()
my_ds.a = "abc" #Trying to update the value
class Handler2:
my_ds = myDS()
self.response.write(my_ds.a) #prints None although I changed the value in Handlers1
def main():
application = webapp.WSGIApplication([
('/set', Handler1),
('/get', Handler2])
I call:
Myapp.com/set
Myapp.com/get : Prints None (Didn't update to "abc")
Why wasn't the value of a updated?
How can I update across the handlers?
Cloud Datastore (GCD) stores data objects as entities, which may have one or more properties. In your case the property value type is a string 'abc'. However each entity is identified by a key which is a unique identifier within your app's datastore.
So in your case you would need to create a key for object my_ds and also define a model class. That could be Handler1 (e.g. class Handler1(ndb.Model): #your code) which defines the property you are trying to call.
Additionally, you cannot expect the value to be updated without using the put() function (e.g. my_ds.put() ). In order to use the second handler (Handler2) to create a new object and set the values of the properties you need to learn a bit more about using Webapp2 request handler.
I also suggest you follow this tutorial to get started.

Querying nested KeyProperties

I have the following models:
class Company(ndb.Model):
name = ndb.StringProperty(indexed=False)
# some other fields
class User(polymodel.PolyModel):
company = ndb.KeyProperty(kind=Company)
# some other fields
class Object(ndb.Model):
user = ndb.KeyProperty(kind=User)
# some other fields
Now I have a user and I want to query Objects that are associated with other Users in the same company like this:
Object.query(Object.user.company == user.company)
Of course, this doesn't work, since Object.user is a key and I cannot access anything beyond that.
Is there any way to do it? I only need the company key, I was thinking on a ComputedProperty but I'm not sure if it's the best solution. Also, it would be better to query based on any field in company.
You need to denormalize and store redundant information, as the datastore doesn't support joins.
For instance given your models above, a user can only be a member of one company, if you really need to search all objects whose user is a member of a particular company then store the company key in the Object.
Use a computed property if that works best for you.
Alternately use a factory that always takes the User as a argument and construct Object that way.

NDB Expando Model with dynamic TextProperty?

I'm trying to do:
MyModel({'text': db.Text('text longer than 500 byets')})
But get:
BadValueError: Indexed value fb_education must be at most 500 bytes
I'm thinking this is just a carry over from this issue with the old db api.
https://groups.google.com/forum/?fromgroups#!topic/google-appengine/wLAwrjtsuks
First create entity dynamically :
kindOfEntity = "MyTable"
class DynamicEntity(ndb.Expando):
#classmethod
def _get_kind(cls):
return kindOfEntity
then after to assign Text Properties run time/dynamically as shown below
dbObject = DynamicEntity()
key = "studentName"
value = "Vijay Kumbhani"
textProperties = ndb.TextProperty(key)
dbObject._properties[key] = {}
dbObject._values[key] = {}
dbObject._properties[key] = textProperties
dbObject._values[key] = value
dbObject.put()
then after key properties assign with Text properties
You're trying to use a db.Text, part of the old API, with NDB, which isn't going to work.
To the best of my knowledge, there's no good way to set unindexed properties in an Expando in NDB, currently. You can set _default_indexed = False on your expando subclass, as (briefly) documented here, but that will make the default for all expando properties unindexed.
A better solution would be to avoid the use of Expando alltogether; there are relatively few compelling uses for it where you wouldn't be better served by defining a model (or even defining one dynamically).
Yeah, I know question is old. But I also googled for same solutions and not found any result.
So here receipt that works for me (I expand User() with "permissions" property):
prop = ndb.GenericProperty("permissions", indexed=False)
prop._code_name = "permissions"
user._properties["permissions"] = prop
prop._set_value(user, permissions)
The previous answer was VERY use to me... Thanks!!! I just wanted to add that it appears you can also create a specific property type using this technique (if you know the datatype you want to create). When the entity is later retrieved, the dynamic property is set to the specific type instead of GenericProperty. This can be handy for ndb.PickleProperty and ndb.JsonProperty values in particular (to get the in/out conversions).
prop = ndb.TextProperty("permissions", indexed=False)
prop._code_name = "permissions"
user._properties["permissions"] = prop
prop._set_value(user, permissions)
I was trying to just change one property of an entity to Text. But when you don't map your properties explicitly, Expando/Model seem to change all properties of an entity to GenericProperty (after get).
When you put those entities again (to change the desired property), it affects other existing TextProperties, changing then to regular strings.
Only the low-level datastore api seems to work:
https://gist.github.com/feroult/75b9ab32b463fe7f9e8a
You can call this from the remote_api_shell.py:
from yawp import *
yawp(kind).migrate(20, 'change_property_to_text', 'property_name')

Entity groups, ReferenceProperty or key as a string

After building a few application on the gae platform I usually use some relationship between different models in the datastore in basically every application. And often I find my self the need to see what record is of the same parent (like matching all entry with same parent)
From the beginning I used the db.ReferenceProperty to get my relations going, like:
class Foo(db.Model):
name = db.StringProperty()
class Bar(db.Model):
name = db.StringProperty()
parentFoo = db.ReferanceProperty(Foo)
fooKey = someFooKeyFromSomePlace
bars = Bar.all()
for bar in bar:
if bar.parentFoo.key() == fooKey:
// do stuff
But lately I've abandoned this approch since the bar.parentFoo.key() makes a sub query to fetch Foo each time. The approach I now use is to store each Foo key as a string on Bar.parentFoo and this way I can string compare this with someFooKeyFromSomePlace and get rid of all the subquery overhead.
Now I've started to look at Entity groups and wondering if this is even a better way to go? I can't really figure out how to use them.
And as for the two approaches above I'm wondering is there any downsides to using them? Could using stored key string comeback and bit me in the * * *. And last but not least is there a faster way to do this?
Tip:
replace...
bar.parentFoo.key() == fooKey
with...
Bar.parentFoo.get_value_for_datastore(bar) == fooKey
To avoid the extra lookup and just fetch the key from the ReferenceProperty
See Property Class
I think you should consider this as well. This will help you fetch all the child entities of a single parent.
bmw = Car(brand="BMW")
bmw.put()
lf = Wheel(parent=bmw,position="left_front")
lf.put()
lb = Wheel(parent=bmw,position="left_back")
lb.put()
bmwWheels = Wheel.all().ancestor(bmw)
For more reference in modeling. you can refer this Appengine Data modeling
I'm not sure what you're trying to do with that example block of code, but I get the feeling it could be accomplished with:
bars = Bar.all().filter("parentFoo " = SomeFoo)
As for entity groups, they are mainly used if you want to alter multiple things in transactions, since appengine restricts that to entities within the same group only; in addition, appengine allows ancestor filters ( http://code.google.com/appengine/docs/python/datastore/queryclass.html#Query_ancestor ), which could be useful depending on what it is you need to do. With the code above, you could very easily also use an ancestor query if you set the parent of Bar to be a Foo.
If your purposes still require a lot of "subquerying" as you put it, there is a neat prefetch pattern that Nick Johnson outlines here: http://blog.notdot.net/2010/01/ReferenceProperty-prefetching-in-App-Engine which basically fetches all the properties you need in your entity set as one giant get instead of a bunch of tiny ones, which gets rid of a lot of the overhead. However do note his warnings, especially regarding altering the properties of entities while using this prefetch method.
Not very specific, but that's all the info I can give you until you be more specific about exactly what you're trying to do here.
When you design your modules you also need to consider whether you want to be able to save this within a transaction. However only do this if you need to use transactions.
An alternative approach is to assign the parent like so:
from google.appengine.ext import db
class Foo(db.Model):
name = db.StringProperty()
class Bar(db.Model):
name = db.StringProperty()
def _save_entities( foo_name, bar_name ):
"""Save the model data"""
foo_item = Foo( name = foo_name )
foo_item.put()
bar_item = Bar( parent = foo_item, name = bar_name )
bar_item.put()
def main():
# Run the save in a transaction, if any fail this should all roll back
db.run_in_transaction( _save_transaction, "foo name", "bar name" )
# to query the model data using the ancestor relationship
for item in bar_item.gql("WHERE ANCESTOR IS :ancestor", ancestor = foo_item.key()).fetch(1000):
# do stuff

Filtering models with ReferenceProperties

I'm using google app engine, and am having trouble writing querys to filter ReferenceProperties.
eg.
class Group(db.Model):
name = db.StringProperty(required=True)
creator = db.ReferenceProperty(User)
class GroupMember(db.Model):
group = db.ReferenceProperty(Group)
user = db.ReferenceProperty(User)
And I have tried writing something like this:
members = models.GroupMember.all().filter('group.name =', group_name)
and various other things that don't work. Hopefully someone can give me a prod in the right direction...
If your groups are uniquely named, then your "group.name" is a unique identifier of a Group entity.
That means you can write:
members = models.GroupMember.all().filter(
"group =",model.Group.gql("WHERE name=:1", group_name).get()
)
though you only need to do that if you don't already have the group entity lying around in the stack somewhere.
Google's essay on many-to-many with appengine is here.
If what you want is to get the members of a group, ReferenceProperties have that built-in.
class GroupMember(db.Model):
group = db.ReferenceProperty(Group, collection_name="groupMembers")
user = db.ReferenceProperty(User, collection_name="groupMembers")
Then you can write:
# get the group entity somehow
group = Group.get(group_key)
# do something with the members, such as list the nicknames
nicknames = [x.user.nickname for x in group.groupMembers]
This would require a join, which isn't possible in App Engine. If you want to filter by a property of another model, you need to include that property on the model you're querying against.
This would result in two datastore hits but should work. If you use memcache shouldnt be a problem.
group = models.Group.all().filter("name =", group_name).get()
members = models.GroupMember.all().filter('group =', group)
Using the Models that you defined in your question, lets say you want to list all members of a group called "Space monkeys".
mygroup = Group.gql("WHERE name = :1",'Space monkeys')
for group_member in mygroup.groupmember_set:
print 'group members name is: %s' % (group_member.user.name)
The "groupmember_set" is called a "implicit collection property" and is very useful. You can call it whatever you want by over-riding the default name using the collection_name keyword parameter to ReferenceProperty. For an example see the answer by Thomas L Holaday.
This is all explained in a very good paper by Rafe Kapla: Modeling Entity Relationships.

Categories

Resources