CRUD operations with single records and Django models

Problem

You want to make CRUD operations for single database records that rely on Django models.

Solution

To create a single database record create a Django model instance an call the save() method on it. Alternatively you can also create a single database record in a single step calling the create() method with the model values as its arguments. To read a single database record through a Django model use the get() method or the convenience method get_or_create() to fetch or create a record if it doesn't exists yet. Be aware the get() and get_or_create() methods only work on single record queries, if a query returns multiple records o there are no results both methods generate an error.

To update a single database record you can change a pre-existing reference's fields using Python's dotted notation and then call the save() method on it. You can also add the update_fields[<list_of_fields_to_update>] argument to the save() method to selectively update certain fields. You can also use the update() method to perform an update in a single operation and avoid race conditions, as well the convenience method update_or_create() to update or create a record if it doesn't exists yet. To delete a single database record can either call the delete() method on a pre-existing reference or append the delete() method to a query.

How it works

Working with single records is one of the most common tasks you'll do with Django models. There are many options in the Django models API to work with single records. I'll structure the following recipe to illustrate the classical web application CRUD (Create-Read-Update-Delete) operations and describe the various techniques for each case so you can get a better grasp of what to use under different circumstances.

Create a single record with create() or save()

To create a single record that relies on a Django model you just need to make an instance of a model and invoke the save() method on it. Listing 1 illustrates the process to create a single record for the Store model.

Listing 1 - Create a single record with a Django model using the save() method

# Import Django model class
from coffeehouse.stores.models import Store

# Create a model Store instance
store_corporate = Store(name='Corporate',address='624 Broadway',state='CA',email='corporate@coffeehouse.com')
# Assign attribute value to instance with Python dotted notation
store_corporate.city = 'San Diego'
# Invoke the save() method to create the record
store_corporate.save()

As you can see in listing 1, you can declare all the instance attributes in a single step or you can use Python's dotted notation to assign attribute values one by one on the reference itself. Once the instance is ready, you just call the save() method on it and Django attempts to make it a record in the database.

There are the two very important behaviors you need to be aware of when you invoke save():

Although there are few more subtleties involved when you use the save() method, these last two points should be enough to get you started and keep you safe. Note that after a successful call to the save() method, the object reference will have an id attribute -- assigned by the database -- and be directly linked to a database record that can be updated and/or deleted.

The create() method offers a shorter route alternative to create a record. Listing 2 illustrates the equivalent record creation in listing 1 using the create() method.

Listing 2 - Create a single record with a Django model using the create() method

# Import Django model class
from coffeehouse.stores.models import Store

# Create a model Store instance which is saved automatically
store_corporate = Store.objects.create(name='Corporate',address='624 Broadway',city='San Diego',state='CA',email='corporate@coffeehouse.com')

As you can see in listing 2, after importing a Django model you can invoke the create() method using a Django model's attribute values as its arguments. The execution of create() returns an object reference to the created record just like the save() method, the only difference is with the create() method you declare and create the record in a single line.

Read a single record with get() or get_or_create()

To retrieve a single database record you can use the get() method that is part of every Django model and which accepts any model field to qualify a record. Listing 3 illustrates a basic example of the get() Django model method.

Listing 3 - Django model get() method to fetch database records

# Import Django model class
from coffeehouse.stores.models import Store

# Get the store with the name "Downtown" or equivalent SQL: 'SELECT....WHERE name = "Downtown"
downtown_store = Store.objects.get(name="Downtown")

# Define uptown_email for the query
uptown_email = "uptown@coffeehouse.com"
# Get the store with the email value uptown_email or equivalent SQL: 'SELECT....WHERE email = "uptown@coffeehouse.com"'
uptown_email_store = Store.objects.get(email=uptown_email)

# Once the get() method runs, you can access an object's attributes
# either in logging statements, functions or templates
downtown_store.address
downtown_store.email

# Note you can access the object without attributes.
# If the Django model has __str__/__unicode__ method definitions, the output is based on these methods
# If the Django model has no __str__/__unicode__ method definitions, the output is just <object>
uptown_email_store

