• No results found

Database Interaction via Managers

In document django-unleashed.pdf (Page 79-94)

Code Repository

3.6.2 Database Interaction via Managers

Django attaches an object called a manager to every model class. The manager helps interaction with the database in complicated ways: it is a key part of Django’s ORM (which is hinted at by thedbin the path below) and is the most common way Django developers will interact with data in the database. Effectively, it is one of the most powerful tools inherited throughmodels.Modeland one with which we will be very familiar by the end of the book.

By default, Django automatically generates a manager for each model and assigns it to theobjectsattribute. As shown in Example 3.45, we can use the Pythontype()built-in to see what the object is.

Example 3.45:Python Interpreter Code

>>> type(Tag.objects)

django.db.models.manager.Manager

Much like models, managers can create and delete objects, but rather than taking two steps (instantiation and thensave()), the manager uses a single command, as shown in Example 3.46.

Example 3.46:Python Interpreter Code

>>> Tag.objects.create(name='Video Games', slug='video-games') <Tag: Video Games>

3.6 Manipulating Data in the Database: Managers and QuerySets 59

But why stop there? As you can see in Example 3.47, the manager can create multiple objects in a single command!

Example 3.47:Python Interpreter Code

>>> Tag.objects.bulk_create([

... Tag(name='Django', slug='django'),

... Tag(name='Mobile', slug='mobile'),

... Tag(name='Web', slug='web'),

... ])

[<Tag: Django>, <Tag: Mobile>, <Tag: Web>]

Unlike a model, however, a manager can get data from the database, as shown in Example 3.48. Any call toobjectswill provide the most updated version of the data.

Example 3.48:Python Interpreter Code

>>> Tag.objects.all() [<Tag: Django>,

<Tag: Mobile>, <Tag: Video Games>, <Tag: Web>]

The return value from a manager looks like a list, acts like a list, but is not a list. It is a queryset object, as shown in Example 3.49.

Example 3.49:Python Interpreter Code

>>> Tag.objects.all()[0] <Tag: Django>

>>> type(Tag.objects.all()) django.db.models.query.QuerySet

Managers are attached to the model class but not to model instances. If you try to access objectson an object, Django will error, as shown in Example 3.50.

Example 3.50:Python Interpreter Code

>>> try:

... edut.objects

... except AttributeError as e:

... print(e)

60 Chapter 3 Programming Django Models and Creating a SQLite Database

There are many methods available on managers and querysets, and we simply don’t have the time to see them all.The Django documentation5supplies all the information you’ll need.Instead, we focus on the tools we will use most often: the tools to get items from the database.

We’ve already seen one: theall()manager method displays all of the objects of that model. I also use thecount()method in Example 3.51 as a quick demonstration.

Example 3.51:Python Interpreter Code

>>> Tag.objects.all() [<Tag: Django>,

<Tag: Mobile>, <Tag: Video Games>, <Tag: Web>]

>>> Tag.objects.count() 4

To be more selective about which object we want, we can use theget()method, shown in Example 3.52, which expects keyword arguments in which each key is a field in the model.

Example 3.52:Python Interpreter Code

>>> Tag.objects.get(slug='django') <Tag: Django>

Theget()method, shown in Example 3.53, is one of the few manager methods that does not return a queryset. Instead, it just returns the model object.

Example 3.53:Python Interpreter Code

>>> type(Tag.objects.all()) django.db.models.query.QuerySet

>>> type(Tag.objects.get(slug='django')) organizer.models.Tag

The values passed toget()are case sensitive by default, as shown in Example 3.54. Example 3.54:Python Interpreter Code

>>> try:

... Tag.objects.get(slug='Django')

... except Tag.DoesNotExist as e:

... print(e)

Tag matching query does not exist.

3.6 Manipulating Data in the Database: Managers and QuerySets 61

However, managers and querysets come with a feature called lookups. A field name followed by two underscores and then a lookup will allow modification of the behavior of the method. For instance, to make theget()method case insensitive, we can use the iexactlookup, shown in Example 3.55.

Example 3.55:Python Interpreter Code

>>> Tag.objects.get(slug--iexact='DJANGO') <Tag: Django>

The behavior in Example 3.55 is exactly how we want to handleslugfields. Slugs are unique case-insensitive identifiers. The ability to browse for “django” and “Django” and get the same data is crucial when handling URLs, and Django supplies the ability to act appropriately out of the box.

Django comes with a large number of lookups and even allows you to create your own (starting in Django 1.7). Once again, we won’t be able to see each and every one, as that is thereference documentation’s job. However, Example 3.56 shows two more just to whet your appetite.

Example 3.56:Python Interpreter Code

>>> Tag.objects.get(slug--istartswith='DJ') <Tag: Django>

