Milad
Milad

Reputation: 125

Default value for ndb.KeyProperty

I have these two models:

#################
### Usergroup ###
#################

class Usergroup(ndb.Model):
    group_name = ndb.StringProperty(indexed = False, required = True)
    is_admin_group = ndb.BooleanProperty(indexed = False, required = False, default = False)

############
### User ###
############

class User(ndb.Model):
    fb_id = ndb.StringProperty(indexed = True, required = True)
    fb_access_token = ndb.TextProperty(indexed = False, required = True)
    email = ndb.StringProperty(indexed = True, required = True)
    first_name = ndb.StringProperty(indexed = False, required = True)
    last_name = ndb.StringProperty(indexed = False, required = True)
    gender = ndb.StringProperty(indexed = False)
    group_key = ndb.KeyProperty(indexed = False, required = False, kind = Usergroup, default = ndb.Key(Usergroup, 'member'))
    join_date = ndb.DateTimeProperty(indexed = True, auto_now_add = True)
    last_login = ndb.DateTimeProperty(indexed = True, auto_now_add = True, auto_now = False)

@app.route('/user/login', methods=['POST'])
def user_login():
    me = exchange_token_me(request.form['accessToken'])
    if me is not False:
        user = user_find_or_register(me)
        if user is not None:
            register_session(user)
            return 'success'
    return 'error'

def user_find_or_register(user):
    qry = User.query(User.fb_id == user['id'])
    existing_user = qry.get()

    if existing_user is not None:
        existing_user.fb_access_token = user['access_token']
        existing_user.fb_id = user['id']
        existing_user.email = user['email']
        existing_user.last_login = datetime.datetime.now()
        existing_user.put()
        return existing_user

    new_user = User()
    new_user.fb_id = user['id']
    new_user.fb_access_token = user['access_token']
    new_user.email = user['email']
    new_user.first_name = user['first_name']
    new_user.last_name = user['last_name']
    new_user.gender = user['gender']
    new_user.last_login = datetime.datetime.now()
    #new_user.group_key = ndb.key(Usergroup, 'member')

    key = new_user.put()
    saved_user = key.get()
    #key.delete() # DEBUG

    if saved_user is not None:
        return saved_user

def register_session(user):
    session['fb_id'] = user.fb_id
    session['first_name'] = user.first_name
    session['last_name'] = user.last_name
    session['group_key'] = user.group_key
    session['loggedin'] = True

The Usergroup model has a small unique string as entity key. There is already a Usergroup whose key is 'member'

Whenever we create/save a user, it should use the key to the 'member' usergroup, but we get this error instead:

TypeError: Key('Usergroup', 'member') is not JSON serializable

Traceback:

ERROR    2016-10-18 14:32:40,572 app.py:1587] Exception on /user/login [POST]
Traceback (most recent call last):
  File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1988, in wsgi_app
    response = self.full_dispatch_request()
  File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1643, in full_dispatch_request
    response = self.process_response(response)
  File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1864, in process_response
    self.save_session(ctx.session, response)
  File "/var/www/mywebsite/public_html/lib/flask/app.py", line 926, in save_session
    return self.session_interface.save_session(self, session, response)
  File "/var/www/mywebsite/public_html/lib/flask/sessions.py", line 359, in save_session
    val = self.get_signing_serializer(app).dumps(dict(session))
  File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 565, in dumps
    payload = want_bytes(self.dump_payload(obj))
  File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 847, in dump_payload
    json = super(URLSafeSerializerMixin, self).dump_payload(obj)
  File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 550, in dump_payload
    return want_bytes(self.serializer.dumps(obj))
  File "/var/www/mywebsite/public_html/lib/flask/sessions.py", line 85, in dumps
    return json.dumps(_tag(value), separators=(',', ':'))
  File "/var/www/mywebsite/public_html/lib/flask/json.py", line 126, in dumps
    rv = _json.dumps(obj, **kwargs)
  File "/usr/lib/python2.7/json/__init__.py", line 251, in dumps
    sort_keys=sort_keys, **kw).encode(obj)
  File "/usr/lib/python2.7/json/encoder.py", line 209, in encode
    chunks = list(chunks)
  File "/usr/lib/python2.7/json/encoder.py", line 434, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "/usr/lib/python2.7/json/encoder.py", line 408, in _iterencode_dict
    for chunk in chunks:
  File "/usr/lib/python2.7/json/encoder.py", line 442, in _iterencode
    o = _default(o)
  File "/var/www/mywebsite/public_html/lib/flask/json.py", line 83, in default
    return _json.JSONEncoder.default(self, o)
  File "/usr/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: Key('Usergroup', 'member') is not JSON serializable