As you can see in listing 3, after importing a Django model you can invoke the get() method using a Django model attribute as its argument to retrieve a specific record. The first example in listing 3 retrieves the Store record with the name Downtown, where as the second example retrieves the Store record with the email uptown@coffeehouse.com. Once the record or object model is assigned to a variable, you can access its contents or attributes using Python's dotted notation.

Note Finer grained single record queries

In addition to exact single record queries -- WHERE id=1 or WHERE email = "uptown@coffeehouse.com") -- Django also offers a wide range of options for finer grained single record queries. For example, matches for case insensitive strings, number ranges, lists and dates.

See the recipe Django model queries classified by SQL keywords for a classification of the different Django query options classified by SQL keyword.

It's as simple as that to use a Django model's get() method. However, the get() method has some behaviors that you should be aware of:

Knowing these get() limitations, let's explore how to tackle a common scenario that involves one of these behaviors. A common occurrence when attempting to read a single record, is to get it and if it doesn't exist just create it. Listing 4 illustrates how to use the get_or_create() method for this purpose.

Listing 4 - Django model get_or_create() method

# Import Django model class
from coffeehouse.stores.models import Drink

# Get or create a drink instance with name="Espresso"
drink_target = Drink.get_or_create(name="Espresso")

As you can see in listing 4, after you gain access to a Django model class you just invoke the get_or_create() method on the reference and Django will either fetch the record or create it and fetch it in a single step. The creation part of the get_or_create() method works just like the save() method described in the previous section, so if a new record violates a validation rule, the database rejects the attempt to create a new record.

Note get_or_create() behind the scenes or explicitly catching get() errors

Behind the scenes, the get_or_create() method relies on both the get() and save() methods. It really is a shortcut method to avoid writing more code. The following snippet shows what goes on behind the scenes which you can also do if you prefer:


from django.core.exceptions import ObjectDoesNotExist
from coffeehouse.stores.models import Drink

try: 
     drink_target = Drink.get(name="Espresso")
     # If get() throws an error you need to handle it. You can use either the generic ObjectDoesNotExist or <model>.DoesNotExist which inherits from django.core.exceptions.ObjectDoesNotExist, so you can target multiple DoesNotExist exceptions
except Drink.DoesNotExist: # or the generic "except ObjectDoesNotExist:"      
     drink_target = Drink(name="Espresso")
     drink_target.save()

As you can see in the previous snippet, there's just more code to write if you know there's a possibility a record might not exist and you want to create it anyways. So the get_or_create() method becomes helpful in this scenario.

If you're not sure a query will return a single record, the get() method is not adequate because it will throw a MultipleObjectsReturned error if more than one record is found. For such a scenario, you can attempt to limit the result to a single record by using a finer grained query. The recipe Django model queries classified by SQL keywords describes various options that can help you reduce a query to a single record.

If you're certain a query will return multiple records, then you'll need to use either the filter() or exclude() methods, both of which are described in detail in the next recipe CRUD operations with multiple records and Django models.

Update a single record with save(), update() or update_or_create()

If you already have a reference to a record, an update is as simple as updating attribute values using Python's dotted notation and invoking the save() method. Listing 5 illustrates this process.

Listing 5 - Django model update with the save() method

# Import Django model class
from coffeehouse.stores.models import Store

# Get the store with the name "Downtown" or equivalent SQL: 'SELECT....WHERE name = "Downtown"
downtown_store = Store.objects.get(name="Downtown")
# Update the name value
downtown_store.name = "Downtown (Madison)"
# Call save() with the update_fields arg and a list of record fields to update selectively
downtown_store.save(update_fields=['name'])
# Or you can call save() without any argument and all record fields are updated
downtown_store.save()

As you can see in listing 5, you can call the save() method in two ways. If you use the update_fields argument with a list of fields to update, Django only updates the specified fields, which can give you a performance boost if a model is large. The other alternative is to simply use save() without any argument, in which case Django updates all fields, which is a good alternative if a majority of record fields need to be updated.