>>> Tag.objects.get(slug--contains='an') <Tag: Django>

You’ll note that all of the examples withget()are crafted to return only a single object. This is by design because theget()method will only ever return a single object. If you query it with information that matches more than one object, as shown in Example 3.57, Django will throw an exception.

Example 3.57:Python Interpreter Code

>>> try:

# djangO, mObile, videO-games

... Tag.objects.get(slug--contains='o')

... except Tag.MultipleObjectsReturned as e:

... print(e)

get() returned more than one Tag -- it returned 3!

Due to theget()method’s behavior, the other commonly used method is thefilter() method (Example 3.58), which does exactly what it sounds like. Like theall()method, it returns a queryset.

62 Chapter 3 Programming Django Models and Creating a SQLite Database

Example 3.58:Python Interpreter Code

>>> Tag.objects.filter(slug--contains='o') [<Tag: Django>, <Tag: Mobile>]

>>> type(Tag.objects.filter(slug--contains='o')) django.db.models.query.QuerySet

Most manager methods return a queryset object, just as most queryset methods return queryset objects. We can chain calls to querysets, allowing us to filter and control the results to get exactly what we’re looking for. In Example 3.59, we first filter the results and then order them.

Example 3.59:Python Interpreter Code

>>> Tag.objects.filter(slug--contains='o').order_by('-name') [<Tag: Mobile>, <Tag: Django>]

The advantage to chaining these calls is that the database will be called only once. The topic of reducing the number of database calls really belongs inChapter 26, but it is worth noting here that managers and querysets are both lazy: they try to wait until they must display or return data before they actually ask the database for that data. This delay can be quite powerful but also confusing if you don’t expect that behavior.

The methods found on querysets are overloaded on the manager, meaning that if we can call it on the queryset we can call it on the manager. (The opposite is usually also true, but not always). For instance, we can callorder byon theTagmanager, as in Example 3.60.

Example 3.60:Python Interpreter Code

>>> # on a manager

>>> Tag.objects.order_by('-name') [<Tag: Web>,

<Tag: Video Games>, <Tag: Mobile>, <Tag: Django>]

>>> # on a queryset

>>> Tag.objects.filter(slug--contains='e').order_by('-name') [<Tag: Web>, <Tag: Video Games>, <Tag: Mobile>]

By default, Django orders objects in querysets according to their primary key. However, as we defined an ordering in theMetasubclass ofTag, ourTagobjects have been listed alphabetically. The query in Example 3.60—reverse alphabetical order—is thus the opposite of the default we set earlier in this chapter.

Finally, on top ofget()andfilter(), there are thevalues()andvalues list()

methods. The two are fairly similar, so I only demonstrate the second one. In Example 3.61, rather than returning a queryset with model objects, Django returns a queryset with tuples!

3.6 Manipulating Data in the Database: Managers and QuerySets 63

Example 3.61:Python Interpreter Code

>>> Tag.objects.values_list() [(3, 'Django', 'django'),

(4, 'Mobile', 'mobile'),

(2, 'Video Games', 'video-games'), (5, 'Web', 'web')]

>>> type(Tag.objects.values_list()) django.db.models.query.ValuesListQuerySet

The neat thing aboutvalues()andvalues list()is the ability to quickly select which model fields we want to get, as shown in Example 3.62.

Example 3.62:Python Interpreter Code

>>> Tag.objects.values_list('name', 'slug') [('Django', 'django'),

('Mobile', 'mobile'),

('Video Games', 'video-games'), ('Web', 'web')]

More often than not, however, we’ll be using the methods to get a single value for a list. As Example 3.63 shows, this leads to a queryset of singletons (single-item tuples), which is far from great for unpacking.

Example 3.63:Python Interpreter Code

>>> Tag.objects.values_list('name') [('Django',),

('Mobile',), ('Video Games',), ('Web',)]

Luckily, as Example 3.64 shows, the method accepts theflatkeyword, which when set toTrue(with a single field passed) will return a queryset of strings.

Example 3.64:Python Interpreter Code

>>> Tag.objects.values_list('name', flat=True) ['Django', 'Mobile', 'Video Games', 'Web']

Just to be clear: this is still a queryset, not a list (Example 3.65)! Example 3.65:Python Interpreter Code

>>> type(Tag.objects.values_list('name', flat=True)) django.db.models.query.ValuesListQuerySet

64 Chapter 3 Programming Django Models and Creating a SQLite Database

3.6.2.1 Data in Memory vs Data in the Database

As discussed in Section3.3, a key concept when building models is that the Python class will map directly to a database table. Each field attribute in the class becomes a database column, and every object instantiated and saved becomes a row. For instance, the call to the Startupmanager, in Example 3.66, creates a database row for Startup data (Table3.2).

