Query by computed property in python mongoengine - python

I wondered if it is possible to query documents in MongoDB by computed properties using mongoengine in python.
Currently, my model looks like this:
class SnapshotIndicatorKeyValue(db.Document):
meta = {"collection": "snapshot_indicator_key_values"}
snapshot_id = db.ObjectIdField(nullable=False)
indicator_key_id = db.ObjectIdField(nullable=False)
value = db.FloatField(nullable=False)
created_at = db.DateTimeField()
updated_at = db.DateTimeField()
#property
def snapshot(self):
return Snapshot.objects(id=self.snapshot_id).first()
def indicator_key(self):
return IndicatorKey.objects(id=self.indicator_key_id).first()
When I do for example SnapshotIndicatorKeyValue .objects().first().snapshot, I can access the snapshotproperty.
But when I try to query it, it doesn't work. For example:
SnapshotIndicatorKeyValue.objects(snapshot__date_time__lte=current_date_time)
I get the error `mongoengine.errors.InvalidQueryError: Cannot resolve field "snapshot"``
Is there any way to get this working with queries?
I need to query SnapshotIndicatorKeyValue based on a property of snapshot.

In order to query the snapshot property directly through mongoengine, you can reference the related snapshot object rather than the snapshot_id in your SnapshotIndicatorKeyValue document definition.
An amended model using a Reference field would be like this:
from mongoengine import Document, ReferenceField
class Snapshot(Document)
property_abc = RelevantPropertyHere() # anything you need
class SnapshotIndicatorKeyValue(Document):
snapshot = ReferenceField(Snapshot)
You would sucessively save an instance of Snapshot and an instance of SnapshotIndicatorKeyValue like this:
sample_snapshot = Snapshot(property_abc=relevant_value_here) # anything you need
sample_snapshot.save()
sample_indicatorkeyvalue = SnapshotIndicatorKeyValue()
sample_indicatorkeyvalue.snapshot = sample_snapshot
sample_indicatorkeyvalue.save()
You can then refer to any of the snapshot's properties through:
SnapshotIndicatorKeyValue.objects.first().snapshot.property_abc

Related

Message Object has no attribute 'fields' when updating model field across apps

I have two apps menu and table. In app table, I have this model:
class Table(models.Model):
available = models.BooleanField(verbose_name="Availability", default=True)
def set_availability(self, avail=False):
self.fields['available'] = avail
self.save()
def __str__(self):
return "Table " + str(self.id_num)
In one of the views of app menu, I have the following call:
from table.models import Table
def menu_category_view(request, table_pk):
table = Table.objects.get(pk=table_pk)
if table.available:
table.set_availability(False)
...
return render(request,
...)
When my template calls this view, I receive this error message 'Table' object has no attribute 'fields'. Here, I am trying to update the value of field available of the instance being called (from True to False). And I got this implementation suggested from a book. Is this the right way to update model instance field value? Thanks.
Just set the attribute.
def set_availability(self, avail=False):
self.available = avail
self.save()
Though, it's questionable whether or not set_<field> methods like this are particularly useful. You could work with the object almost as easily:
if table.available:
table.available = False
table.save()

Django REST API query on related field

I have 3 models, Run, RunParameter, RunValue:
class Run(models.Model):
start_time = models.DateTimeField(db_index=True)
end_time = models.DateTimeField()
class RunParameter(models.Model):
parameter = models.ForeignKey(Parameter, on_delete=models.CASCADE)
class RunValue(models.Model):
run = models.ForeignKey(Run, on_delete=models.CASCADE)
run_parameter = models.ForeignKey(RunParameter, on_delete=models.CASCADE)
value = models.FloatField(default=0)
class Meta:
unique_together=(('run','run_parameter'),)
A Run can have a RunValue, which is a float value with the value's name coming from RunParameter (which is basically a table containing names), for example:
A RunValue could be AverageTime, or MaximumTemperature
A Run could then have RunValue = RunParameter:AverageTime with value X.
Another Run instance could have RunValue = RunParameter:MaximumTemperature with value Y, etc.
I created an endpoint to query my API, but I only have the RunParameter ID (because of the way you can select which parameter you want to graph), not the RunValue ID directly. I basically show a list of all RunParameter and a list of all Run instances, because if I showed all instances of RunValue the list would be too long and confusing, as instead of seeing "Maximum Temperature" you would see:
"Maximum Temperature for Run X"
"Maximum Temperature for Run Y"
"Maximum Temperature for Run Z", etc. (repeat 50+ times).
My API view looks like this:
class RunValuesDetailAPIView(RetrieveAPIView):
queryset = RunValue.objects.all()
serializer_class = RunValuesDetailSerializer
permission_classes = [IsOwnerOrReadOnly]]
And the serializer for that looks like this:
class RunValuesDetailSerializer(ModelSerializer):
run = SerializerMethodField()
class Meta:
model = RunValue
fields = [
'id',
'run',
'run_parameter',
'value'
]
def get_run(self, obj):
return str(obj.run)
And the URL just in case it's relevant:
url(r'^run-values/(?P<pk>\d+)/$', RunValuesDetailAPIView.as_view(), name='values_list_detail'),
Since I'm new to REST API, so far I've only dealt with having the ID of the model API view I am querying directly, but never an ID of a related field. I'm not sure where to modify my queryset to pass it an ID to get the appropriate model instance from a related field.
At the point I make the API query, I have the Run instance ID and the RunParameter ID. I would need the queryset to be:
run_value = RunValue.objects.get(run=run_id, run_parameter_id=param_id)
While so far I've only ever had to do something like:
run_value = RunValue.objects.get(id=value_id) # I don't have this ID
If I understand correctly, you're trying to get an instance of RunValue with only the Run id and the RunParameter id, i.e. query based on related fields.
The queryset can be achieved with the following:
run_value = RunValue.objects.get(
run__id=run_id,
run_parameter__id=run_parameter_id
)
Providing that a RunValue instance only ever has 1 related Run and RunParameter, this will return the instance of RunValue you're after.
Let me know if that's not what you mean.
The double underscore allows you to access those related instance fields in your query.
Well its pretty simple, all you have to do is override the get_object method, for example(copy pasted from documentation):
# view
from django.shortcuts import get_object_or_404
class RunValuesDetailAPIView(RetrieveAPIView):
queryset = RunValue.objects.all()
serializer_class = RunValuesDetailSerializer
permission_classes = [IsOwnerOrReadOnly]]
lookup_fields = ["run_id", "run_parameter_id"]
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
filter = {}
for field in self.lookup_fields:
if self.kwargs[field]: # Ignore empty fields.
filter[field] = self.kwargs[field]
obj = get_object_or_404(queryset, **filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
# url
url(r'^run-values/(?P<run_id>\d+)/(?P<run_parameter_id>\d+)/$', RunValuesDetailAPIView.as_view(), name='values_list_detail'),
But one big thing you need to be careful, is not to have duplicate entries with same run_id and run_parameter_id, then it will throw errors. To avoid it, either use unique_together=['run', 'run_parameter'] or you can use queryset.filter(**filter).first() instead of get_object_or_404 in the view. But second option will produce wrong results when duplicate entries are created.

Update multiple fields on Google NDB entity

Working with Google App Engine for Python, I am trying to create and then update an ndb entity. To update a single property, you can just access the property using a dot, e.g.
post.body = body
But I would like to know if there is a simple way to update multiple fields within an ndb entity. The following code:
class Create(Handler):
def post(self):
## code to get params
post = Post(author = author,
title = title,
body = body)
post.put()
class Update(Handler):
def post(self, post_id):
post = post.get_by_id(int(post_id))
fields = ['author', 'title', 'body']
data = get_params(self.request, fields)
for field in fields:
post[field] = data[field]
post.put()
The "Create" handler works fine, but the "Update" handler results in:
TypeError: 'Post' object does not support item assignment
So it seems I would need to access the properties using a dot, but that is not going to work when I have a list of properties I want to access.
Can someone provide an alternative way to update multiple properties of an NDB entity after it has been created?
You should use setattr.
for field in fields:
setattr(post, field, data[field])
(Note that GAE objects do actually provide a hidden way of updating them via a dict, but you should use the public interface.)
You can use the populate method:
post.populate(**data)

Store a mongo_id on a MongoAlchemy Document?

Is it possible to store a mongo_id as an ObjectId object in a MongoAlchemy field? I've been able to store an ObjectId inside of a document I defined manually, but it seems as though I'm restricted to storing the string value of the id in the context of the MongoAlchemy ORM.
Here's some of my code:
class Group(db.Document):
name = db.StringField()
trial_id = db.StringField(required=False)
participants = db.ListField(
db.DictField(db.AnythingField()), default_empty=True, required=False)
def add_participant(self, participant):
self.participants.append({
'participant_id': participant.mongo_id,
'start': datetime.utcnow(),
})
class Trial(db.Document):
name = db.StringField()
groups = db.ListField(
db.DocumentField(Group), default_empty=True, required=False)
def add_group(self, group):
group.trial_id = str(self.mongo_id)
group.save()
def get_group(self, group):
return Group.query.filter(
{'name': group, 'trial_id': str(self.mongo_id)}).first()
You'll see that I'm able to store a mongo_id as an ObjectId object in the Group method add_participant (since it's creating document manually, not through the MongoAlchemy ORM), but am forced to convert the mongo_id to a string in order to store it in a db.StringField.
I tried storing the original ObjectId in a db.AnythingField, but was then unable to filter by it.
Does anyone know if it's possible to store an ObjectId in a MongoAlchemy field and then filter by it in a database query?
Thank you!
You want an ObjectIdField: http://www.mongoalchemy.org/api/schema/fields.html#mongoalchemy.fields.ObjectIdField
This is the type of field which is used for mongo_id (although that one is special-cased)
try
id = db.ObjectIdField().gen()
This would automatically generate the object id for each instance of the mongo db object/document - as would id's in relational dbs

Equivalent of objects.latest() in App Engine

What would be the best way to get the latest inserted object using AppEngine ?
I know in Django this can be done using
MyObject.objects.latest()
in AppEngine I'd like to be able to do this
class MyObject(db.Model):
time = db.DateTimeProperty(auto_now_add=True)
# Return latest entry from MyObject.
MyObject.all().latest()
Any idea ?
Your best bet will be to implement a latest() classmethod directly on MyObject and call it like
latest = MyObject.latest()
Anything else would require monkeypatching the built-in Query class.
Update
I thought I'd see how ugly it would be to implement this functionality. Here's a mixin class you can use if you really want to be able to call MyObject.all().latest():
class LatestMixin(object):
"""A mixin for db.Model objects that will add a `latest` method to the
`Query` object returned by cls.all(). Requires that the ORDER_FIELD
contain the name of the field by which to order the query to determine the
latest object."""
# What field do we order by?
ORDER_FIELD = None
#classmethod
def all(cls):
# Get the real query
q = super(LatestMixin, cls).all()
# Define our custom latest method
def latest():
if cls.ORDER_FIELD is None:
raise ValueError('ORDER_FIELD must be defined')
return q.order('-' + cls.ORDER_FIELD).get()
# Attach it to the query
q.latest = latest
return q
# How to use it
class Foo(LatestMixin, db.Model):
ORDER_FIELD = 'timestamp'
timestamp = db.DateTimeProperty(auto_now_add=True)
latest = Foo.all().latest()
MyObject.all() returns an instance of the Query class
Order the results by time:
MyObject.all().order('-time')
So, assuming there is at least one entry, you can get the most recent MyObject directly by:
MyObject.all().order('-time')[0]
or
MyObject.all().order('-time').fetch(limit=1)[0]

Categories