ERROR    2016-10-18 14:32:40,593 main.py:178] An error occurred during a request.
Traceback (most recent call last):
  File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1988, in wsgi_app
    response = self.full_dispatch_request()
  File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1643, in full_dispatch_request
    response = self.process_response(response)
  File "/var/www/mywebsite/public_html/lib/flask/app.py", line 1864, in process_response
    self.save_session(ctx.session, response)
  File "/var/www/mywebsite/public_html/lib/flask/app.py", line 926, in save_session
    return self.session_interface.save_session(self, session, response)
  File "/var/www/mywebsite/public_html/lib/flask/sessions.py", line 359, in save_session
    val = self.get_signing_serializer(app).dumps(dict(session))
  File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 565, in dumps
    payload = want_bytes(self.dump_payload(obj))
  File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 847, in dump_payload
    json = super(URLSafeSerializerMixin, self).dump_payload(obj)
  File "/var/www/mywebsite/public_html/lib/itsdangerous.py", line 550, in dump_payload
    return want_bytes(self.serializer.dumps(obj))
  File "/var/www/mywebsite/public_html/lib/flask/sessions.py", line 85, in dumps
    return json.dumps(_tag(value), separators=(',', ':'))
  File "/var/www/mywebsite/public_html/lib/flask/json.py", line 126, in dumps
    rv = _json.dumps(obj, **kwargs)
  File "/usr/lib/python2.7/json/__init__.py", line 251, in dumps
    sort_keys=sort_keys, **kw).encode(obj)
  File "/usr/lib/python2.7/json/encoder.py", line 209, in encode
    chunks = list(chunks)
  File "/usr/lib/python2.7/json/encoder.py", line 434, in _iterencode
    for chunk in _iterencode_dict(o, _current_indent_level):
  File "/usr/lib/python2.7/json/encoder.py", line 408, in _iterencode_dict
    for chunk in chunks:
  File "/usr/lib/python2.7/json/encoder.py", line 442, in _iterencode
    o = _default(o)
  File "/var/www/mywebsite/public_html/lib/flask/json.py", line 83, in default
    return _json.JSONEncoder.default(self, o)
  File "/usr/lib/python2.7/json/encoder.py", line 184, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: Key('Usergroup', 'member') is not JSON serializable
INFO     2016-10-18 14:32:40,610 module.py:788] default: "POST /user/login HTTP/1.1" 500 27

UPDATE: After Dan has spotted the problem, the solution is in the following function:

def register_session(user):
    session['fb_id'] = user.fb_id
    session['first_name'] = user.first_name
    session['last_name'] = user.last_name
    session['group_key'] = user.group_key.id() # Thanks Dan
    session['loggedin'] = True

Upvotes: 1

Views: 382

Answers (1)

Dan Cornilescu
Dan Cornilescu

Reputation: 39824

FWIW, a quick test with your code as models.py shows this to be working just fine, at least on the development server:

    from models import User

    user = User(email='email', username='username')
    user.put()

produced:

enter image description here

This worked without even having a Usergroup entity - a key can exist without a matching entity. Of course, trying to follow the link to the Usergroup in the datastore viewer fails:

Traceback (most recent call last):
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 1536, in __call__
    rv = self.handle_exception(request, response, e)
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 1530, in __call__
    rv = self.router.dispatch(request, response)
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "/home/usr_local/google_appengine/google/appengine/tools/devappserver2/admin/admin_request_handler.py", line 96, in dispatch
    super(AdminRequestHandler, self).dispatch()
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "/home/usr_local/google_appengine_1.9.40/lib/webapp2-2.5.1/webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "/home/usr_local/google_appengine/google/appengine/tools/devappserver2/admin/datastore_viewer.py", line 741, in get
    entities = [datastore.Get(entity_key)]
  File "/home/usr_local/google_appengine/google/appengine/api/datastore.py", line 671, in Get
    return GetAsync(keys, **kwargs).get_result()
  File "/home/usr_local/google_appengine/google/appengine/api/apiproxy_stub_map.py", line 613, in get_result
    return self.__get_result_hook(self)
  File "/home/usr_local/google_appengine/google/appengine/datastore/datastore_rpc.py", line 1717, in __get_hook
    entities = extra_hook(entities)
  File "/home/usr_local/google_appengine/google/appengine/api/datastore.py", line 640, in local_extra_hook
    raise datastore_errors.EntityNotFoundError()
EntityNotFoundError

So you might want to show your actual code creating the entity and the full traceback, something else may be going on.

Your traceback indicates that the problem is not with creating the entity, but with saving your session:

File "/var/www/mywebsite/public_html/lib/flask/app.py", line 926, in save_session
    return self.session_interface.save_session(self, session, response)

It appears you included in the session content the key object (possibly by including the entire User entity?), which is what causes the failure. For that purpose keys need to be serialized and you can use key.urlsafe() for that. See this answer for an example: https://stackoverflow.com/a/34835074/4495081

If indeed you included the entire User entity in the session you can just include its urlsafe key instead.

Yup, this is the source of your problem:

session['group_key'] = user.group_key

change it to:

session['group_key'] = user.group_key.urlsafe()

And you'll retrieve it like this:

urlsafe_key = session.get('group_key')
if urlsafe_key:
    group_key = ndb.Key(urlsafe=urlsafe_key)

Upvotes: 1

Related Questions