Skip to content

Latest commit

 

History

History
261 lines (180 loc) · 7.46 KB

embedded-objects.rst

File metadata and controls

261 lines (180 loc) · 7.46 KB

Embedded Objects

django-mongodb-engine supports MongoDB's embedded objects.

Simple Embedding

Using ~djangotoolbox-fields.EmbeddedModelField, you can embed any Django model in other models:

from djangotoobox.fields import EmbeddedModelField

class ModelA(models.Model):
   b = EmbeddedModelField(ModelB) # or 'B' (in quotes)

With ~djangotoolbox-fields.EmbeddedModelField, which was originally written for django-mongodb-engine and then merged into djangotoolbox, you can embed any Django model into another model. Example:

from django.db import models
from djangotoolbox import fields

class Customer(models.Model):
    name = models.CharField()
    address = fields.EmbeddedModelField('Address')

class Address(models.Model):
    city = models.CharField()
    street = models.CharField()

Similar to relation definitions, the model passed to ~djangotoolbox-fields.EmbeddedModelField can either be the class name (Address) or a string containing the class name ('Address').

Let's add a customer to the database. :

Customer.objects.create(
    name='John',
    address=Address(city='London', street='Some street')
)

The resulting data record can be inspected with the MongoDB console:

/* db.docs_customer.findOne() */
{
    "_id" : ObjectId("4ce..."),
    "name" : "John",
    "address" : {
        "city" : "London",
        "street" : "Some street",
    }
}

As you see, the address is stored as a nested JSON/BSON object.

Embedded Objects in a List

It's cool to use Embedded Objects in a list instead of relations:

from djangotoobox.fields import EmbeddedModelField, ListField

class ModelA(models.Model):
   b = ListField(EmbeddedModelField(ModelB))

If used in a list or dict or similar, Embedded Objects can be a good or even better alternative to relations.

Imagine you're a music store and you have those models:

from djangotoolbox.fields import EmbeddedModelField, ListField

class Musician(models.Model):
    name = models.CharField()
    albums = ListField(EmbeddedModelField('Album'))

class Album(models.Model):
    title = models.CharField()
    year = models.IntegerField()
    tracks = ListField(EmbeddedModelField('Track'))

class Track(models.Model):
    title = models.CharField()
    length = models.IntegerField()

Now let's add a data record :

dimmu= Musician.objects.create(name='Dimmu Borgir')
in_sorte_diaboli = Album(
    title='In Sorte Diaboli', year=2007,
    tracks=[Track(title='The Serpentine Offering', length=309),
            Track(title='The Chosen Legacy', length=257),
            Track(title='The Conspiracy Unfolds', length=324)]
)
dimmu.albums.append(in_sorte_diaboli)
dimmu.save()

and have a look at what BSON was generated by django-mongodb-engine:

/* > db.docs_musician.findOne() */
{
    "_id" : ObjectId("4cee2549e4721c3b2c000001"),
    "name" : "Dimmu Borgir",
    "albums" : [
        {
            "title" : "In Sorte Diaboli",
            "year" : 2007,
            "tracks" : [
                {
                    "length" : 309,
                    "title" : "The Serpentine Offering"
                },
                {
                    "length" : 257,
                    "title" : "The Chosen Legacy"
                },
                {
                    "length" : 324,
                    "title" : "The Conspiracy Unfolds"
                }
            ]
        }
    ]
}

Neat, isn't it?

Keep in Mind: auto_now_add behaves like auto_now

because there's no difference between an insert and update in MongoDB.

Keep in Mind: No Reverse-Relations

The model structure used above makes queries down the data tree easy, e.g. get me all albums by musician xy or get me all albums whose title starts with an 'a'. You could even emulate queries like get me all tracks of musician xy:

sum((album.tracks for album in Musician.objects.get(name=xy).album), [])

However, queries that would normally require reverse-JOINs are not possible. For example, get me all albums from 2005 with the related musicians simply is impossible on non-relational databases as such a query would require JOINs. Emulation of such queries is possible but very expensive because you have to crawl through the whole collection:

# VERY expensive and slow query. Don't do this!
result = []
for musician in Musician.objects.all():
    musician.albums = filter(lambda album:album.year == 2005, musician.albums)
    if musician.albums:
        result.append(musician)

What's the point?

Consider your queries when designing your models.

Querying and Updating Embedded Objects

Querying

objects.filter(field=A('subfield', 'value'))

translates to the Mongo query

.find( {'field.subfield' : 'value'} )

django-mongodb-engine has full support for queries and updates that reach into subobjects using the ~django_mongodb_engine.query.A query helper.

For example, find any customer whose habitat is London:

>>> from django_mongodb_engine.query import A
>>> Customer.objects.create(name='Bob', address=Address(city='NY'))
>>> Customer.objects.create(name='Ann', address=Address(city='London'))
>>> Customer.objects.filter(address=A('city', 'London'))
[<Customer 'Ann'>]

If you enable query debugging <query-debugging>, you can see what this query translates to:

DEBUG [...] .find {'address.city': 'London'} [...]

If you need to match multiple filters on the same embedded field, chain ~django.db.query.QuerySet.filter calls:

Customer.objects.filter(address=A('city', 'London')) \
                .filter(address=A('street', 'Some street'))

This translates to:

.find( {'address.city': 'London', 'address.street': 'Some street'} )

Updating

objects.filter(foo='bar').update(field=A('subfield', 'new-value'))

translates to the Mongo query

.update( {foo: 'bar'}, {'$set' : {'field.subfield' : 'new-value'}} )

Updating works similar to this, e.g. John moved to Houston:

Customer.objects.filter(name='John').update(addresss=A('city', 'Houston'))

Translates to:

.update( {'name': 'John'}, {'$set': {'address.city': 'Houston'}} )

Of course, ~django_mongodb_engine.query.A can be used for both filtering and updating at the same time; for example, for moving all customers from New York to Canasas:

Customer.objects.filter(address=A('city', 'NY')) \
                .update(address=A('city', 'Cansas'))

Which translates to:

.update( {'address.city': 'NY'}, {'$set': {'address.city': 'Cansas'}} )