Example 3.66:Python Interpreter Code

>>> jb = Startup.objects.create(

... name='JamBon Software',

... slug='jambon-software',

... contact='[email protected]',

... description='Web and Mobile Consulting.\n\n'

... 'Django Tutoring.\n',

... founded_date=date(2013, 1, 18),

... website='https://jambonsw.com/',

... )

>>> jb

<Startup: JamBon Software>

Please note that the primary key (PK) I have selected in Table3.2is arbitrary and will be automatically assigned by your database. Also, thefounded datefield expects an instance ofdateand will error if given anything else.

The nuance here is that the database will save data only when it is told to. We saw this with the educational tag earlier, but it bears repeating because it is a common beginner mistake. For instance, let’s take thedatefield on our Startup (Example 3.67).

Example 3.67:Python Interpreter Code

>>> jb.founded_date datetime.date(2013, 1, 18)

If we reassign it to a different date, as in Example 3.68, it will register correctly in Python, as expected.

Example 3.68:Python Interpreter Code

>>> jb.founded_date = date(2014,1,1)

>>> jb.founded_date datetime.date(2014, 1, 1)

Table 3.2:Database Table for Startup Data

PK Name Description Founded EMail Website

3.6 Manipulating Data in the Database: Managers and QuerySets 65

However, if we do not runsave()on thejbobject, then the database will never know of this change. We can see this in Example 3.69 when we retrieve the data from the database.

Example 3.69:Python Interpreter Code

>>> jb = Startup.objects.get(slug='jambon-software')

>>> jb.founded_date datetime.date(2013, 1, 18)

Always remember to save your data!

3.6.2.2 Connecting Data through Relations

When talking about the ORM, we’ve focused entirely on simple fields. But many of our models have relation fields with many-to-one and many-to-many relationships.

OurPostobject is connected to both tags and startups. We can easily create aPost(as in Example 3.70), but we cannot assign relations usingcreate()on the model manager.

Example 3.70:Python Interpreter Code

>>> djt = Post.objects.create(

... title='Django Training',

... slug='django-training',

... text=(

... "Learn Django in a classroom setting "

... "with JamBon Software."), )

>>> djt

<Post: Django Training on 2015-06-09>

The date is automatically assigned at creation because ofauto now add. We can override it as in Example 3.71.

Example 3.71:Python Interpreter Code

>>> djt.pub_date = date(2013, 1, 18)

>>> djt.save()

>>> djt

<Post: Django Training on 2013-01-18>

Model managers are not the only managers available in Django. When dealing with relations, Django uses a manager to connect data. For example, thetagsfield in ourPost model is actually a manager for many-to-many relationships and comes with many of the same functions as a model manager. We can verify this by first creating aPostand then using the Pythontype()built-in, as shown in Example 3.72.

66 Chapter 3 Programming Django Models and Creating a SQLite Database

Example 3.72:Python Interpreter Code

>>> type(djt.tags) django.db.models.fields.related.create_many_related_manager.<locals> .ManyRelatedManager >>> type(djt.startups) django.db.models.fields.related.create_many_related_manager.<locals> .ManyRelatedManager

Just as we can model managers, we can call theall()method to see all of the objects the manager can access. The calls in Example 3.73 show all of the tags associated with the post, followed by all of the startups associated with the post (none in both cases).

Example 3.73:Python Interpreter Code

>>> djt.tags.all() []

>>> djt.startups.all() []

Relation managers come with anadd()method, which allows for objects to be connected. In Example 3.74, we connect the Django tag to our new blog post.

Example 3.74:Python Interpreter Code

>>> django = Tag.objects.get(slug--contains='django')

>>> djt.tags.add(django)

>>> djt.tags.all() [<Tag: Django>]

We started to learn about how many-to-many relationships are structured in Django in Section3.4.3but only skimmed the surface. Let’s take a moment to review that material and add to it our newfound knowledge of managers.

Consider the code in Example 3.75. Example 3.75:Python Code

Student(models.Model):

classes = models.ManyToManyField( SchoolClass)

Given objectstimmyandphysics, for example, instances ofStudentand

SchoolClassrespectively, the classes being taken bytimmyaretimmy.classes.all(),

and the students takingphysicsarephysics.student set.all(). The attributes

timmy.classesandphysics.student setare relational managers, and both return QuerySetobjects. Even though a many-to-many relationship is symmetric (if someone is

3.6 Manipulating Data in the Database: Managers and QuerySets 67

your friend, you are his or her friend, too), many talk about the forward and backward relation of the relationship because the relationship is defined in only one place. In this example,timmy.classesis the forward relation (because the field is defined on Student), whereasphysics.student setis the reverse relation because Django automatically sets it for us.

