How to structure movies database and user choices?
Asked Answered
P

2

3

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).

Paleoclimatology answered 29/7, 2012 at 16:53 Comment(1)
Why don't you pass the default argument to the properties that are initialized to None so that every-time the are initialized with true or false depending to your implementationFlyfish
C
8

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())
Chittagong answered 30/7, 2012 at 15:43 Comment(2)
Thanks, the main question here is how to link users (developers.google.com/appengine/docs/python/users/overview) with UsersChoices?Paleoclimatology
Thank you! I've updated the original question with the question I have nowPaleoclimatology
A
3

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.

Asseverate answered 30/7, 2012 at 16:20 Comment(1)
Thanks, Mark. I have the same question what I've asked to @Chittagong - how to link these classes with Users (developers.google.com/appengine/docs/python/users/overview)?Paleoclimatology

© 2022 - 2024 — McMap. All rights reserved.