Django Models

It's nice to be able to get all of the records back, but what's even more useful and important is to be able to select individual records and then to order them.

For this, you can apply the "filter" method to your manager:


>>> for a in Appointment.objects.filter(meeting_with='VIP'):
        print a.starts_at

Now you know when your appointments with a VIP will be starting. But, what if you want to search for a range of things, such as all of the appointments since January 1st, 2015?

Django provides a number of special methods that perform such comparisons. For each field that you have defined in your model, Django defines __lt, __lte, __gt and __gte methods that you can use to filter query sets. For example, to find all of the appointments since January 1st, 2015, you can say:


>>> Appointment.objects.filter(starts_at__gte=datetime(2015,1,1))

As you can see, because you have a starts_at field name, Django accepts a starts_at__gte keyword, which is turned into the appropriate operator. If you pass more than one keyword, Django will combine them with AND in the underlying SQL.

QuerySets can be filtered in more sophisticated ways too. For example, you might want to compare a field with NULL. In that case, you cannot use the = operator in SQL, but rather, you must use the IS operator. Thus, you might want to use something like this:


>>> Appointment.objects.filter(notes__exact=None)

Notice that __exact knows to apply the appropriate comparison, based on whether it was given None (which is turned into SQL's NULL) or another value.

You can ask whether a field contains a string:


>>> Appointment.objects.filter(meeting_with__contains='VIP')

If you don't care about case sensitivity, you can use icontains instead:


>>> Appointment.objects.filter(meeting_with__icontains='VIP')

Don't make the mistake of adding % characters to the front and back of the string for which you're searching. Django will do that for you, turning the icontains filter parameter into an SQL ILIKE query.

You even can use slice notation on a QuerySet in order to get the effects of OFFSET and LIMIT. However, it's important to remember that in many databases, the uses of OFFSET and LIMIT can lead to performance issues.

Django, by default, defines an "id" field that represents a numeric primary key for each record stored. If you know the ID, you can search based on that, using the get method:


>>> Appointment.objects.get(pk=2)

If there is a record with this primary key, it'll be returned. If not, you'll get a DoesNotExist exception.

Finally, you also can sort the records that are returned using the order_by method. For example:


>>> Appointment.objects.filter
↪(starts_at__gte=datetime(2015,1,1)).order_by('id')

What if you want to reverse the ordering? Just preface the name of the column with a - sign:


>>> Appointment.objects.filter
↪(starts_at__gte=datetime(2015,1,1)).order_by('-id')

You can pass multiple arguments to order_by if you want to order (ascending or descending) by a combination of columns.

One nice feature of Django's QuerySets is that every call to filter or order_by returns a new QuerySet object. In this way, you can make your calls to filter all at once or incrementally. Moreover, you can create one QuerySet and then use that as the basis for further QuerySets, each of which will execute (when necessary) its query independently.

A big problem with creating dynamic queries is that of SQL injection—that users can, through the use of manipulation, force their own SQL to be executed, rather than what you intended. Using Django's QuerySets basically removes this threat, because it checks and appropriately quotes any parameters it receives before passing their values along to SQL. Really, there's no excuse nowadays for SQL injection to be a problem—please think twice (or three times) before trying to work around Django's safeguards.

Updating and Deleting

Updating the fields of a Django model is trivially easy. Modify one or more attributes, as you would with any other Python object and then save the updated object. Here, I load the first (unordered) record from the database before updating it:


>>> a = Appointment.objects.first()
>>> a.notes = 'blah blah'
>>> a.save()

Note that if you change the "id" attribute and then save your object, you'll end up creating a new record in the database! Of course, you shouldn't be changing the "id" of an object in any event, but now you can consider yourself warned as well.

To delete an object, just use the delete method on the instance. For example:


>>> len(Appointment.objects.all())
2

>>> a = Appointment.objects.first()
>>> a.delete()

>>> len(Appointment.objects.all())
>>> 1

As you can see, in the above example, I found that there is a total of two records in my database. I load the first and then delete it. Following that call—no need for saving or otherwise approving this action—you can see that the record is removed.

Conclusion

In my next article, I'll finish this series on Django with a discussion of the different types of relationships you can have across different models. I'll look at one-to-one, one-to-many and many-to-many relationships, and how Django lets you express and work with each of them.

Resources

The main site for Django is http://DjangoProject.com, and it has a great deal of excellent documentation, including a tutorial. Several pages are dedicated to QuerySets and how you can create and manipulate them.

Information about Python, in which Django is implemented, is at http://python.org.

______________________

Reuven M. Lerner, Linux Journal Senior Columnist, a longtime Web developer, consultant and trainer, is completing his PhD in learning sciences at Northwestern University.