When we set therelated nameattribute of a relationship field, as in Example 3.76, we are changing the name of the variable Django creates for the relation on the other model (the reverse relation).

Example 3.76:Python Code Student(models.Model):

classes = models.ManyToManyField( SchoolClass,

related_name='students')

With the code in Example 3.76, instead ofphysics.student set.all(), we access

the related manager of students withphysics.students.all(). The call to

timmy.classes.all()remains unchanged.

I must stress: despite some of the vocabulary around many-to-many relations, there is no real “forward” or “reverse” relation. The relationship is symmetric, and the vocabulary exists only as a means of talking about where the field is defined in Django.

Let’s return to our site code. In Example 3.77, we can use that to call the reverse relation between the Django tag and the blog post we just connected.

Example 3.77:Python Interpreter Code

>>> django.blog_posts.all()

[<Post: Django Training on 2013-01-18>]

Had we not overridden the name of the manager toblog postsin ourPostmodel,

the call in Example 3.77 would instead have been topost set. We can see this with the Startup<->Tagmany-to-many relationship. With aStartupobject, we can use the variabletagsto get the related manager. With aTagobject, as in Example 3.78, we instead usestartup setfor the same relation.

Example 3.78:Python Interpreter Code

>>> django.startup_set.add(jb) # a "reverse" relation >>> django.startup_set.all()

[<Startup: JamBon Software>]

>>> jb.tags.all() # the "forward" relation

68 Chapter 3 Programming Django Models and Creating a SQLite Database

Let’s see it one more time, just to be clear. In Example 3.79, we take our new blog post and use theManyToManyFielddefined in thePostmodel to access the related manager and add the JamBon Software startup.

Example 3.79:Python Interpreter Code

>>> djt

<Post: Django Training on 2013-01-18>

>>> djt.startups.add(jb)

>>> djt.startups.all() [<Startup: JamBon Software>]

We can then use the reverse related manager on theStartupobject to see that the JamBon Software startup and blog post are now connected. Because we defined the related nameoption on thestartups ManyToManyFielddefined in thePost model, we access the related manager throughblog postsinstead of throughpost set in Example 3.80.

Example 3.80:Python Interpreter Code

>>> jb.blog_posts.all()

[<Post: Django Training on 2013-01-18>]

Take a step back for a second. We’ve covered a lot of ground, and not all of this is going to sink in right away. The good news is that it doesn’t have to: this is material that will keep coming back throughout the book because it is crucial to using Django.

3.7

String Case Ordering

Note that when we created our data in Section3.8, all of the strings for each field met the same capitalization criteria. As Example 3.81 shows, when we createdTagdata, the first letter of thenamefield of the tag was always capitalized, while theslugfield was always all lowercase, as discussed in the beginning of the chapter.

Example 3.81:Python Interpreter Code

>>> Tag.objects.values_list('name','slug').order_by('name') [('Django', 'django'),

('Education', 'education'), ('Mobile', 'mobile'),

('Video Games', 'video-games'), ('Web', 'web')]

3.7 String Case Ordering 69

It is important to pick a case and stick to it for all values because the case of a string may affect the order of the values. For instance, if we create a newTagobject with the name andrew in all lowercase, most developers would assume that it would appear as the first object in the queryset when ordering by thenamefield. However, as you can see in Example 3.82, you would be wrong: it is possible, depending on your environment, for the newTagto appear last.

Example 3.82:Python Interpreter Code

>>> Tag.objects.create(name='andrew', slug='ZEBRA') <Tag: andrew>

>>> Tag.objects.values_list('name','slug').order_by('name') [('Django', 'django'), ('Education', 'education'),

('Mobile', 'mobile'), ('Video Games', 'video-games'), ('Web', 'web'), ('andrew', 'ZEBRA')]

Similarly, an uppercase slug will change the ordering, as you can see in Example 3.83. Given theslugfield ZEBRA, developers might expect the object to be last in the list and be surprised to discover otherwise.

Example 3.83:Python Interpreter Code

>>> Tag.objects.values_list('name','slug').order_by('slug') [('andrew', 'ZEBRA'), ('Django', 'django'),

('Education', 'education'), ('Mobile', 'mobile'), ('Video Games', 'video-games'), ('Web', 'web')]

As you may have gathered, uppercase letters appear before lowercase letters in the environment used above. This is part of the reason thatslugvalues are typically all lowercase, which ensures correct ordering.

Therefore, a project developer must make a conscious decision about the case of strings in the project database. In light of this information, we may choose to make the names of all tags lowercase. We can make this change very easily because of Django’s ORM, as

demonstrated in Example 3.84. Example 3.84:Python Interpreter Code

>>> for tag in Tag.objects.all():

In document django-unleashed.pdf (Page 79-94)