NDB: How can I retrieve next/previous entity? - google-app-engine

I'm writing an application for a factory where I need to provide a way for the user to retrieve the next (or previous) entity of a kind in NDB and can't figure out how to do this. Any help /tips will be appreciated!
Assume I have the following simplified model:
from google.appengine.ext import ndb
class Product(ndb.Model):
prod_id = ndb.StringProperty(required = True)
prod_desc = ndb.StringProperty(required = True)
prod_units = ndb.StringProperty(required = True)
... other properties
To allow the user to find a particular product I use the query below ('find' comes from a form filled in by the user).
Products_Str = 'Products' # The string ID for the common products ancestor
...
def get_products_key(products_key_str = Products_Str):
return ndb.Key('Products', Products_Str)
class DisplayProduct(BaseHandler): # Displays a product found on exact prod_id property
def post(self):
search_key = self.request.get('find')
find_query = Product.query(Product.prod_id == search_key, ancestor = get_products_key()).get()
... here I display the one result of the query, i.e. the requested product
So far so good: I provide the user with a way to find a specific product based on code (or description).
Now I need to place two buttons on the display page named "previous" and "next" and I can't figure out how to retrieve the next and previous products.
I would welcome any suggestions.

Related

Django Rest returning related items for each item

I was trying to find answer in similiar questions, but none was meeting my expectations.
I have 2 models:
class Artist(models.Model):
name = models.CharField(max_length=255)
music_type = models.CharField(max_lenght=50)
def __str__(self):
return self.name
class Event(models.Model):
event_name = models.CharField(max_length=255)
...
artists = models.ManyToManyField(Artist)
def __str__(self):
return self.event_name
I also have serializers.py file:
class EventSerializer(serializers.ModelSerializer):
class Meta:
model = Event
fields = '__all__'
class ArtistSerializer(serializers.ModelSerializer):
events = EventSerializer(source='event_set', many=True)
class Meta:
model = Artist
fields ='__all__'
The event in ArtistSerializer allows me to return all events where artist takes part.
Now for each artist I would like to get list of all artists if they ever were taking part in the same event.
For example I have 5 Artists (A1...A5) and 3 Events (E1...E3)
In Event 1: [A1,A3]
In Event 2: [A3,A4,A5,A2]
In Event 3: [A2, A3]
So for A3 I would like to get list [A1,A4,A5,A2]
For A1: [A3]
For A2: [A3,A4,A5]
Unfortunately I have huge problem to create this query as SQL-query and ORM mechanism looks more complicated in this situation. Can somebody help me with this problem or give hints how to solve this?
If it's needed I'm gonna share more code
You can query the through model to get the artists related to an event. This is the intermediate model which django will have created to make that M2M relationship.
Where you have a ManyToManyField it has an attribute of through which is the M2M model.
So from your event model you could do something like Event.artists.through.objects.all() and you'd see all the instances in your M2M model.
So to find out the artists which are linked to a given event you could query that same table;
Event.artists.through.objects.filter(event_id=1).select_related('artist')
This would then return all the objects in the M2M which belong to Event 1. You could then get the artists from there, or just grab the artist IDs Event.artists.through.objects.filter(event_id=1).values_list('artist_id, flat=True)
Given the scenario in your comment...
If you have an artist, then you can run a query to get the events they've been in, and then run another query with those event ids. In that second query you are then looking to get the artist ids that aren't the current artist you're already looking at.
# First get the events that the current artist has been in
event_ids = Event.artists.through.objects.filter(artist_id=1).values_list('event_id, flat=True)
# Then get the other artists who have been in the same events
other_artist_ids = Event.artists.through.objects.filter(event_id__in =event_ids).exclude(artist_id=1).values_list('artist_id, flat=True)
# or the full instances
other_artists = Event.artists.through.objects.filter(event_id__in =event_ids).exclude(artist_id=1).select_related('artist')

Replace Django ManyToMany fields with chained strings or convert to first normal form

Suppose that I have a Position table that specifies a user's position (leader, assistant, member, ...):
class Position(models.Model):
name = CharField(max_length=20)
A document has a title and privileges, namely, create, modify, and so on. We may specify which user positions may perform which privileges; e.g., a leader or an assistant may create a document:
class Document(models.Model):
title = CharField(max_length=50)
create = ManyToManyField(Position)
modify = ManyToManyField(Position)
...
Of course, the above arrangement is not acceptable in Django because there should be only one ManyToManyField in a model.
I wonder which one of the followings is a better solution:
(a) Chain the positions (provided the number of positions are not great and the position data will not change):
class = Document(...):
...
create = CharField(max_length=100)
modify = CharField(max_length=100)
where create may be leader-assistance-member, which means a leader, an assistance, or a member may create a document. All we have to do is to split the string to obtain a list of positions.
(b) Convert to 1NF:
class = Document(...)
title = ...
class Create(...):
document = ForeignKey(Document)
position = ForeignKey(Position)
class Modify(...):
document = ForeignKey(Document)
position = ForeignKey(Position)
I know this question is old, but per Django docs, you could have multiple many-to-many fields referencing the same model. You'd just need to specify a related_name to differentiate them.
class Document(models.Model):
title = models.CharField(max_length=50)
create = models.ManyToManyField(Position, related_name='create_positions')
modify = models.ManyToManyField(Position, related_name='modify_positions')
...

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 structure movies database and user choices?

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.

Resources