How to structure movies database and user choices? - google-app-engine

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.

Related

Got AttributeError when attempting to get a value for field `people` on serializer `commentsSerializer`

I am building a blog website and I am using Django rest framework
I want to fetch top 2 comments for a particular post along with their related data such as user details.
Now I have user details in two models
User
People
and the comments model is related to the user model using foreign key relationship
Models ->
Comments
class Comment(models.Model):
comment = models.TextField(null=True)
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
post = models.ForeignKey(Post, on_delete=models.CASCADE,
related_name='comments_post')
user = models.ForeignKey(User, on_delete=models.CASCADE,
related_name='comments_user')
The People model is also connected to the user model with a foreign key relationship
People Model ->
class People(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE,related_name='people')
Name = models.CharField(max_length=255,null=True)
following = models.ManyToManyField(to=User, related_name='following', blank=True)
photo = models.ImageField(upload_to='profile_pics', blank=True,null=True)
Phone_number = models.CharField(max_length=255,null=True,blank=True)
Birth_Date = models.DateField(null=True,blank=True)
Created_date = models.DateTimeField(auto_now_add=True)
Updated_date = models.DateTimeField(auto_now=True)
for fetching the comments I am using rest-framework and the serializers look like this
class UserSerializer(serializers.Serializer):
username = serializers.CharField(max_length=255)
class peopleSerializer(serializers.Serializer):
Name = serializers.CharField(max_length=255)
class commentsSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
comment = serializers.CharField(max_length=255)
Created_date = serializers.DateTimeField()
user = UserSerializer()
people = peopleSerializer()
The query to fetch the comments look like this ->
post_id = request.GET.get('post_id')
comments = Comment.objects.filter(post_id=post_id).select_related('user').prefetch_related('user__people').order_by('-Created_date')[:2]
serializer = commentsSerializer(comments, many=True)
return Response(serializer.data)
I am getting this error ->
Got AttributeError when attempting to get a value for field `people` on serializer `commentsSerializer`. The serializer field might be named incorrectly and not match any attribute or key on the `Comment` instance. Original exception text was: 'Comment' object has no attribute 'people'.
Unable to find a way out.
The source is user.people, not people, so:
class commentsSerializer(serializers.Serializer):
# …
people = peopleSerializer(source='user.people')
In the .select_related(…) [Django-doc] to can specify user__people: this will imply selecting user and will fetch the data in the same query, not in an extra query as is the case for .prefetch_related(…) [Django-doc]:
post_id = request.GET.get('post_id')
comments = Comment.objects.filter(
post_id=post_id
).select_related('user__people').order_by('-Created_date')[:2]
serializer = commentsSerializer(comments, many=True)
return Response(serializer.data)
Note: normally a Django model is given a singular name, so Person instead of People.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: normally the name of the fields in a Django model are written in snake_case, not PascalCase, so it should be: created_date instead of Created_date.

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.

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.

Updating existing entity in endpoints-proto-datastore

I am using Endpoints-proto-datastore written by Danny Hermes for Google App Engine and need help figuring out how to update an entity.. My model for what I need to update is the following
class Topic(EndpointsModel):
#_message_fields_schema = ('id','topic_name','topic_author')
topic_name = ndb.StringProperty(required=True)
topic_date = ndb.DateTimeProperty(auto_now_add=True)
topic_author = ndb.KeyProperty(required=True)
topic_num_views = ndb.IntegerProperty(default=0)
topic_num_replies = ndb.IntegerProperty(default=0)
topic_flagged = ndb.BooleanProperty(default=False)
topic_followers = ndb.KeyProperty(repeated=True)
topic_avg_rating = ndb.FloatProperty(default=0.0)
topic_total_rating = ndb.FloatProperty(default=0.0)
topic_num_ratings = ndb.IntegerProperty(default=0)
topic_raters = ndb.KeyProperty(repeated=True)
And as you can see, the rating properties have a default of 0. So each time a topic is rated, I need to update each of the rating properties. However, none of my properties is the actual rating being provided by the user. How can i pass in the value the user rated the topic to be able to update the properties in the model? Thanks!
You can do this by having an "alias" property called rating associated with your UserModel:
from endpoints_proto_datastore.ndb import EndpointsAliasProperty
class UserModel(EndpointsModel):
...
def rating_set(self, value):
# Do some validation
self._rating = value
#EndpointsAliasProperty(setter=rating_set)
def rating(self):
return self._rating
This will allow ratings to be sent with UserModels in requests but won't require those ratings to be stored.
You're better off using the OAuth 2.0 token for the user and calling endpoints.get_current_user() to determine who the user is in the request.
Something like a dedicated model for ratings could be much easier:
from endpoints_proto_datastore.ndb import EndpointsUserProperty
class Rating(EndpointsModel):
rater = EndpointsUserProperty(raise_unauthorized=True)
rating = ndb.IntegerProperty()
topic = ndb.KeyProperty(kind=Topic)
and then transactionally retrieving the Topic from the datastore and updating it in a request method decorated by #Rating.method.

How to use NDB's id property?

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

Resources