How to use NDB's id property? - google-app-engine

With old datastore I've used keys. Now I have to use ids (taken from NDB Cheat Sheet doc):
user = User.get_by_id(user_id)
if user is None:
user = User(id=user_id)
But looks like this code doesn't work - the record is added several times.
(user_id is String in my case)

I found the reason, my code above is correct, but my class contained id property:
class User(ndb.Model):
id = ndb.StringProperty()
and it was the problem.

Sounds like you want a transactional operation:
user = User.get_or_insert(user_id)

See the section 'Building a Key' of the NDB Cheat Sheet.
user = ndb.Key('User', user_id).get()
if user is None:
user = User(id=user_id)
or try this and let me know if it works:
user = User.get_by_id(user_id)
if user is None:
user = User(id=user_id)
BTW even if user_id is a string you should use '%s' % user_id

Related

fetch data from ndb query by KeyProperty field

I have User model and another model is user token
class User(EndpointsModel):
name = ndb.StringProperty()
#classmethod
def get_by_name(cls,name)
return cls.query(cls.name == name).get()
2 model is user token
class Token(EndpointsModel):
user = ndb.KeyProperty()
token = ndb.StringProperty()
#classmethod
def get_by_user(cls, key):
return cls.query(cls.token == key).get()
Now i fetch data from user
data = User.get_by_name('jaskaran')
when i try to fetch token then it return me None
print( Token.get_by_user(data.key) )
this is return None How can i fetch data from token with the help of user keyProrperty?
I think that you may be looking for ancestor queries, that according to the official documentation, look like this:
As the example in the docs shows:
purchases = Purchase.query(
Purchase.customer == customer_entity.key).fetch()
This applied to your use case will be then something like:
class Token(EndpointsModel):
user = ndb.KeyProperty(kind=User) #added kind User in key property, just in case
token = ndb.StringProperty()
#classmethod
def get_by_user(cls, key):
return cls.query(cls.token == key).fetch()
###Fetching data
data = User.get_by_name('jaskaran')
##HERE WILL BE GOOD TO PRINT DATA.KEY, FOR EXAMPLE,
##TO MAKE SURE THAT YOU ARE ACTUALLY RETRIEVING SOMETHING
print(data.key)
print(Token.get_by_user(data.key)
As a side note, the docs also explain that Account.get_by_id(...) is faster than Account.query(...).get(), therefore removing the .get() will be a good update to the code.

Retrieving Datastore Object With Key

Using examples from the GAE documentation I have successfully put and object to the datastore as I can view it in the admin console. Retrieving has been difficult, here is my code.
import webapp2
from google.appengine.ext import ndb
user_key = ndb.Key('Info_model', 'Bill')
class Info_model(ndb.Model):
username = ndb.StringProperty()
phone = ndb.IntegerProperty()
active = ndb.BooleanProperty()
class Create_entity(webapp2.RequestHandler):
def get(self):
user1 = Info_model(username = 'Bill',
phone = 1231231234,
active = False)
user1.put()
self.response.write('<!doctype html><html><body>Entity created.<pre>')
self.response.write('</pre></body></html>')
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.out.write('<html><body>')
#get object from datastore using example from GAE Documentation.
user_key = ndb.Key('Info_model', 'Bill')
user1 = user_key.get()
self.response.write(user1.username)
self.response.write(user1.phone)
self.response.write(user1.active)
self.response.write('</body></html>')
application = webapp2.WSGIApplication([
('/', MainPage),
('/create', Create_entity)
], debug=True)
From datastore documentation:
It says:
Retrieving Entities from Keys
Given an entity's key, you can retrieve the entity from the Datastore:
sandy = sandy_key.get()
I believe this example assumes we have set a variable named sandy_key as an ndb.Key() but it doesn't really say.
I have successfully run all the ndb tutorial examples but they create a new object for each entry. I want to have only one object, call it up, edit it and put() it again. I suspect I have made the key incorrectly or called it incorrectly. I have also tried:
user1 = ndb.get('agxkZXZ-aWZnYWxlcnRyFwsSCkluZm9fbW9kZWwYgICAgICAgAkM')
Having copied the key from the admin console. This does not work, Eclipse code editor says, "Undefined variable frome import:get". I have tried a different example from the GAE documentation:
# Create an entity and write it to the Datastore.
ent = MyModel(name='booh', xyz=[10**100, 6**666])
assert ent.abc == 0
key = ent.put()
# Read an entity back from the Datastore and update it.
ent = key.get()
ent.abc += 1
ent.xyz.append(ent.abc//3)
ent.put()
But this seems to be made for all being in one scope. If I create an object in one class then try to retrieve it in another class, the variable ent in ent=key.get() is undefined.
As well I have tried many other examples in the documentation but many are incomplete and assume the reader is not a novice.
Given I have an object in the datastore, how can I retrieve that object specifically and print it out like the following:
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.out.write('<html><body>')
#get object from datastore using example from GAE Documentation.
user_key = ndb.Key('Info_model', 'Bill')
user1 = user_key.get()
self.response.write(user1.username)
self.response.write(user1.phone)
self.response.write(user1.active)
self.response.write('</body></html>')
Sorry for the noob question, if there is a more appropriate forum for beginner GAE programmers please let me know.
The problem is, that you have created a key here ...
user_key = ndb.Key('Info_model', 'Bill')
... but this is never put() to the datastore.
Later (in your Create_entity get method) you are using ...
user1 = Info_model(username = 'Bill',
phone = 1231231234,
active = False)
user1.put()
... and this is correctly 'putting' the Info_model entity into the datastore.
However, this code ...
user_key = ndb.Key('Info_model', 'Bill')
user1 = user_key.get()
... attempts to get an entity of kind Info_model from the datastore with key_name "Bill", but this is not what you put into the datastore in the first place.
Maybe what you are trying to achieve, is to create an entity of kind Info_model, with key_name 'Bill', which you can then later get by keyname? If that is the case, try this code ...
user1 = Info_model.get_or_insert('Bill',
username='Bill',
phone=1231231234,
active=False)
user2_key = ndb.Key('Info_model', 'Bill')
user2 = user2_key.get()
assert user1.key == user2.key
Do be aware, however, that an entities key_name is final - you cannot change this later. You may want to consider how you would handle the scenario where Bill wanted to change his username.
As a side note, I would recommend using an ndb.StringProperty for the phone property, as often phone numbers start with a 0, or contain spaces or characters such as +

Simple datastore entity with 2 fields that are also unique

All I am trying to produce is an entity that holds a unique username, and a unique device ID, and the ability to return an error if either of these conditions are not met on submission.
The only way I can see is to perform a query within a transaction, then filter the results. This however requires an ancestor (which seems unnecessary for a single simple entity).
What is the best method to go about doing this?
Here is an example that does what you want.
I put 2 entities to show you also how to make relationships
class Person(ndb.Expando):
registration_date = ndb.DateTimeProperty(auto_now_add=True)
#property
def info(self):
info = PersonInfo.query(ancestor=self.key).get()
return info
class PersonInfo(ndb.Expando):
email = ndb.StringProperty()
nick_name = ndb.StringProperty()
edit_date = ndb.DateTimeProperty(auto_now=True)
Later in the controller for register:
class RegisterPersonHandler(webapp2.RequestHandler):
def get(self):
user = users.get_current_user() #Stub here
if not user:
self.redirect(users.create_login_url(self.request.uri), abort=True)
return
person = Person.get_or_insert(user.user_id())
if not self._register(person, user):
# more logging is needed
logging.warning('Warning registration failed')
return
#ndb.transactional()
def _register(self, person, user):
''' Registration process happens here
'''
# check if the person has info and if not create it
info = PersonInfo.query(ancestor=person.key).get()
if not info:
info = PersonInfo(id=user.user_id(), parent=person.key)
info.nick_name = user.nickname()
info.email = user.email()
info.put()
return True
To answer also the comment question:
How can you programatically tell whether the returned entity is a new
or existing one though?
Try checking against a property that is default. Eg creation_date etc.
Though you can also check on something you need or on another entity's existence like I do because I expect the data to be consistent, and if not then create the bond.

Using ndb.KeyProperty how to reference the same model?

I have a simple scenario where there is a User class which has the name, email and followers property.
class User(ndb.Model):
name = ndb.StringProperty(required = True)
search_name = ndb.StringProperty(required = True)
pw_hash = ndb.StringProperty(required = True)
email = ndb.StringProperty()
follows = ndb.KeyProperty(Follow, repeated=True)
followers = ndb.KeyProperty(User, repeated=True)
While executing this I am getting the error.
File "C:\ujjal\my project\cravel\code2\Users.py", line 46, in User
followers = ndb.KeyProperty(User, repeated=True)
NameError: name 'User' is not defined
INFO 2012-09-11 11:45:23,953 dev_appserver.py:2967] "GET / HTTP/1.1" 500 -
Any suggestion as to how model the "followers" attribute would be highly appreciated.
Thanks in Advance
Using a kind on a key property e.g. some_prop = ndb.KeyProperty(User) only enforces that the kind of the key must be of kind User. So you can still use a KeyProperty without a kind if need be.
However, if you want to enforce that all follower keys must be of kind User(inside the User model) then surround the kind with quotes:
followers = ndb.KeyProperty(kind='User', repeated=True)
It's explained a little better in the ndb cheat sheet
If you just want "followers" to be a KeyProperty then just put:
followers = ndb.KeyProperty(repeated=True)
or, what I think you are after, to specify the type of key.
follows = ndb.KeyProperty(kind=User,repeated=True)
I think you are just missing kind=User probably.
https://developers.google.com/appengine/docs/python/ndb/properties#types

How to structure movies database and user choices?

I would like to create movies database, where user will be able to mark movies he/she watched and liked:
class Movies(ndb.Model):
watched = ndb.UserProperty()
liked = ndb.UserProperty()
Will that work? I use Google accounts.
How should I choose later all movies user liked?
Upd. I've followed systempuntoout approach and use the following code to save user choices:
user = users.get_current_user()
if user:
userschoices = models.UsersChoices(
movie=ndb.Key(models.Movies, movie_id), # TODO: what if movie_id is wrong?
watched=True,
user_id=user.user_id()
)
try:
userschoices.put()
self.response.out.write('1')
except:
self.response.out.write('0')
But if user makes his choice several times, then several records are added to the datastore...
Wouldn't be it better just to save user id and movie id as keyname?
userschoices = models.UsersChoices.get_by_id(user.user_id() + '-' + movie_id)
if userschoices is None:
userschoices = models.UsersChoices(id=user.user_id() + '-' + movie_id)
userschoices.movie = ndb.Key(models.Movies, movie_id) # TODO: what if movie_id is wrong?
userschoices.user_id = user.user_id()
if option == 'liked':
userschoices.liked = True
elif option == 'watched':
userschoices.watched = True
However, with such approach if I don't pass liked, then it overwrites its value with None (the same with watched, if not passed, None is used).
I would go with two different Models, one that stores all the Movies details and one to store the UserChoices :
class Movies(ndb.Model):
title = ndb.StringProperty(required=True)
director = ndb.StringProperty()
whatever = ndb.StringProperty()
class UsersChoices(ndb.Model):
movie = ndb.KeyProperty(kind=Movies, required=True)
watched = ndb.BooleanProperty(required=True)
liked = ndb.BooleanProperty(required=True)
user_id = ndb.StringProperty(required=True)
#classmethod
def get_liked_movies(cls, user_id):
return cls.query(cls.user_id == user_id, cls.liked == true).fetch(10)
#classmethod
def get_watched_movies(cls, user_id):
return cls.query(cls.user_id == user_id, cls.watched == true).fetch(10)
#classmethod
def get_by(cls, user_id, movie_key):
return cls.query(cls.user_id == user_id, cls.movie == movie_key).get()
If you need to store informations about users you should create your UserInfo Model, keyed by user_id from the users API, with all the details Properties your application needs.
class UserInfo(ndb.Model):
#Keyed by user_id
nickname = ndb.StringProperty()
email = ndb.StringProperty()
To create a new UserInfo, you could do:
from google.appengine.api import users
user = users.get_current_user()
userinfo = UserInfo(
id = user.user_id(),
nickname = user.keyname(),
email = user.email()
)
userinfo.put()
Then, when the user is logged in, use his/her user_id to retrieve the watched/liked movies.
from google.appengine.api import users
user = users.get_current_user()
userinfo = ndb.Key(UserInfo, user.user_id()).get()
watched_movies = UsersChoices.get_watched_movies(userinfo.key.id())
liked_movies = UsersChoices.get_liked_movies(userinfo.key.id())
It appears you are trying to model a many-to-many relationship. There are a few ways to model this relationship (see the Many-to-Many section). See also Nick's blog. (Unfortunately, neither of those references are written for NDB, so, for example, you can't use collection_name, i.e., back-references. But they are still useful in showing you how to break up the data into different models.)
Here's one way you could do it, using "join tables"/"relationship models":
class Movie(ndb.Model):
title = ndb.StringProperty(required=True)
class LikedMovie(ndb.Model):
movie = ndb.KeyProperty(kind=Movie, required=True)
user = ndb.StringProperty(required=True) # user.user_id()
class WatchedMovie(ndb.Model):
movie = ndb.KeyProperty(kind=Movie, required=True)
user = ndb.StringProperty(required=True) # user.user_id()
...
movies_user_likes = LikedMovie.query(LikedMovie.user == user.user_id()).fetch()
Depending on how many users your application will support, and how often the database will be updated, it may be more efficient to use repeated properties (i.e., lists of users) instead of join tables:
class Movie(ndb.Model):
title = ndb.StringProperty(required=True)
users_who_watched = ndb.StringProperty(repeated=True) # list of user.user_id()s
users_who_liked = ndb.StringProperty(repeated=True) # list of user.user_id()s
...
movies_user_likes = Movie.query(Movie.users_who_liked == user.user_id()).fetch(projection=[Movie.title])
Note that I used a projection query above, so that the users_who_watched lists are not returned with the query results. You probably don't need these, and this should make fetching significantly faster.
If you expect, say, less than 1,000 users to watch or like a particular movie, the list approach might be better.
For a more advanced technique, see Building Scalable, Complex Apps on App Engine, where Brett shows how to move the repeated/list property into a separate model, using parent keys.

Resources