Young Young - 21 days ago 21
Python Question

Google App Engine - storing key into ndb KeyProperty

I am creating a commenting system using Google App Engine with webapp2 using ndb datastore. I created a property KeyProperty so I can fetch comments associated with posts with the same key.

However, I keep receiving an error message whenever I try to store the key of a post into ndb.KeyProperty. I tried changing the KeyProperty to

p_key= ndb.KeyProperty(Post),
p_key= ndb.KeyProperty(kind="Post"),
p_key= ndb.KeyProperty(kind=Post, repeated=True) but none of these worked.

When I was using db model with ReferenceProperty, the app was behaving the way I wanted to.

Here's the Code.

def question_key(name = 'default'):
return ndb.Key('questions', name)

class Post(ndb.Model):
question = ndb.StringProperty(required = True)
created = ndb.DateTimeProperty(auto_now_add = True)
last_modified = ndb.DateTimeProperty(auto_now = True)
user = ndb.StringProperty()

def render(self):
self._render_text = self.question.replace('\n', '<br>')
return render_str("post.html", p = self)

class Reply(ndb.Model):
content = ndb.TextProperty(required = True)
p_key= ndb.KeyProperty(kind=Post)
user = ndb.StringProperty()
created = ndb.DateTimeProperty(auto_now_add = True)
last_modified = ndb.DateTimeProperty(auto_now = True)

def render(self):
self._render_text = self.content.replace('\n', '<br>')
return self._render_text

class PostPage(Handler):
def get(self, post_id):
key = ndb.Key('Post', int(post_id), parent=question_key())
post = key.get()

params = dict(post=post)
#retrieve all the comments and then filter it by key
if Reply.query():
reply = Reply.query(Reply.p_key == key)
params['reply'] = reply

if self.user:
params['current_user'] =

if not post:

self.render("permalink.html", **params)

def post(self, post_id):
reply_content = self.request.get('reply')
p_key = self.request.get('p_key') #get post key from template
user = self.user

if reply_content:
r = Reply(content=reply_content, p_key=p_key, user=user)

self.redirect('/stories/%s' % str(post_id))

error = "error"
self.redirect('/stories/%s' % str(post_id))

class NewPost(Handler):
def get(self):
if self.user:

def post(self):
if not self.user:

question = self.request.get('question')

if question:
p = Post(parent = question_key(), question = question, user=user)
self.redirect('/stories/%s' % str(
error = "error"
self.render("newpost.html", error=error)

app = webapp2.WSGIApplication([('/', MainPage),
('/stories/([0-9]+)', PostPage),
('/stories/newpost', NewPost),

Below is the error message.

Traceback (most recent call last):
File "/Applications/", line 1535, in __call__
rv = self.handle_exception(request, response, e)
File "/Applications/", line 1529, in __call__
rv = self.router.dispatch(request, response)
File "/Applications/", line 1278, in default_dispatcher
return route.handler_adapter(request, response)
File "/Applications/", line 1102, in __call__
return handler.dispatch()
File "/Applications/", line 572, in dispatch
return self.handle_exception(e,
File "/Applications/", line 570, in dispatch
return method(*args, **kwargs)
File "/Users/young-junpark/kloupod-143223/", line 240, in post
r = Reply(content=reply_content, p_key=p_key, user=user)
File "/Applications/", line 2947, in __init__
File "/Applications/", line 2993, in _set_attributes
prop._set_value(self, value)
File "/Applications/", line 1145, in _set_value
value = self._do_validate(value)
File "/Applications/", line 1092, in _do_validate
value = self._call_shallow_validation(value)
File "/Applications/", line 1284, in _call_shallow_validation
return call(value)
File "/Applications/", line 1331, in call
newvalue = method(self, value)
File "/Applications/", line 1781, in _validate
BadValueError: Expected string, got User(key=Key('users', 'default', 'User', 5629499534213120), created=datetime.datetime(2016, 10, 4, 23, 12, 27, 1990), email=u'', name=u'\ubc15\uc6a9\uc900', pw_hash=u'JsIho,9c9f5b4b3a19213e8a84318db6d2e94179678d2d7f22cce6af9b30a558423b28', verification_code=u'12345', verified=False)

I really appreciate your help!


The error tells you what's happening:

BadValueError: Expected string, got User

If you look a little further back in the traceback, you see:

post r = Reply(content=reply_content, p_key=p_key, user=user) 

In your Reply class, you set user to be a ndb.StringProperty, but in that line of code, it looks like you are passing a full User object, not just a string.

If you change the user attribute of Reply to be a User type not a StringProperty, then it looks like everything should work correctly.

You can accomplish this using structured properties:

user = ndb.StructuredProperty(User)

UPDATE: After going through your comments, it seems you have another problem.

You create the ndb.Key("Post", post_id, parent=question_key()) which you then send to a template in PostPage.get(). This key is encoded as a string in the template that looks something like:

u"Key('question', 'default', 'Post', 123123213123)

I'm assuming that you send that key back to So when you try and create a Reply using that key in:

r = Reply(content=reply_content, p_key=p_key, user=user)

It fails saying that it expected a ndb.Key but instead received a string that looks like a key. You should consider using urlsafe keys. This page on Google has a good explanation of how to use ndb.