Skip to content

Files

Latest commit

2c86cf7 · Feb 6, 2017

History

History
470 lines (352 loc) · 37.2 KB

chapter5-database-setup.md

File metadata and controls

470 lines (352 loc) · 37.2 KB

Models and Databases {#chapter-models-databases}

When you think of databases, you will usually think of the Structured Query Language (SQL), the common means with which we query the database for the data we require. With Django, querying an underlying database - which can store all sorts of data, such as your website's user details - is taken care of by the Object Relational Mapper (ORM). In essence, data stored within a database table can be encapsulated within a model. A model is a Python object that describes your database table's data. Instead of directly working on the database via SQL, you only need to manipulate the corresponding Python model object.

This chapter walks you through the basics of data management with Django and its ORM. You'll find it's incredibly easy to add, modify and delete data within your app's underlying database, and how straightforward it is to get data from the database to the Web browsers of your users.

Rango's Requirements {#section-models-databases-requirements}

Before we get started, let's go over the data requirements for the Rango app that we are developing. Full requirements for the application are provided in detail earlier on, but to refresh your memory, let's quickly summarise our client's requirements.

  • Rango is essentially a web page directory - a site containing links to other websites.
  • There are a number of different webpage categories with each category housing a number of links. We assumed in the overview chapter that this is a one-to-many relationship. Check out the Entity Relationship diagram below.
  • A category has a name, a number of visits, and a number of likes.
  • A page refers to a category, has a title, URL and a number of views.

{id="fig-rango-erd-repeat"} The Entity Relationship Diagram of Rango's two main entities.

Telling Django about Your Database {#section-models-database-telling}

Before we can create any models, we need to set up our database with Django. In Django 1.9, a DATABASES variable is automatically created in your settings.py module when you set up a new project. It'll look similar to the following example.

{lang="python",linenos=off} DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }

We can pretty much leave this as is for our Rango app. You can see a default database that is powered by a lightweight database engine, SQLite (see the ENGINE option). The NAME entry for this database is the path to the database file, which is by default db.sqlite3 in the root of your Django project.

T> ### Git Top Tip T> If you are using Git, you might be tempted to add and commit the database file. This is not a good idea because if you are working on your app with other people, they are likely to change the database and this will cause endless conflicts. T> T> Instead, add db.sqlite3 to your .gitignore file so that it won't be added when you git commit and git push. You can also do this for other files like *.pyc and machine specific files.

I> ### Using other Database Engines I> The Django database framework has been created to cater for a variety of different database backends, such as PostgresSQL, MySQL and Microsoft's SQL Server. For other database engines, other keys like USER, PASSWORD, HOST and PORT exist for you to configure the database with Django. I> I> While we don't cover how to use other database engines in this book, there are guides online which show you how to do this. A good starting point is the official Django documentation. I> I> Note that SQLite is sufficient for demonstrating the functionality of the Django ORM. When you find your app has become viral and has accumulated thousands of users, you may want to consider switching the database backend to something more robust.

Creating Models

With your database configured in settings.py, let's create the two initial data models for the Rango application. Models for a Django app are stored in the respective models.py module. This means that for Rango, models are stored within rango/models.py.

For the models themselves, we will create two classes - one class representing each model. Both must inherit from the Model base class, django.db.models.Model. The two Python classes will be the definitions for models representing categories and pages. Define the Category and Page model as follows.

{lang="python",linenos=off} class Category(models.Model): name = models.CharField(max_length=128, unique=True)

    def __str__(self):  # For Python 2, use __unicode__ too
        return self.name


class Page(models.Model):
    category = models.ForeignKey(Category)
    title = models.CharField(max_length=128)
    url = models.URLField()
    views = models.IntegerField(default=0)

    def __str__(self):  # For Python 2, use __unicode__ too
        return self.title

T> ### Check import Statements T> At the top of the models.py module, you should see from django.db import models. If you don't see it, add it in.

I> ### __str__() or __unicode__()? I> The __str__() and __unicode__() methods in Python generate a string representation of the class (similar to the toString() method in Java). I> In Python 2.x, strings are represented in ASCII format in the __str__() method. If you want Unicode support, then you need to also implement the __unicode__() method. I> I> In Python 3.x, strings are Unicode by default - so you only need to implement the __str__() method.

When you define a model, you need to specify the list of fields and their associated types, along with any required or optional parameters. By default, all models have an auto-increment integer field called id which is automatically assigned and acts a primary key.

Django provides a comprehensive series of built-in field types. Some of the most commonly used are detailed below.

  • CharField, a field for storing character data (e.g. strings). Specify max_length to provide a maximum number o characters the field can store.
  • URLField, much like a CharField, but designed for storing resource URLs. You may also specify a max_length parameter.
  • IntegerField, which stores integers.
  • DateField, which stores a Python datetime.date object.

I> ### Other Field Types I> Check out the Django documentation on model fields for a full listing of the Django field types you can use, along with details on the required and optional parameters that each has.

For each field, you can specify the unique attribute. If set to True, the given field's value must be unique throughout the underlying database table that is mapped to the associated model. For example, take a look at our Category model defined above. The field name has been set to unique, meaning that every category name must be unique. This means that you can use the field like a primary key.

You can also specify additional attributes for each field, such as stating a default value with the syntax default='value', and whether the value for a field can be blank (or NULL) (null=True) or not (null=False).

Django provides three types of fields for forging relationships between models in your database. These are:

From our model examples above, the field category in model Page is of type ForeignKey. This allows us to create a one-to-many relationship with model/table Category, which is specified as an argument to the field's constructor.

Finally, it is good practice to implement the __str__() and/or __unicode__() methods. Without this method implemented when you go to print the object, it will show as <Category: Category object>. This isn't very useful when debugging or accessing the object - instead the code above will print, for example, <Category: Python> for the Python category. It is also helpful when we go to use the Admin Interface because Django will display the string representation of the object.

Creating and Migrating the Database

With our models defined in models.py, we can now let Django work its magic and create the tables in the underlying database. Django provides what is called a migration tool to help us set up and update the database to reflect any changes to your models. For example, if you were to add a new field then you can use the migration tools to update the database.

Setting up

First of all, the database must be initialised. This means creating the database and all the associated tables so that data can then be stored within it. To do this, you must open a terminal or command prompt, and navigate to your project's root directory - where manage.py is stored. Run the following command, bearing in mind that the output may vary from what you see below.

{lang="text",linenos=off} $ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, contenttypes, auth, sessions
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying sessions.0001_initial... OK

All apps that are installed in your Django project (check INSTALLED_APPS in settings.py) will update their database representations with this command. After this command is issued, you should then see a db.sqlite3 file in your Django project's root.

Next, create a superuser to manage the database. Run the following command.

{lang="text",linenos=off} $ python manage.py createsuperuser

The superuser account will be used to access the Django admin interface, used later on in this chapter. Enter a username for the account, e-mail address and provide a password when prompted. Once completed, the script should finish successfully. Make sure you take a note of the username and password for your superuser account.

Creating and Updating Models/Tables

Whenever you make changes to your app's models, you need to register the changes via the makemigrations command in manage.py. Specifying the rango app as our target, we then issue the following command from our Django project's root directory.

{lang="text",linenos=off} $ python manage.py makemigrations rango

Migrations for 'rango':
  0001_initial.py:
    - Create model Category
    - Create model Page

Upon the completion of this command, check the rango/migrations directory to see that a Python script has been created. It's called 0001_initial.py, which contains all the necessary details to create your database schema for that particular migration.

I> ### Checking the Underlying SQL I> If you want to check out the underlying SQL that the Django ORM issues to the database engine for a given migration, you can issue the following command. I> I> {lang="text",linenos=off} I> $ python manage.py sqlmigrate rango 0001 I> I> In this example, rango is the name of your app, and 0001 is the migration you wish to view the SQL code for. Doing this allows you to get a better understanding of what exactly is going on at the database layer, such as what tables are created. You may find for complex database schemas including a many-to-many relationship that additional tables are created for you.

After you have created migrations for your app, you need to commit them to the database. Do so by once again issuing the migrate command.

{lang="text",linenos=off} $ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, rango, contenttypes, auth, sessions
Running migrations:
  Rendering model states... DONE
  Applying rango.0001_initial... OK

This output confirms that the database tables have been created in your database, and you are good to go.

However, you may have noticed that our Category model is currently lacking some fields that were specified in Rango's requirements. Don't worry about this, as these will be added in later, allowing you to go through the migration process again.

Django Models and the Shell

Before we turn our attention to demonstrating the Django admin interface, it's worth noting that you can interact with Django models directly from the Django shell - a very useful tool for debugging purposes. We'll demonstrate how to create a Category instance using this method.

To access the shell, we need to call manage.py from within your Django project's root directory once more. Run the following command.

$ python manage.py shell

This will start an instance of the Python interpreter and load in your project's settings for you. You can then interact with the models, with the following terminal session demonstrating this functionality. Check out the inline commentary that we added to see what each command achieves. Note there are slight differences between what Django 1.9 and Django 1.10 return -- these are both demonstrated below, complete with commentary.

{lang="python",linenos=off} # Import the Category model from the Rango application >>> from rango.models import Category

# Show all the current categories
>>> print(Category.objects.all())
# The output examples below are for both Django 1.9 and Django 1.10.
# Both denote the same thing, that no categories have been defined.
[]  # Django 1.9 output -- an empty list.
<QuerySet []>  # Django 1.10 output -- an empty QuerySet object.

# Create a new category object, and save it to the database.
>>> c = Category(name="Test")
>>> c.save()

# Now list all the category objects stored once more.
>>> print(Category.objects.all())
# The output examples below are for both Django 1.9 and Django 1.10.
# You'll now see a 'test' category in both output examples.
[<Category: test>]  # Django 1.9
<QuerySet [<Category: test>]  # Django 1.10

# Quit the Django shell.
>>> quit()

In the example, we first import the model that we want to manipulate. We then print out all the existing categories. As our underlying Category table is empty, an empty list is returned. Then we create and save a Category, before printing out all the categories again. This second print then shows the new Category just added. Note the name, Test appears in the second print - this is your __str__() or __unicode__() method at work!

X> ### Complete the Official Tutorial X> The example above is only a very basic taster on database related activities you can perform in the Django shell. If you have not done so already, it's now a good time to complete part two of the official Django Tutorial to learn more about interacting with models. Also check out the official Django documentation on the list of available commands for working with models.

Configuring the Admin Interface

One of the standout features of Django is the built-in, Web-based administrative (or admin) interface that allows you to browse, edit and delete data represented as model instances (from the corresponding database tables). In this section, we'll be setting the admin interface up so you can see the two Rango models you have created so far.

Setting everything up is relatively straightforward. In your project's settings.py module, you will notice that one of the preinstalled apps (within the INSTALLED_APPS list) is django.contrib.admin. Furthermore, there is a urlpattern that matches admin/ within your project's urls.py module.

By default, things are pretty much ready to go. Start the Django development server in the usual way with the following command.

{lang="text",linenos=off} $ python manage.py runserver

Navigate your Web browser to http://127.0.0.1:8000/admin/. You are then presented with a login prompt. Login using the credentials you created previously with the $ python manage.py createsuperuser command. You are then presented with an interface looking similar to the one shown below.

{id="fig-ch5-admin-first"} The Django admin interface, sans Rango models.

While this looks good, we are missing the Category and Page models that were defined for the Rango app. To include these models, we need to give Django some help.

To do this, open the file rango/admin.py. With an include statement already present, modify the module so that you register each class you want to include. The example below registers both the Category and Page class to the admin interface.

{lang="python",linenos=off} from django.contrib import admin from rango.models import Category, Page

admin.site.register(Category)
admin.site.register(Page)

Adding further classes which may be created in the future is as simple as adding another call to the admin.site.register() method.

With these changes saved, restart the Django development server and revisit the admin interface at http://127.0.0.1:8000/admin/. You will now see the Category and Page models, as shown below.

{id="fig-ch5-admin-second"} The Django admin interface, complete with Rango models.

Try clicking the Categorys link within the Rango section. From here, you should see the test category that we created earlier via the Django shell.

X> ### Experiment with the Admin Interface X> You'll be using the admin interface quite a bit to verify data is stored correctly as you develop the Rango app. Experiment with it, and see how it all works. The interface is self-explanatory and straightforward to understand. X> X> Delete the test category that was previously created. We'll be populating the database shortly with more example data.

I> ### User Management I> The Django admin interface is your port of call for user management, through the Authentication and Authorisation section. Here, you can create, modify and delete user accounts, and varying privilege levels.

T> ### Plural vs. Singular Spellings T> Note the typo within the admin interface (Categorys, not Categories). This typo can be fixed by adding a nested Meta class into your model definitions with the verbose_name_plural attribute. Check out a modified version of the Category model below for an example, and Django's official documentation on models for more information about what can be stored within the Meta class. T> T> {lang="python",linenos=off} T> class Category(models.Model): T> name = models.CharField(max_length=128, unique=True) T> T> class Meta: T> verbose_name_plural = 'Categories' T>
T> def str(self): T> return self.name

I> ### Expanding admin.py I> It should be noted that the example admin.py module for your Rango app is the most simple, functional example available. However you can customise the Admin interface in a number of ways. Check out the official Django documentation on the admin interface for more information if you're interested.

Creating a Population Script {#section-models-population}

Entering test data into your database tends to be a hassle. Many developers will add in some bogus test data by randomly hitting keys, like wTFzmN00bz7. Rather than do this, it is better to write a script to automatically populate the database with realistic and credible data. This is because when you go to demo or test your app, you'll see some good examples in the database. Also, if you are deploying the app or sharing it with collaborators, then you/they won't have to go through the process of putting in sample data. It's therefore good practice to create what we call a population script.

To create a population script for Rango, start by creating a new Python module within your Django project's root directory (e.g. <workspace>/tango_with_django_project/). Create the populate_rango.py file and add the following code.

{lang="python",linenos=on} import os os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tango_with_django_project.settings')

import django
django.setup()
from rango.models import Category, Page

def populate():
    # First, we will create lists of dictionaries containing the pages
    # we want to add into each category.
    # Then we will create a dictionary of dictionaries for our categories.
    # This might seem a little bit confusing, but it allows us to iterate
    # through each data structure, and add the data to our models.
    
    python_pages = [
        {"title": "Official Python Tutorial",
         "url":"http://docs.python.org/2/tutorial/"},
        {"title":"How to Think like a Computer Scientist",
         "url":"http://www.greenteapress.com/thinkpython/"},
        {"title":"Learn Python in 10 Minutes",
         "url":"http://www.korokithakis.net/tutorials/python/"} ]
    
    django_pages = [
        {"title":"Official Django Tutorial",
         "url":"https://docs.djangoproject.com/en/1.9/intro/tutorial01/"},
        {"title":"Django Rocks",
         "url":"http://www.djangorocks.com/"},
        {"title":"How to Tango with Django",
         "url":"http://www.tangowithdjango.com/"} ]
    
    other_pages = [
        {"title":"Bottle",
         "url":"http://bottlepy.org/docs/dev/"},
        {"title":"Flask",
         "url":"http://flask.pocoo.org"} ]
    
    cats = {"Python": {"pages": python_pages},
            "Django": {"pages": django_pages},
            "Other Frameworks": {"pages": other_pages} }
    
    # If you want to add more catergories or pages,
    # add them to the dictionaries above.
    
    # The code below goes through the cats dictionary, then adds each category,
    # and then adds all the associated pages for that category.
    # if you are using Python 2.x then use cats.iteritems() see
    # http://docs.quantifiedcode.com/python-anti-patterns/readability/
    # for more information about how to iterate over a dictionary properly.
    
    for cat, cat_data in cats.items():
        c = add_cat(cat)
        for p in cat_data["pages"]:
            add_page(c, p["title"], p["url"])
    
    # Print out the categories we have added.
    for c in Category.objects.all():
        for p in Page.objects.filter(category=c):
            print("- {0} - {1}".format(str(c), str(p)))

def add_page(cat, title, url, views=0):
    p = Page.objects.get_or_create(category=cat, title=title)[0]
    p.url=url
    p.views=views
    p.save()
    return p

def add_cat(name):
    c = Category.objects.get_or_create(name=name)[0]
    c.save()
    return c

# Start execution here!
if __name__ == '__main__':
    print("Starting Rango population script...")
    populate()

T> ### Understand this Code! T> To reiterate, don't simply copy, paste and leave. Add the code to your new module, and then step through line by line to work out what is going on. It'll help with your understanding. T> T> We've explanations below - hopefully you'll learn something new! T> T> You should also note that when you see line numbers along side the code, it indicates that we have listed the entire file, rather than code fragments. It also makes things more difficult for you to copy and paste!

While this looks like a lot of code, what is going on is essentially a series of function calls to two small functions, add_page() and add_cat() defined towards the end of the module. Reading through the code, we find that execution starts at the bottom of the module - look at lines 75 and 76. This is because above this point, we define functions; these are not executed unless we call them. When the interpreter hits if __name__ == '__main__', we call the populate() function.

T> ### What does __name__ == '__main__' Represent? T> The __name__ == '__main__' trick is a useful one that allows a Python module to act as either a reusable module or a standalone Python script. Consider a reusable module as one that can be imported into other modules (e.g. through an import statement), while a standalone Python script would be executed from a terminal/Command Prompt by entering python module.py. T> T> Code within a conditional if __name__ == '__main__' statement will therefore only be executed when the module is run as a standalone Python script. Importing the module will not run this code; any classes or functions will however be fully accessible to you.

E> ### Importing Models E> When importing Django models, make sure you have imported your project's settings by importing django and setting the environment variable DJANGO_SETTINGS_MODULE to be your project's setting file, as demonstrated in lines 1 to 6 above. You then call django.setup() to import your Django project's settings. E> E> If you don't perform this crucial step, you'll get an exception when attempting to import your models. This is because the necessary Django infrastructure has not yet been initialised. This is why we import Category and Page after the settings have been loaded on line 8.

The for loop occupying lines 51-54 is responsible for the calling the add_cat() and add_page() functions repeatedly. These functions are in turn responsible for the creation of new categories and pages. populate() keeps tabs on categories that are created. As an example, a reference to a new category is stored in local variable c - check line 52 above. This is stored because a Page requires a Category reference. After add_cat() and add_page() are called in populate(), the function concludes by looping through all new Category and associated Page objects, displaying their names on the terminal.

I> ### Creating Model Instances I> We make use of the convenience get_or_create() method for creating model instances in the population script above. As we don't want to create duplicates of the same entry, we can use get_or_create() to check if the entry exists in the database for us. If it doesn't exist, the method creates it. If it does, then a reference to the specific model instance is returned. I> I> This helper method can remove a lot of repetitive code for us. Rather than doing this laborious check ourselves, we can make use of code that does exactly this for us. I> I> The get_or_create() method returns a tuple of (object, created). The first element object is a reference to the model instance that the get_or_create() method creates if the database entry was not found. The entry is created using the parameters you pass to the method - just like category, title, url and views in the example above. If the entry already exists in the database, the method simply returns the model instance corresponding to the entry. created is a boolean value; True is returned if get_or_create() had to create a model instance. I> I> This explanation therefore means that the [0] at the end of our call to the get_or_create() returns the object reference only. Like most other programming language data structures, Python tuples use zero-based numbering. I> I> You can check out the official Django documentation for more information on the handy get_or_create() method.

When saved, you can then run your new populations script by changing the present working directory in a terminal to the Django project's root. It's then a simple case of executing the command $ python populate_rango.py. You should then see output similar to that shown below -- the order in which categories are added may vary depending upon how your computer is set up.

{lang="text",linenos=off} $ python populate_rango.py

Starting Rango population script...
- Python - Official Python Tutorial
- Python - How to Think like a Computer Scientist
- Python - Learn Python in 10 Minutes
- Django - Official Django Tutorial
- Django - Django Rocks
- Django - How to Tango with Django
- Other Frameworks - Bottle
- Other Frameworks - Flask

Next, verify that the population script actually populated the database. Restart the Django development server, navigate to the admin interface (at http://127.0.0.1:8000/admin/) and check that you have some new categories and pages. Do you see all the pages if you click Pages, like in the figure shown below?

{id="fig-admin-populated"} The Django admin interface, showing the Page model populated with the new population script. Success!

While creating a population script may take time, you will save yourself time in the long run. When deploying your app elsewhere, running the population script after setting everything up means you can start demonstrating your app straight away. You'll also find it very handy when it comes to unit testing your code.

Workflow: Model Setup {#section-models-databases-workflow}

Now that we've covered the core principles of dealing with Django's ORM, now is a good time to summarise the processes involved in setting everything up. We've split the core tasks into separate sections for you. Check this section out when you need to quickly refresh your mind of the different steps.

Setting up your Database

With a new Django project, you should first tell Django about the database you intend to use (i.e. configure DATABASES in settings.py). You can also register any models in the admin.py module of your app to make them accessible via the admin interface.

Adding a Model

The workflow for adding models can be broken down into five steps.

  1. First, create your new model(s) in your Django application's models.py file.
  2. Update admin.py to include and register your new model(s).
  3. Perform the migration $ python manage.py makemigrations <app_name>.
  4. Apply the changes $ python manage.py migrate. This will create the necessary infrastructure within the database for your new model(s).
  5. Create/edit your population script for your new model(s).

There will be times when you will have to delete your database -- sometimes it's easier to just start afresh. When you want to do this, do the the following. Note that for this tutorial, you are using a SQLite database -- Django does support a variety of other database engines.

  1. If you're running it, stop your Django development server.
  2. For an SQLite database, delete the db.sqlite3 file in your Django project's directory. It'll be in the same directory as the manage.py file.
  3. If you have changed your app's models, you'll want to run the $ python manage.py makemigrations <app_name> command, replacing <app_name> with the name of your Django app (i.e. rango). Skip this if your models have not changed.
  4. Run the $ python manage.py migrate to create a new database file (if you are running SQLite), and migrate database tables to the database.
  5. Create a new admin account with the $ python manage.py createsuperuser command.
  6. Finally, run your population script again to insert credible test data into your new database.

X> ### Exercises X> Now that you've completed this chapter, try out these exercises to reinforce and practice what you have learnt. Once again, note that the following chapters will have expected you to have completed these exercises! If you're stuck, there are some hints to help you complete the exercises below. X> X> * Update the Category model to include the additional attributes views and likes where the default values for each are both zero (0). X> * Make the migrations for your app and then migrate your database to commit the changes. X> * Update your population script so that the Python category has 128 views and 64 likes, the Django category has 64 views and 32 likes, and the Other Frameworks category has 32 views and 16 likes. X> * Delete and recreate your database, populating it with your updated population script. X> * Complete parts two and seven of the official Django tutorial. These sections will reinforce what you've learnt on handling databases in Django, and show you additional techniques to customising the Django admin interface. X> * Customise the admin interface. Change it in such a way so that when you view the Page model, the table displays the category, the name of the page and the url - just like in the screenshot shown below. You will need to complete the previous exercises or at least go through the official Django Tutorial to complete this exercise.

{id="fig-admin-completed"} The updated admin interface Page view, complete with columns for category and URL.

T> ### Exercise Hints T> If you require some help or inspiration to complete these exercises done, here are some hints. T> T> * Modify the Category model by adding two IntegerFields: views and likes. T> * In your population script, you can then modify the add_cat() function to manipulate the value of the new views and likes fields in the Category model. T> * You'll need to add two parameters to the definition of add_cat() so that views and likes values can be passed to the function, as well as a name for the category. T> * You can then use these parameters to set the views and likes fields within the new Category model instance you create within the add_cat() function. The model instance is assigned to variable c in the population script, as defined earlier in this chapter. As an example, you can access the likes field using the notation c.likes. Don't forget to save() the instance! T> * You then need to update the cats dictionary in the populate() function of your population script. Look at the dictionary. Each key/value pairing represents the name of the category as the key, and an additional dictionary containing additional information relating to the category as the value. You'll want to modify this dictionary to include views and likes for each category. T> * The final step involves you modifying how you call the add_cat() function. You now have three parameters to pass (name, views and likes); your code currently provides only the name. You need to add the additional two fields to the function call. If you aren't sure how the for loop works over dictionaries, check out this online Python tutorial. From here, you can figure out how to access the views and likes values from your dictionary. T> * After your population script has been updated, you can move on to customising the admin interface. You will need to edit rango/admin.py and create a PageAdmin class that inherits from admin.ModelAdmin. T> * Within your new PageAdmin class, add list_display = ('title', 'category', 'url'). T> * Finally, register the PageAdmin class with Django's admin interface. You should modify the line admin.site.register(Page). Change it to admin.site.register(Page, PageAdmin) in Rango's admin.py file.

I> ### Tests I> I> We have written a few tests to check if you have completed the exercises. To check your work so far, download the tests.py script from our GitHub repository, and save it within your rango app directory. I> I> To run the tests, issue the following command in the terminal or Command Prompt. I> I> {lang="text",linenos=off} I> $ python manage.py test rango I> I> If you are interested in learning about automated testing, now is a good time to check out the chapter on testing. The chapter runs through some of the basics on how you can write tests to automatically check the integrity of your code.