I am creating a database for my school. There is a Student Document that has an EmbeddedDocumentListField of Adults. I am trying to update existing EmbeddedDocuments using the EmbeddedDocumentListField methods but both save() and update() give me errors. get() seems to work though.
class Adult(EmbeddedDocument):
relation = StringField() # Adult, Mother, Father, Grandmother...
...
notes = StringField()
class Student(Document):
afname = StringField()
alname = StringField()
...
adults = EmbeddedDocumentListField('Adult')
I am able to successfully use the get method
#app.route('/editadult/<aeriesid>/<adultoid>')
def editadult(aeriesid,adultoid):
editUser = User.objects.get(aeriesid=aeriesid)
editAdult = editUser.adults.get(oid=adultoid)
This returns the object named editAdult with the attributes as expected but not the methods. I now want to update the values in that Object. I am able to see the methods I want to call by doing.
dir(editUser.adults)
But can't see the methods with
dir(editAdult)
From my reading of the docs I should be able to do this.
editAdult.update(fname="Juanita", lname="Sanchez")
This gives the error: AttributeError: 'Adult' object has no attribute 'update'.
But can't figure out how to use the methods on that context.
I tried
editAdult.fname = "Juanita"
editAdult.lname = "Sanchez"
editAdult.save()
But that gives the error: AttributeError: 'Adult' object has no attribute 'save'
The documentation is sparse. Mongoengine tells me what the methods are but no examples.
http://docs.mongoengine.org/apireference.html#embedded-document-querying
And GitHub gives good examples but I couldn't get them to work.
https://github.com/MongoEngine/mongoengine/pull/826
I am using Mongoengine 0.20.0
After a lot of trial and error I figured this out. With this data structure:
class Adult(EmbeddedDocument):
relation = StringField() # Adult, Mother, Father, Grandmother...
fname = StringField()
lname = StringField()
...
notes = StringField()
class Student(Document):
afname = StringField()
alname = StringField()
...
adults = EmbeddedDocumentListField('Adult')
I then created this Flask Route
#app.route('/editadult/<aeriesid>/<adultoid>')
def editadult(aeriesid,adultoid):
editUser = User.objects.get(aeriesid=aeriesid)
editAdult = editUser.adults.get(oid=adultoid)
adultoid is the unique identifier for the adult and aeriesid is the unique identifier for the student so I know that both of these will retrieve exactly one record.
The part that I was missing is that while the editAdult Object contains exactly the values that I want, it is NOT an EmbeddedDocumentListObject so it does not contain the methods. So, that get() command above is the basic MongoEngine get() and NOT the get() method from the EmbeddedDocumentFieldList. (I show the EmbeddedDocumentListField get() below)
Here is what I was missing. This is how you use the update() method in the EmbeddedDocumentListField.
editUser.adults.filter(oid=adultoid).update(
relation = form.relation.data,
fname = form.fname.data,
lname = form.lname.data,
...
notes = form.notes.data
)
I am not sure if this update command would update all of the filtered records. In my case, it is only possible that one record is returned because I am filtering on a uniqueid. Next, it turns out the EmbeddedDocumentListField() update() does NOT save like it does in the base update() method so you then have to do this. This fact is actually well documented in the MongoEngine docs.
http://docs.mongoengine.org/apireference.html?highlight=embedded%20documents#embedded-document-querying
editUser.adults.filter(oid=adultoid).save()
Finally, there is another way to do the original get() command:
editAdult2 = editUser.adults.filter(oid=adultoid).get()
For the sake of completeness, here is the Flask route to delete an EmbeddedDocument record
#app.route('/deleteadult/<aeriesid>/<adultoid>')
def deleteadult(aeriesid,adultoid):
editUser = User.objects.get(aeriesid=aeriesid)
editAdult = editUser.adults.get(oid=adultoid)
editUser.adults.filter(oid=adultoid).delete()
editUser.adults.filter(oid=adultoid).save()
I hope this is helpful to others. The process to learn this was crazy hard and mostly just trial and error. I considering going back to SQLAlchemy. :(
Related
I have the following marshmallow schemas and mongoengine classes defined:
class DataReport(Document):
id = ObjectIdField(required=True)
firmware_statistics_report_reference_id = LazyReferenceField(FirmwareReport, required=True)
...
class DataReportSchema(Schema):
id = fields.Str()
firmware_statistics_report_reference_id = fields.Nested(FirmwareReportSchema)
...
class FirmwareReport(Document):
id = ObjectIdField(required=True)
firmware_id_list = ListField(required=True))
...
class FirmwareReportSchema(Schema):
id = fields.Str()
firmware_id_list = fields.List(fields.Str())
...
report = DataReport().objects.get(pk=...)
DataReportSchema().dump(report)
Is it possible to dump the nested schema with the LazyReferenceField from mongoengine? What I want is to get the all fields defined in the child schema, but I did not find a way to get the full schema data when it is referenced with the LazyReferenceField.
If I dump the DataReportSchema I will only get the referenced Object-ID and not the fields from the FirmwareReportSchema, which makes sense since it is a LazyReference until I call mongoengines fetch() function. I assume there must be a way to fetch the LazyReference before it is dumped, but I do not understand how.
Is it possible to fetch() the LazyReference before the marshmallow dump() in order to get the full dump from the child schema and not only an Object-ID?
Ah I found the solution myself. Instead of using the fields.Nested method it is possible to use the fields.Method method. Then it is possible to fetch() the id within a custom method:
class DataReportSchema(Schema):
id = fields.Str()
report_date = fields.DateTime()
firmware_statistics_report_reference_id = fields.Method("get_firmware_schema")
def get_firmware_schema(self, data_statistics_report):
firmware_report = data_statistics_report.firmware_statistics_report_reference_id.fetch()
return FirmwareStatisticsReportSchema().dump(firmware_report)
class PurchaseOrder(models.Model):
purchase_order_id = models.AutoField(primary_key=True)
purchase_order_number = models.CharField(unique=True)
vendor = models.ForeignKey(Vendor)
i am creating Purchase Order(po) table. when po created i have to update purchase_order_number as "PO0"+purchase_order_id ex PO0123 (123 is Primary key). so i am using def save in models to accomplish this
def save(self):
if self.purchase_order_id is not None:
self.purchase_order_number = "PO"+str(self.purchase_order_id)
return super(PurchaseOrder, self).save()
It is working fine with single creation but when i try to create bulk of data using locust(Testing tool) its giving an error duplicate entry for PurchseOrdernumber Can we modify field value in models itself some thing like this
purchase_order_number = models.CharField(unique=True,default=("PO"+self.purchase_order_id )
To be honest, I don't think it should work when you create multiple instances. Because as I can see from the code:
if self.purchase_order_id is not None:
self.purchase_order_number = "PO"+str(self.purchase_order_id)
Here purchase_order_id will be None when you are creating new instance. Also, until you call super(PurchaseOrder, self).save(), it will not generate purchase_order_id, meaning purchase_order_number will be empty.
So, what I would recommend is to not store this information in DB. Its basically the same as purchase_order_id with PO in front of it. Instead you can use a property method to get the same value. Like this:
class PurchaseOrder(models.Model):
purchase_order_id = models.AutoField(primary_key=True)
# need to remove `purchase_order_number = models.CharField(unique=True)`
...
#property
def purchase_order_number(self):
return "PO{}".format(self.purchase_order_id)
So, you can also see the purchase_order_number like this:
p = PurchaseOrder.objects.first()
p.purchase_order_number
Downside of this solution is that, you can't make any query on the property field. But I don't think it would be necessary anyway, because you can do the same query for the purchase_order_id, ie PurchaseOrder.objects.filter(purchase_order_id=1).
Let's say I have this:
class Parent(models.Model):
id = models.IntegerField(primary_key=True)
children = ListField(EmbeddedModelField('Child'))
class Child(models.Model):
id = models.IntegerField(primary_key=True)
In the mongo interactive shell, finding Parent's with a particular Child is as easy as:
db.myapp_parent.find({'children.id': 123})
How is this done in django-nonrel?
I tried a few things including I looked for raw queries but raw_results is not a method in Parent.objects for some reason.
FWIW, this is what I have in my requirements.txt:
git+https://github.com/django-nonrel/django#nonrel-1.3
git+https://github.com/django-nonrel/djangotoolbox#toolbox-1.3
git+https://github.com/django-nonrel/mongodb-engine#mongodb-engine-1.3
I think I found the answer myself:
https://groups.google.com/forum/#!topic/django-non-relational/kCLOcI7nHS0
Basically, looks like this is not supported yet.
So the workaround is raw queries.
In order to make raw queries the code in the question should be modified to:
from django_mongodb_engine.contrib import MongoDBManager
class Parent(models.Model):
id = models.IntegerField(primary_key=True)
children = ListField(EmbeddedModelField('Child'))
objects = MongoDBManager()
class Child(models.Model):
id = models.IntegerField(primary_key=True)
Then
Parent.objects.raw_query({'children.id': 123})
works.
Looked around for a while and suddenly the following mentioned there worked like magic for me, that appears to avoid the need for raw queries (adapted to your example):
from django_mongodb_engine.query import A
...
Parent.objects.filter( children = A('id', '123') )
As for requirements:
git+https://github.com/django-nonrel/django#nonrel-1.5
git+https://github.com/django-nonrel/djangotoolbox#toolbox-1.8
git+https://github.com/django-nonrel/mongodb-engine
#(django-mongodb-engine==0.6.0)
#(pymongo==3.2)
I'm trying to figure out how to design my model. I've been going over the documentation, and it ultimately seems like I should be using the "through" attribute, but I just can't figure out how to get it to work how I want.
If someone could take a look and point out what I'm missing, that would be really helpful. I have pasted my model below.
This is what I am trying to do:
1) Have a list of server types
2) Each server type will need to have different parts available to that specific server type
3) The asset has a FK to the servermodel, which has a M2M to the parts specific to that server type.
My question is, how can each "Asset" store meta data for each "Part" specific to that "Asset"? For example, each "Asset" should have it's own last_used data for the part that's assigned to it.
Thanks! :)
class Part(models.Model):
part_description = models.CharField(max_length=30,unique=1)
last_used = models.CharField(max_length=30)
def __unicode__(self):
return self.part_description
class ServerModel(models.Model):
server_model = models.CharField(max_length=30,unique=1)
parts = models.ManyToManyField(Part)
def __unicode__(self):
return self.server_model
class Asset(models.Model):
server_model = models.ForeignKey(ServerModel)
serial_number = models.CharField(max_length=10,unique=1)
def __unicode__(self):
return self.server_model.server_model
EDIT:
Thank you for the help!
I may have not explained myself clearly, though. It's probably my confusing model names.
Example:
ServerModel stores the type of server being used, say "Dell Server 2000".
The "Dell Server 2000" should be assigned specific parts:
"RAM"
"HARD DISK"
"CDROM"
Then, I should be able to create 10x Assets with a FK to the ServerModel. Now, each of these assets should be able to mark when the "RAM" part was last used for this specific asset.
I'm not sure I exactly understand what you want to do, but basically you can solve that with a "through" model, as you expected:
import datetime
class Part(models.Model):
name = models.CharField(max_length=30,unique=1)
class ServerModel(models.Model):
server_model = models.CharField(max_length=30,unique=1)
parts = models.ManyToManyField(Part,through='Asset')
class Asset(models.Model):
server_model = models.ForeignKey(ServerModel)
part = models.ForeignKey(Part)
serial_number = models.CharField(max_length=10,unique=1)
used = models.DateTimeField(default=datetime.datetime.now())
First thing to notice is the relation of the parts to the servermodel using the "through"-model: that way for each Part instance assigned to the "parts"-property of a ServerModel instance a new Asset instance is created (Phew - hope that doesn't sound too complicated). At the time of creation the "used"-property of the Asset instance is set to the current date and time (thats what default=datetime.datetime.now() does).
If you do that, you can then just query the database for the last asset containing your part. That queryset can then be sorted by the "used" property of the Asset model, which is the date when the Asset instance has been created.
ServerModel.objects.filter(parts__name='ThePartYouAreLookingFor').order_by('asset__used')
I'm not absolutely sure if the queryset is correct, so if someone finds huge nonsense in it, feel free to edit ;)
edit:
The models above do not exactly that. But you do not even need a through model for what you want:
class ServerModel(models.Model):
server_model = models.CharField(max_length=30,unique=1)
parts = models.ManyToManyField(Part)
class Asset(models.Model):
server_model = models.ForeignKey(ServerModel)
parts = models.ForeignKey(Part)
serial_number = models.CharField(max_length=10,unique=1)
used = models.DateTimeField(default=datetime.datetime.now())
Basically you can just add assets and then query all assets that have a RAM in parts.
Asset.objects.filter(parts__contains='RAM').order_by('used')
Get the date of the first (or last) result of that queryset and you have the date of the last usage of your 'RAM'-part.
I have models as follows:
class Place(models.Model):
name = models.CharField(max_length=300)
class Person(models.Model):
name = models.CharField(max_length=300)
class Manor(models.Model):
place = models.ManyToManyField(Place, related_name="place"))
lord = models.ManyToManyField(Person, related_name="lord")
overlord = models.ManyToManyField(Person, related_name="overlord")
I want to get all the Places attached with the relation 'lord' to a particular person, and then get the centre, using a GeoDjango method. This is as far as I've got:
person = get_object_or_404(Person, namesidx=namesidx)
manors = Manor.objects.filter(lord=person)
places = []
for manor in manors:
place_queryset = manor.place.all()
for place in place_queryset:
places.append(place)
if places.collect():
centre = places.collect().centroid
However, this gives me:
AttributeError at /name/208460/gamal-of-shottle/
'list' object has no attribute 'collect'
Can I either (a) do this in a more elegant way to get a QuerySet of places back directly, or (b) construct a QuerySet rather than a list in my view?
Thanks for your help!
The way you're doing this, places is a standard list, not a QuerySet, and collect is a method that only exists on GeoDjango QuerySets.
You should be able to do the whole query in one go by following the relations with the double-underscore syntax:
places = Place.objects.filter(manor__lord=person)
Note that your use of related_name="place" on the Manor.place field is very confusing - this is what sets the reverse attribute from Place back to Manor, so it should be called manors.