I currently have a model in NDB and I'd like to add a new property to it. Let's say I have the following:
class User(Model, BaseModel):
name = ndb.StringProperty(required=False)
email = ndb.StringProperty(required=False)
#property
def user_roles(self):
return UserRole.query(ancestor=self.key).fetch()
#property
def roles(self):
return [user_role.role for user_role in UserRole.query(ancestor=self.key).fetch()]
Now, let's say, I've added one additional property called market_id. For example,
class User(Model, BaseModel):
name = ndb.StringProperty(required=False)
email = ndb.StringProperty(required=False)
#property
def user_roles(self):
return UserRole.query(ancestor=self.key).fetch()
#property
def roles(self):
return [user_role.role for user_role in UserRole.query(ancestor=self.key).fetch()]
#property
def market_id(self):
""" fetches `id` for the resource `market` associated with `user` """
for each_role in UserRole.query(ancestor=self.key):
resource = each_role.role.get().resource
if resource.kind() == 'Market':
return resource.id()
return None
The problem here is, roles are fetched properly as expected for all the existing entities (since that property had been there since the beginning and also, an extra column can be observed in datastore called roles).
Since, I'm dealing with Python class property, I assume that migration is not required. But, how does column called roles already exist? And why newly added property called market_id does not? Does it require migration?
The change you're suggesting is not an actual ndb model change as you're not adding/deleting/modifying any of the model's datastore properties. Only ndb.Property class children are real ndb model properties that are stored when the entity is put() into the datastore.
The property you're adding is a Python class #property - nothing to do with what's in the datastore.
So for this particular case no migration is needed.
The update to the question makes this even more clear, I believe. The market_id #property is not a User datastore entity property. To get values for it you don't need to update the User entity, but you have to create/edit corresponding UserRole entities with their resource property point to a Market entity.
Related
I recently encountered a situation where one might want to run a datastore query which includes a kind, but the class of the corresponding model is not available (e.g. if it's defined in a module that hasn't been imported yet).
I couldn't find any out-of-the-box way to do this using the google.appengine.ext.db package, so I ended up using the google.appengine.api.datastore.Query class from the low-level datastore API.
This worked fine for my needs (my query only needed to count the number of results, without returning any model instances), but I was wondering if anyone knows of a better solution.
Another approach I've tried (which also worked) was subclassing db.GqlQuery to bypass its constructor. This might not be the cleanest solution, but if anyone is interested, here is the code:
import logging
from google.appengine.ext import db, gql
class ClasslessGqlQuery(db.GqlQuery):
"""
This subclass of :class:`db.GqlQuery` uses a modified version of ``db.GqlQuery``'s constructor to suppress any
:class:`db.KindError` that might be raised by ``db.class_for_kind(kindName)``.
This allows using the functionality :class:`db.GqlQuery` without requiring that a Model class for the query's kind
be available in the local environment, which could happen if a module defining that class hasn't been imported yet.
In that case, no validation of the Model's properties will be performed (will not check whether they're not indexed),
but otherwise, this class should work the same as :class:`db.GqlQuery`.
"""
def __init__(self, query_string, *args, **kwds):
"""
**NOTE**: this is a modified version of :class:`db.GqlQuery`'s constructor, suppressing any :class:`db.KindError`s
that might be raised by ``db.class_for_kind(kindName)``.
In that case, no validation of the Model's properties will be performed (will not check whether they're not indexed),
but otherwise, this class should work the same as :class:`db.GqlQuery`.
Args:
query_string: Properly formatted GQL query string.
*args: Positional arguments used to bind numeric references in the query.
**kwds: Dictionary-based arguments for named references.
Raises:
PropertyError if the query filters or sorts on a property that's not indexed.
"""
from google.appengine.ext import gql
app = kwds.pop('_app', None)
namespace = None
if isinstance(app, tuple):
if len(app) != 2:
raise db.BadArgumentError('_app must have 2 values if type is tuple.')
app, namespace = app
self._proto_query = gql.GQL(query_string, _app=app, namespace=namespace)
kind = self._proto_query._kind
model_class = None
try:
if kind is not None:
model_class = db.class_for_kind(kind)
except db.KindError, e:
logging.warning("%s on %s without a model class", self.__class__.__name__, kind, exc_info=True)
super(db.GqlQuery, self).__init__(model_class)
if model_class is not None:
for property, unused in (self._proto_query.filters().keys() +
self._proto_query.orderings()):
if property in model_class._unindexed_properties:
raise db.PropertyError('Property \'%s\' is not indexed' % property)
self.bind(*args, **kwds)
(also available as a gist)
You could create a temporary class just to do the query. If you use an Expando model, the properties of the class don't need to match what is actually in the datastore.
class KindName(ndb.Expando):
pass
You could then do:
KindName.query()
If you need to filter on specific properties, then I suspect you'll have to add them to the temporary class.
I am trying to implement an ndb model audit so that all changes to properties are stored within each model instance. Here is the code of the _pre_put_hook I chose to implement that.
def _pre_put_hook(self):
# save a history record for updates
if not (self.key is None or self.key.id() is None):
old_object = self.key.get(use_cache=True)
for attr in dir(self):
if not callable(getattr(self, attr)) and not attr.startswith("_"):
if getattr(self, attr) != getattr(old_object, attr):
logging.debug('UPDATE: {0}'.format(attr))
logging.debug('OLD: {0} NEW: {1}'.format(getattr(old_object, attr), getattr(self, attr)))
The problem is old_object is always populated with the same values of the self (object) being updated. How can I access the property values of the old object BEFORE the put() being actually made (_pre_put)?
EDIT:
I realized over time I was doing a bunch of work that didn't need to be done (alot of CPU/memory used copying entire entities and passing them around when may not be needed). Here's the updated version which stores a reference to the original protobuf and only deserializes it if you need it
__original = None # a shadow-copy of this object so we can see what changed... lazily inflated
_original_pb = None # the original encoded Protobuf representation of this entity
#property
def _original(self):
"""
Singleton to deserialize the protobuf into a new entity that looks like the original from database
"""
if not self.__original and self._original_pb:
self.__original = self.__class__._from_pb(self._original_pb)
return self.__original
#classmethod
def _from_pb(cls, pb, set_key=True, ent=None, key=None):
"""
save copy of original pb so we can track if anything changes between puts
"""
entity = super(ChangesetMixin, cls)._from_pb(pb, set_key=set_key, ent=ent, key=key)
if entity._original_pb is None and not entity._projection:
# _from_pb will get called if we unpickle a new object (like when passing through deferred library)
# so if we are being materialized from pb and we don't have a key, then we don't have _original
entity.__original = None
entity._original_pb = pb
return entity
Make a clone of the entity when you first read it:
Copy an entity in Google App Engine datastore in Python without knowing property names at 'compile' time
and put it on the entity itself so it can be referenced later when desired. That way you don't have to do a second datastore read just to make the comparison
We override two different Model methods to make this happen:
#classmethod
def _post_get_hook(cls, key, future):
"""
clone this entity so we can track if anything changes between puts
NOTE: this only gets called after a ndb.Key.get() ... NOT when loaded from a Query
see _from_pb override below to understand the full picture
also note: this gets called after EVERY key.get()... regardless if NDB had cached it already
so that's why we're only doing the clone() if _original is not set...
"""
entity = future.get_result()
if entity is not None and entity._original is None:
entity._original = clone(entity)
#classmethod
def _from_pb(cls, pb, set_key=True, ent=None, key=None):
"""
clone this entity so we can track if anything changes between puts
this is one way to know when an object loads from a datastore QUERY
_post_get_hook only gets called on direct Key.get()
none of the documented hooks are called after query results
SEE: https://code.google.com/p/appengine-ndb-experiment/issues/detail?id=211
"""
entity = super(BaseModel, cls)._from_pb(pb, set_key=set_key, ent=ent, key=key)
if entity.key and entity._original is None:
# _from_pb will get called if we unpickle a new object (like when passing through deferred library)
# so if we are being materialized from pb and we don't have a key, then we don't have _original
entity._original = clone(entity)
return entity
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.
In my Google App Engine app I have model objects that need to be stored. These objects are parameterized by various policy objects. For example, my Event class has a Privacy policy object which determines who can see, update, etc. There are various subclasses of PrivacyPolicy that behave differently. The Event consults its PrivacyPolicy object at various points.
class PrivacyPolicy(db.Model):
def can_see(self, event, user):
pass
class OwnerOnlyPolicy(PrivacyPolicy):
def can_see(self, event, user):
return user == event.owner
class GroupOnlyPolicy(PrivacyPolicy):
def can_see(self, event, user):
for grp in event.owner.groups()
if grp.is_member(user):
return True
return False
class OnlyCertainUsersPolicy(PrivacyPolicy):
def __init__(self, others):
self.others = others
def can_see(self, event, user):
return user in others
I could make my Event class use a ReferenceProperty to the PrivacyPolicy:
class Event(db.Model):
privacy: db.ReferenceProperty(PrivacyPolicy)
#…
The reason I don’t like this is that the one-to-one relationship means that nobody every queries for the policy object, there is no need to maintain the back-reference from the policy to its Event object, and in no other way is PrivacyPolicy an independent db-level object. It is functionally equivalent to an IntegerProperty, in that it is part of the Event object’s state, it’s just an object instead of a number — specifically it’s an object that can have zero state or lots of state, unknown to the Event type.
I can’t find anyone talking about how to approach such a situation. Is there a tool/approach I don’t know about? Do I just suck it up and use a reference property and the hell with the overhead?
If the only other way to handle this is a custom Property type, any advice about how to approach it would be welcome. My first thought is to use a TextProperty to store the string rep of the policy object (policy), decode it when needed, caching the result, and having any change to the policy object invalidate the cache and update the string rep.
You're overcomplicating by trying to store this in the datastore. This belongs in code rather than in the datastore.
The least complicated way would be:
class Event(db.Model):
privacy = db.IntegerProperty()
def can_see(self, user):
if self.privacy == PRIVACY_OWNER_ONLY:
return user == event.owner
else if self.privacy == PRIVACY_GROUP:
for grp in self.owner.groups()
if grp.is_member(user):
return True
return False
Sometimes all it takes is to think of the right approach. The solution is to introduce a new kind of property that uses pickle to store and retrieve values, such as that described in https://groups.google.com/forum/?fromgroups#!topic/google-appengine/bwMD0ZfRnJg
I wanted something slightly more sophisticated, because pickle isn’t always the answer, and anyway documentation is nice, so here is my ObjectReference type:
import pickle
from google.appengine.ext import db
class ObjectProperty(db.Property):
def __init__(self, object_type=None, verbose_name=None, to_store=pickle.dumps, from_store=pickle.loads, **kwds):
"""Initializes this Property with all the given options
All args are passed to the superclass. The ones used specifically by this class are described here. For
all other args, see base class method documentation for details.
Args:
object_type: If not None, all values assigned to the property must be either instances of this type or None
to_store: A function to use to convert a property value to a storable str representation. The default is
to use pickle.dumps()
from_store: A function to use to convert a storable str representation to a property value. The default is
to use pickle.loads()
"""
if object_type and not isinstance(object_type, type):
raise TypeError('object_type should be a type object')
kwds['indexed'] = False # It never makes sense to index pickled data
super(ObjectProperty, self).__init__(verbose_name, **kwds)
self.to_store = to_store
self.from_store = from_store
self.object_type = object_type
def get_value_for_datastore(self, model_instance):
"""Get value from property to send to datastore.
We retrieve the value of the attribute and return the result of invoking the to_store function on it
See base class method documentation for details.
"""
value = getattr(model_instance, self.name, None)
return self.to_store(value)
def make_value_from_datastore(self, rep):
"""Get value from datastore to assign to the property.
We take the value passed, convert it to str() and return the result of invoking the from_store function
on it. The Property class assigns this returned value to the property.
See base class method documentation for details.
"""
# It passes us a unicode, even though I returned a str, so this is required
rep = str(rep)
return self.from_store(rep)
def validate(self, value):
"""Validate reference.
Returns:
A valid value.
Raises:
BadValueError for the following reasons:
- Object not of correct type.
"""
value = super(ObjectProperty, self).validate(value)
if value is not None and not isinstance(value, self.object_type):
raise db.KindError('Property %s must be of type %s' % (self.name, self.object_type))
return value
I have my own User model in app engine, which should have a property of his gravatar url. However, since this can be very quickly computed using his email address, it doesn't make sense to store it. Is there a way to just automatically initialize this property when it s loaded from the datastore?
I could just add a method called get_avatar_url(), but you can't call an object's methods (as far as I know), from within a jinja2 template, and I don't want to post all these values individually to the template.
You can define a method, as you describe, or you can define a property, like this:
class MyModel(db.Model):
email = db.StringProperty(required=True)
#property
def avatar_url(self):
return "http://gravatar.com/avatar/%s" % (hashlib.md5(self.email).hexdigest(),)
You can then refer to this as instance.avatar_url (or in a template, {{instance.avatar_url}}).
Either will work fine in a jinja2 template, but using a property is slightly neater if you need to request it elsewhere. Since only datastore property instances result in storing data in the datastore, your property will not be stored in the datastore.
It's ok to call them from a template. All you need to do is to declare this model's method as classmethod or property
Here's a quick example:
# sample model
class UserProfile(db.Model):
...
email = db.EmailProperty()
...
#property
def id(self):
return self.key().id()
#classmethod
def get_avatar_url(self):
# whatever you need to call gravatar url
return self.email
# sample view
def show_user(user_id):
user = User.all().filter("user = ", user_id).get()
flowers = Flower.all().filter("user = ", user)
return render_template('index.html', u=user, f=flowers)
# sample template
<div class="user">user id: {{ u.id }}, and gravatar: {{ u.get_gravatar_url() }}<div>
HTH.
You most certainly can call methods within a template. That is the best way to do it.