How to filter on ComputedProperty in query_method? - endpoints-proto-datastore

How does one query for computed properties?
class MyModel(EndpointsModel):
attr1 = ndb.IntegerProperty(default=0)
#EndpointsComputedProperty(property_type=messages.BooleanField)
def attr2(self):
return self.attr1 % 2 == 1
#endpoints.api(name='myapi', version='v1', description='My Little API')
class MyApi(remote.Service):
#MyModel.query_method(query_fields=('attr2'),
path='mymodels', name='mymodel.list')
def MyModelList(self, query):
return query
In this case query will always have a filter that will test for attr2 == False.
The cause seems to be that the filters are created from an entity created with FromMessage. As attr2 is a computed property, it cannot be set. As attr1 defaults to 0, attr2 is always False regardless of what is passed in.

You can query for computed properties by setting up a placeholder alias property similar to how inequality queries can be achieved, as described here: https://stackoverflow.com/a/17288934/4139124
More specifically, we set up an alias property with a different name from our existing attr1 and attr2 fields. We also define a setter that gets called whenever the alias property is assigned to, and use this setter to modify the query. Note that the setter must appear above the alias property:
import logging
from google.appengine.ext import ndb
from endpoints_proto_datastore.ndb import EndpointsModel
from endpoints_proto_datastore.ndb.properties import EndpointsAliasProperty
from endpoints_proto_datastore.ndb.properties import EndpointsComputedProperty
from protorpc import messages
class MyModel(EndpointsModel):
attr1 = ndb.IntegerProperty(default=0)
#EndpointsComputedProperty(property_type=messages.BooleanField)
def attr2(self):
return self.attr1 % 2 == 1
def Attr2AliasSetter(self, value):
self._endpoints_query_info._filters.add(MyModel.attr2 == value)
#EndpointsAliasProperty(name='attr2_alias',
property_type=messages.BooleanField,
setter=Attr2AliasSetter)
def Attr2Alias(self):
logging.error('attr2_alias should never be accessed')
We then update the query_method to accept the attr2_alias field rather than attr2:
#endpoints.api(name='myapi', version='v1', description='My Little API')
class MyApi(remote.Service):
#MyModel.query_method(query_fields=('attr2_alias'),
path='mymodels', name='mymodel.list')
def MyModelList(self, query):
return query
Then, for example, querying for myapi/mymodels?attr2_alias=true will return all MyModel entities with attr2 set to true.

Related

Serializer field for side effect model django rest framework

