Effective Django ORM§

Configuring the Database§

Writing Models§

from django.db import models

class Address(models.Model):

    address = models.CharField(max_length=255, blank=True)
    city = models.CharField(max_length=150, blank=True)
    state = models.CharField(max_length=2, blank=True)
    zip = models.CharField(max_length=15, blank=True)

class Contact(models.Model):

    first_name = models.CharField(max_length=255, blank=True)
    last_name = models.CharField(max_length=255, blank=True)

    birthdate = models.DateField(auto_now_add=True)
    phone = models.CharField(max_length=25, blank=True)
    email = models.EMailField(blank=True)

    address = models.ForeignKey(Address, null=True)

Working with Models§

nathan = Contact()
nathan.first_name = 'Nathan'
nathan.last_name = 'Yergler'
nathan.save()

What Goes in Models§

Saving Data§

Managers§

Contact.objects.filter(last_name__iexact='yergler')
Contact.objects.filter(address__state='OH')

Custom Managers§

class ContactManager(models.Manager):

    def with_email(self):
        return self.filter(email__ne = '')

class Contact(models.Model):
    ...

    objects = ContactManager()
contacts.objects.with_email().filter(email__endswith='osu.edu')

Low-level Managers§

Testing§

What to Test§

Writing a Test§

def test_with_email():

    # make a couple Contacts
    Contact.objects.create(first_name='Nathan')
    Contact.objects.create(email='')

    self.assertEqual(
        len(Contact.objects.with_email()), 1
    )

Test Objects§

FactoryBoy Example§

import factory
from models import Contact

class ContactFactory(factory.Factory):
    FACTORY_FOR = Contact

    first_name = 'John'
    last_name = 'Doe'

# Returns a Contact instance that's not saved
contact = ContactFactory.build()
contact = ContactFactory.build(last_name='Yergler')

# Returns a saved Contact instance
contact = ContactFactory.create()

Querying Your Data§

Contact.objects.filter(state='OH').filter(email__ne='')

OR conditions in Queries§

If you need to do "or" conditions, you can use Q objects

from django.db.models import Q

Contact.objects.filter(
    Q(state='OH') | Q(email__endswith='osu.edu')
)

ORM Performance§

Instantiation is Expensive§

for user in Users.objects.filter(is_active=True):
    send_email(user.email)

Avoiding Instantiation§

user_emails = Users.objects.\
    filter(is_active=True).\
    values_list('email', flat=True)

for email in user_emails:
    send_email(email)

Traversing Relationships§

Contact.objects.\
    select_related('address').\
    filter(last_name = 'Yergler')

Query Performance§

Falling Back to Raw SQL§

Contact.objects.raw('SELECT * FROM contacts WHERE last_name = %s', [lname])

Other Manager Operations§

Managers have some additional helpers for operating on the table or collection:

Read Repeatable§

MySQL's default transaction isolation for InnoDB breaks Django's get_or_create when running at scale

def get_or_create(self, **kwargs):

    try:
        return self.get(**lookup), False
    except self.model.DoesNotExist:
        try:
            obj = self.model(**params)
            obj.save(force_insert=True, using=self.db)
            return obj, True
        except IntegrityError, e:
            try:
                return self.get(**lookup), False
            except self.model.DoesNotExist:
                raise e