Google App Engine - storing key into ndb KeyProperty
<p>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. </p>

<p>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>

<pre><code>p_key= ndb.KeyProperty(Post), 
p_key= ndb.KeyProperty(kind="Post"), 
p_key= ndb.KeyProperty(kind=Post, repeated=True) but none of these worked.
</code></pre>

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

<p>Here's the Code.</p>

<pre><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', '&lt;br&gt;')
        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', '&lt;br&gt;')
        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'] = self.user.name

        if not post:
            self.error(404)
            return

        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)
            r.put()

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

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

class NewPost(Handler):
    def get(self):
        if self.user:
            self.render("newpost.html")
        else:
            self.redirect("/login")

    def post(self):
        if not self.user:
            self.redirect('/')

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

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

app = webapp2.WSGIApplication([('/', MainPage),
                           ('/stories/([0-9]+)', PostPage),
                           ('/stories/newpost', NewPost),
                           ], 
                            debug=True)
</code></pre>

<p>Below is the error message.</p>

<pre><code>Traceback (most recent call last):
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1535, in __call__
    rv = self.handle_exception(request, response, e)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1529, in __call__
    rv = self.router.dispatch(request, response)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/webapp2-2.5.2/webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "/Users/young-junpark/kloupod-143223/main.py", line 240, in post
    r = Reply(content=reply_content, p_key=p_key, user=user)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/model.py", line 2947, in __init__
    self._set_attributes(kwds)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/model.py", line 2993, in _set_attributes
    prop._set_value(self, value)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/model.py", line 1145, in _set_value
    value = self._do_validate(value)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/model.py", line 1092, in _do_validate
    value = self._call_shallow_validation(value)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/model.py", line 1284, in _call_shallow_validation
    return call(value)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/model.py", line 1331, in call
    newvalue = method(self, value)
  File "/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ndb/model.py", line 1781, in _validate
    (value,))
BadValueError: Expected string, got User(key=Key('users', 'default', 'User', 5629499534213120), created=datetime.datetime(2016, 10, 4, 23, 12, 27, 1990), email=u'youngtheabsolute@gmail.com', name=u'\ubc15\uc6a9\uc900', pw_hash=u'JsIho,9c9f5b4b3a19213e8a84318db6d2e94179678d2d7f22cce6af9b30a558423b28', verification_code=u'12345', verified=False)
</code></pre>

<p>I really appreciate your help! </p>

<p>The error tells you what's happening:</p>

<pre><code>BadValueError: Expected string, got User
</code></pre>

<p>If you look a little further back in the traceback, you see:</p>

<pre><code>post r = Reply(content=reply_content, p_key=p_key, user=user) 
</code></pre>

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

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

<p>You can accomplish this using <a href="https://cloud.google.com/appengine/docs/python/ndb/entity-property-reference#structured" rel="nofollow">structured properties</a>:</p>

<pre><code>user = ndb.StructuredProperty(User)
</code></pre>

<p>UPDATE:
After going through your comments, it seems you have another problem.</p>

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

<pre><code>u"Key('question', 'default', 'Post', 123123213123)
</code></pre>

<p>I'm assuming that you send that key back to <code>PostPage.post()</code>. So when you try and create a <code>Reply</code> using that key in:</p>

<pre><code>r = Reply(content=reply_content, p_key=p_key, user=user)
</code></pre>

<p>It fails saying that it expected a <code>ndb.Key</code> but instead received a string that looks like a key.  You should consider using <code>urlsafe</code> keys.  <a href="https://cloud.google.com/appengine/docs/python/ndb/creating-entities" rel="nofollow">This page</a> on Google has a good explanation of how to use <code>ndb</code>.</p>

python google-app-engine google-cloud-datastore webapp2 google-app-engine-python