In cases where you don't yet have a reference to a record you want to update, it can be slightly inefficient to first get it (i.e. issue a SELECT query) and then update it with the save() method. In addition, doing the entire update process in separate steps can lead to race conditions. For example, if another user fetches the same data at the same time and also does an update, you'll both race to save it, but who's update is definitive and whose is overwritten ? This is the difficulty in race conditions, because no party is aware the other is working on the same data, you need a way to indicate -- technically known as lock or isolate -- the data to avoid race conditions.

For such cases you can use the update() method, which allows you to make an update in a single operation and guarantees there are no race conditions. Listing 6 illustrates this process.

Listing 6 - Django model update with the update() method

from coffeehouse.stores.models import Store

Store.objects.filter(id=1).update(name="Downtown (Madison)")


from coffeehouse.stores.models import Drink
from django.db.models import F

Drink.objects.filter(id=3).update(stock=F('stock') +100)

The first example in listing 6 uses the update() method to update the Store record with id=1 and set its name to Downtown (Madison). The second example uses a Django F expression and the update() method to update the Drink record with id=3 and set its stock value to the current stock value plus 100. For the moment, don't worry to much about Django F expressions, just realize they allow you to reference model fields within a query, which is necessary in this case to perform the update in a single operation.

Note Inspect the query that precedes update() carefully

The update() method can update a field across multiple records if you're not careful. The update() method is preceded by the objects.filter() method which can return query results with multiple records. Notice in listing 6 the query uses the id field to define the query, ensuring that only a single record matches the query, because id is the table's primary key. If the query definition in objects.filter() uses a less strict look-up (e.g. a string) you can inadvertently update more records than you expect.

Similar to the convenience get_or_create() method described in the previous section, Django also offers the convenience update_or_create() method. This method is helpful in cases where you want to perform an update and aren't sure if the record exists yet. Listing 7 illustrates this process.

Listing 7 - Django model update_or_create() method

# Import Django model class
from coffeehouse.stores.models import Store

values_to_update = {'email':'downtown@coffeehouse.com'}

obj, created = Store.objects.update_or_create(
    name='Downtown',city='San Diego', defaults=updated_values)

As you can see in listing 7, the first thing that needs to be done is create a dictionary with field-values to update. Once this is done, you call update_or_create with a query for the desired object and pass the dictionary with updated field-values to defaults. The return values are the updated or created object & a boolean value to indicate if the object is newly created or not. For the case in listing 7, if there is already a Store record with name='Downtown' and city='San Diego' the record's values in values_to_update are updated, if there is no matching Store record a new Store record with name='Downtown', city='San Diego' and the values in values_to_update is created.

Note update_or_create only works on queries with a single record

The update_or_create() method is restricted to queries that return a single record, so there's no chance to update multiple records inadvertently. If by any chance there are multiple records that match the query in update_or_create you'll get the error MultipleObjectsReturned just like the get() method.

Delete a single record with delete()

If you already have a reference to a record, deleting it is as simple as invoking the delete() method on it. Listing 8 illustrates this process.

Listing 8 - Django model delete with the delete() method

# Import Django model class
from coffeehouse.stores.models import Store

# Get the store with the name "Downtown" or equivalent SQL: 'SELECT....WHERE name = "Downtown"
downtown_store = Store.objects.get(name="Downtown")
# Call delete() to delete the record in the database
downtown_store.delete()

In cases where you don't yet have a reference to a record you want to delete, it can be slightly inefficient to first get it (i.e. issue a SELECT query) and then delete it with the delete() method. For such cases you can use the delete() method and append it to a query so everything is done in a single operation. Listing 9 illustrates this process.

Listing 9 - Django model delete with the delete() method on query

from coffeehouse.stores.models import Store

Store.objects.filter(id=1).delete()

The example in listing 9 uses the delete() method to delete the Store record with id=1.

Note Inspect the query that precedes delete() carefully

The delete() method can delete multiple records if you're not careful. The delete() method is preceded by the objects.filter() method which can return query results with multiple records. Notice in listing 9 the query uses the id field to define the query, ensuring that only a single record matches the query, because id is the table's primary key. If the query definition in objects.filter() uses a less strict look-up (e.g. a string) you can inadvertently delete more records than you expect.