I have a django.db.models.Model A whose instances are created in a rest_framework.serializers.ModelSerializer from POST requests.
Depending on the data being sent in the POST, I would like to create one of several other "addon" models, let's say B or C, which I link to the original through a django.db.models.OneToOneField:
from django.db import models
class A(models.Model):
some_field = models.CharField()
class B(models.Model):
a = models.OneToOneField(A, related_name='addon', on_delete=models.CASCADE)
class C(models.Model):
a = models.OneToOneField(A, related_name='addon', on_delete=models.CASCADE)
What I would like to is to have a serializer which validates the incoming data, including some string indicating which addon to use. The serializer then creates the model instance of A and based on this creates the addon model.
I do not want to create a utility field in model A used to determine which addon to use, I would like to create the model directly using the instance of model A and information from the POST itself.
At the same time when accessing the data through a get, I would like to return the original string used to determine which addon to use.
What I have come up with so far:
from rest_framework import serializers
str2model = {'b': B, 'c': C}
class AddonField(serializers.Field):
def to_representation(self, value):
# I completely ignore "value" as no "internal value" is set in "to_internal_value"
myvalue = self.parent.instance.addon
for addon_name, addon_class in str2model.items():
if isinstance(myvalue, addon_class):
return addon_name
def to_internal_value(self, data):
# I create the "internal value" after "A" instance is created, thus here I do nothing?
return data
class ASerializer(serializers.ModelSerializer):
some_field = serializers.CharField()
the_addon = AddonField()
def validate_the_addon(self, value): # here addon is a string
if value in str2model.keys():
return value
def create(self, validated_data):
addon_name = validated_data.pop('the_addon')
addon_class = str2model[addon]
a = super(ASerializer, self).create(validated_data)
addon_class.objects.create(a=a)
return a
class Meta:
model = A
fields = ["some_field", "the_addon"]
When testing this I get:
AttributeError: Got AttributeError when attempting to get a value for field `the_addon` on serializer `ASerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `A` instance.
Original exception text was: 'A' object has no attribute 'the_addon'.
How can I temporarily store the_addon in the serializer until the A instance has been created?
This is how I would typically approach it
# Serializer
class ASerializer(serializers.Serializer):
some_field = serializers.CharField()
addon_b = serializers.CharField(required=False, allow_null=True)
addon_c = serializers.CharField(required=False, allow_null=True)
def create(self, validated_data):
addon_b = validated_data.pop('addon_b')
addon_c = validated_data.pop('addon_c')
a = A.objects.create(some_field=validated_data['some_field'])
if addon_b:
B.objects.create(a=a)
if addon_c:
C.objects.create(a=a)
return a
You can do other validations if necessary.
class TestAPIView01(generics.CreateAPIView):
permission_classes = {}
serializer_class = serializers.ASerializer
queryset = A.objects.all()
Also, look at the related_name on B and C you may want to consider making them different, as that might throw an error in the future. Cheers

How to migrate newly added python class property in ndb model?

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.

Endpoints Proto Datastore - Inequality filter on query_method

When using the endpoints-proto-datastore query_method on an EndpointsModel, is it possible to have an inequality filter passed as a query field?
For instance, say I have a model with a lastmodified field, and I want to query for all the records that have a lastmodified > date, where date is a value passed to the API by the client.
class MyModel(EndpointsModel):
attr1 = ndb.StringProperty()
lastmodified = ndb.DateTimeProperty()
From the documentation I have seen, it seems like the query_fields is strictly an equality filter.
Yes, but you'll need to use an EndpointsAliasProperty with a name different than the lastmodified field. For example, you could do something like was done in the Picturesque app used as a sample at Google I/O 2013.
First, define your model
from google.appengine.ext import ndb
from endpoints_proto_datastore.ndb import EndpointsModel
class MyModel(EndpointsModel):
attr1 = ndb.StringProperty()
lastmodified = ndb.DateTimeProperty()
then create an alias property which will be used to update the query
from google.appengine.ext import endpoints
from endpoints_proto_datastore.ndb import EndpointsAliasProperty
...
#EndpointsAliasProperty(name='modifiedSince', setter=ModifiedSinceSet)
def ModifiedSince(self):
raise endpoints.BadRequestException(
'modifiedSince value should never be accessed.')
This property won't ever be accessed, but its setter is relevant to make the query sort on what we want, so we need to define ModifiedSinceSet (it needs to come before the variable is reference, as in the sample):
import datetime
from endpoints_proto_datastore import utils
...
def ModifiedSinceSet(self, value):
try:
modified_since = utils.DatetimeValueFromString(value)
if not isinstance(modified_since, datetime.datetime):
raise TypeError('Not a datetime stamp.')
except TypeError:
raise endpoints.BadRequestException('Invalid timestamp for modifiedSince.')
self._endpoints_query_info._filters.add(
MyModel.lastmodified >= modified_since)
Here we convert the passed in value (from the request) to a datetime.datetime object and then use it to add a filter to self._endpoints_query_info._filters on the current EndpointsModel object.
This _endpoints_query_info is of course used in query_method to create the query that gets passed to your method.
Another alternative:
You could just parse the value and use introduce your own syntax. For example, accept strings like lastmodified>=TIMESTAMP and then parse the timestamp from the statement.

How do I handle objects that are part of a Model object’s state, but don’t need separate db-level support?

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

Django-nonrel in Google App Engine ListField

I am trying to build an example app in Google App Engine using django-nonrel. and am having problems implementing ListField attribute into a model.
I have created an app test_model and have included it as an installed app in my settings. The model.py is:
from django.db import models
from djangotoolbox import *
from dbindexer import *
# Create your models here.
class Example(models.Model):
some_choices = models.ListField('Choice_examples')
notes = models.CharField(max_length='20')
updated_at = models.DateTimeField(auto_now=True)
def __unicode__(self):
return u'%s' % (self.notes)
class Choice_examples(models.Model):
name = models.CharField(max_length='30')
def __unicode__(self):
return u'%s' % (self.name)
The above example gives me:
AttributeError:'module' object has no attribute 'Model'
If I comment out the djangotoolbox import, I get the following :
AttributeError: 'module' object has no attribute 'ListField'
What am I doing wrong here? I can't seem to find any documention as to how to go about using ListField in django-nonrel. Is that because it is supposed to really obvious?
Your imports are smashing each other:
from django.db import models
from djangotoolbox import *
The second import will replace the django.db models with djangotoolbox' empty models module. Using from X import * is a terrible idea in general in Python and produces confusing results like these.
If you're looking to use ListField from djangotoolbox, use:
from djangotoolbox import fields
and refer to the ListField class as fields.ListField.
OK, here is what I did to be able to use ListFields. MyClass the equivalent to your Example class and AnotherClass is the same as your Choice_examples. What I describe will allow you to use ListFields in the admin interface and your self implemented views.
I'll start from the beginning
This is what what my model looks like
class MyClass(models.Model):
field = ListField(models.ForeignKey(AnotherClass))
I wanted to be able to use the admin interface to create/edit instances of this model using a multiple select widget for the list field. Therefore, I created some custom classes as follows
class ModelListField(ListField):
def formfield(self, **kwargs):
return FormListField(**kwargs)
class ListFieldWidget(SelectMultiple):
pass
class FormListField(MultipleChoiceField):
"""
This is a custom form field that can display a ModelListField as a Multiple Select GUI element.
"""
widget = ListFieldWidget
def clean(self, value):
#TODO: clean your data in whatever way is correct in your case and return cleaned data instead of just the value
return value
These classes allow the listfield to be used in the admin. Then I created a form to use in the admin site
class MyClassForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyClasstForm,self).__init__(*args, **kwargs)
self.fields['field'].widget.choices = [(i.pk, i) for i in AnotherClass.objects.all()]
if self.instance.pk:
self.fields['field'].initial = self.instance.field
class Meta:
model = MyClass
After having done this I created a admin model and registered it with the admin site
class MyClassAdmin(admin.ModelAdmin):
form = MyClassForm
def __init__(self, model, admin_site):
super(MyClassAdmin,self).__init__(model, admin_site)
admin.site.register(MyClass, MyClassAdmin)
This is now working in my code. Keep in mind that this approach might not at all be well suited for google_appengine as I am not very adept at how it works and it might create inefficient queries an such.
I don't know, but try with:
class Choice_examples(models.Model):
name = models.CharField(max_length='30')
def __unicode__(self):
return u'%s' % (self.name)
class Example(models.Model):
some_choices = models.ListField(Choice_examples)
notes = models.CharField(max_length='20')
updated_at = models.DateTimeField(auto_now=True)
def __unicode__(self):
return u'%s' % (self.notes)
Looks like the answer is that you cannot pass an object into fields.ListField.
I have ditched trying to work with ListField as documentation is limited and my coding skills aren't at a level for me to work it out.
Anyone else coming across a similar problem, you should consider create a new model to map the ManyToMany relationships. And if the admin view is important, you should look into the following to display the ManyToMany table inline with any given admin view:
http://docs.djangoproject.com/en/1.2/ref/contrib/admin/#s-working-with-many-to-many-models

Resources