Book Image

Web Development with Django

By : Ben Shaw, Saurabh Badhwar, Andrew Bird, Bharath Chandra K S, Chris Guest
Book Image

Web Development with Django

By: Ben Shaw, Saurabh Badhwar, Andrew Bird, Bharath Chandra K S, Chris Guest

Overview of this book

Do you want to develop reliable and secure applications which stand out from the crowd, rather than spending hours on boilerplate code? Then the Django framework is where you should begin. Often referred to as a 'batteries included' web development framework, Django comes with all the core features needed to build a standalone application. Web Development with Django takes this philosophy and equips you with the knowledge and confidence to build real-world applications using Python. Starting with the essential concepts of Django, you'll cover its major features by building a website called Bookr – a repository for book reviews. This end-to-end case study is split into a series of bitesize projects that are presented as exercises and activities, allowing you to challenge yourself in an enjoyable and attainable way. As you progress, you'll learn various practical skills, including how to serve static files to add CSS, JavaScript, and images to your application, how to implement forms to accept user input, and how to manage sessions to ensure a reliable user experience. Throughout this book, you'll cover key daily tasks that are part of the development cycle of a real-world web application. By the end of this book, you'll have the skills and confidence to creatively tackle your own ambitious projects with Django.
Table of Contents (17 chapters)
Preface

Many to Many

In this relationship, multiple records in a table can have a relationship with multiple records in a different table. For example, a book can have multiple co-authors and each author (contributor) could have written multiple books. So, this forms a many-to-many relationship between the Book and Contributor tables:

Figure 2.15: Many-to-many relationship between books and co-authors

Figure 2.15: Many-to-many relationship between books and co-authors

In models.py, for the Book model, add the last line as shown here:

class Book(models.Model):
    """A published book."""
    title = models.CharField\
            (max_length=70, \
             help_text="The title of the book.")
    publication_date = models.DateField\
                       (verbose_name=\
                        "Date the book was published.")
    isbn = models.CharField\
           (max_length=20, \
            verbose_name="ISBN number of the book.")
    publisher = models.ForeignKey\
                (Publisher, on_delete=models.CASCADE)
    contributors = models.ManyToManyField\
                   ('Contributor', through="BookContributor")

The newly added contributors field establishes a many-to-many relationship with Book and Contributor using the ManyToManyField field type:

  • models.ManyToManyField: This is the field type to establish a many-to-many relationship.
  • through: This is a special field option for many-to-many relationships. When we have a many-to-many relationship across two tables, if we want to store some extra information about the relationship, then we can use this to establish the relationship via an intermediary table.

For example, we have two tables, namely Book and Contributor, where we need to store the information on the type of contributor for the book, such as Author, Co-author, or Editor. Then the type of contributor is stored in an intermediary table called BookContributor. Here is how the BookContributor table/model looks. Make sure you include this model in reviews/models.py:

class BookContributor(models.Model):
    class ContributionRole(models.TextChoices):
        AUTHOR = "AUTHOR", "Author"
        CO_AUTHOR = "CO_AUTHOR", "Co-Author"
        EDITOR = "EDITOR", "Editor"
    book = models.ForeignKey\
           (Book, on_delete=models.CASCADE)
    contributor = models.ForeignKey\
                  (Contributor, \
                   on_delete=models.CASCADE)
    role = models.CharField\
           (verbose_name=\
            "The role this contributor had in the book.", \
            choices=ContributionRole.choices, max_length=20)

Note

The complete models.py file can be viewed at this link: http://packt.live/3hmFQxn.

An intermediary table such as BookContributor establishes relationships by using foreign keys to both the Book and Contributor tables. It can also have extra fields that can store information about the relationship the BookContributor model has with the following fields:

  • book: This is a foreign key to the Book model. As we saw previously, on_delete=models.CASCADE will delete an entry from the relationship table when the relevant book is deleted from the application.
  • Contributor: This is again a foreign key to the Contributor model/table. This is also defined as CASCADE upon deletion.
  • role: This is the field of the intermediary model, which stores the extra information about the relationship between Book and Contributor.
  • class ContributionRole(models.TextChoices): This can be used to define a set of choices by creating a subclass of models.TextChoices. For example, ContributionRole is a subclass created out of TextChoices, which is used by the roles field to define Author, Co-Author, and Editor as a set of choices.
  • choices: This refers to a set of choices defined in the models, and they are useful when creating Django Forms using the models.

    Note

    When the through field option is not provided while establishing a many-to-many relationship, Django automatically creates an intermediary table to manage the relationship.

One-to-One Relationships

In this relationship, one record in a table will have a reference to only one record in a different table. For example, a person can have only one driver's license, so a person to their driver's license could form a one-to-one relationship:

Figure 2.16: Example of a one-to-one relationship

Figure 2.16: Example of a one-to-one relationship

The OneToOneField can be used to establish a one-to-one relationship, as shown here:

class DriverLicence(models.Model):
    person = models.OneToOneField\
             (Person, on_delete=models.CASCADE)
    licence_number = models.CharField(max_length=50)

Now that we have explored database relationships, let's come back to our bookr application and add one more model there.

Adding the Review Model

We've already added the Book and Publisher models to the reviews/models.py file. The last model that we are going to add is the Review model. The following code snippet should help us do this:

from django.contrib import auth
class Review(models.Model):
    content = models.TextField\
              (help_text="The Review text.")
    rating = models.IntegerField\
             (help_text="The rating the reviewer has given.")
    date_created = models.DateTimeField\
                   (auto_now_add=True, \
                    help_text=\
                    "The date and time the review was created.")
    date_edited = models.DateTimeField\
                  (null=True, \
                   help_text=\
                   "The date and time the review was last edited.")
    creator = models.ForeignKey\
              (auth.get_user_model(), on_delete=models.CASCADE)
    book = models.ForeignKey\
           (Book, on_delete=models.CASCADE, \
            help_text="The Book that this review is for.")

Note

The complete models.py file can be viewed at this link: http://packt.live/3hmFQxn.

The review model/table will be used to store user-provided review comments and ratings for books. It has the following fields:

  • content: This field stores the text for a book review, hence the field type used is TextField as this can store a large amount of text.
  • rating: This field stores the review rating of a book. Since the rating is going to be an integer, the field type used is IntegerField.
  • date_created: This field stores the time and date when the review was written, hence the field type is DateTimeField.
  • date_edited: This field stores the date and time whenever a review is edited. The field type is again DateTimeField.
  • Creator: This field specifies the review creator or the person who writes the book review. Notice that this is a foreign key to auth.get_user_model(), which is referring to the User model from Django's built-in authentication module. It has a field option on_delete=models.CASCADE. This explains that when a user is deleted from the database, all the reviews written by that user will be deleted.
  • Book: Reviews have a field called book, which is a foreign key to the Book model. This is because for a book review application, reviews have to be written, and a book can have many reviews, so this is a many-to-one relationship. This is also defined with a field option, on_delete=models.CASCADE, because once the book is deleted, there is no point in retaining the reviews in the application. So, when a book is deleted, all the reviews referring to the book will also get deleted.

Model Methods

In Django, we can write methods inside a model class. These are called model methods and they can be custom methods or special methods that override the default methods of Django models. One such method is __str__(). This method returns the string representation of the Model instances and can be especially useful while using the Django shell. In the following example, where the __str__() method is added to the Publisher model, the string representation of the Publisher object will be the publisher's name:

class Publisher(models.Model):
    """A company that publishes books."""
    name = models.CharField\
           (max_length=50, \
            help_text="The name of the Publisher.")
    website = models.URLField\
              (help_text="The Publisher's website.")
    email = models.EmailField\
            (help_text="The Publisher's email address.")
    def __str__(self):
        return self.name

Add the _str_() methods to Contributor and Book as well, as follows:

class Book(models.Model):
    """A published book."""
    title = models.CharField\
            (max_length=70, \
             help_text="The title of the book.")
    publication_date = models.DateField\
                       (verbose_name=\
                        "Date the book was published.")
    isbn = models.CharField\
           (max_length=20, \
            verbose_name="ISBN number of the book.")
    publisher = models.ForeignKey\
                (Publisher, \
                 on_delete=models.CASCADE)
    contributors = models.ManyToManyField\
                   ('Contributor', through="BookContributor")
    def __str__(self):
        return self.title
class Contributor(models.Model):
"""
A contributor to a Book, e.g. author, editor, \
co-author.
"""
    first_names = models.CharField\
                  (max_length=50, \
                   help_text=\
                   "The contributor's first name or names.")
    last_names = models.CharField\
                 (max_length=50, \
                  help_text=\
                  "The contributor's last name or names.")
    email = models.EmailField\
            (help_text=\
             "The contact email for the contributor.")
    def __str__(self):
        return self.first_names

Migrating the Reviews App

Since we have the entire model file ready, let's now migrate the models into the database, similar to what we did before with the installed apps. Since the reviews app has a set of models created by us, before running the migration, it is important to create the migration scripts. Migration scripts help in identifying any changes to the models and will propagate these changes into the database while running the migration. Execute the following command to create the migration scripts:

python manage.py makemigrations reviews

You should get an output similar to this:

  reviews/migrations/0002_auto_20191007_0112.py
    - Create model Book
    - Create model Contributor
    - Create model Review
    - Create model BookContributor
    - Add field contributors to book
    - Add field publisher to book

Migration scripts will be created in a folder named migrations in the application folder. Next, migrate all the models into the database using the migrate command:

python manage.py migrate reviews

You should see the following output:

Operations to perform:
  Apply all migrations: reviews
Running migrations:
  Applying reviews.0002_auto_20191007_0112... OK

After executing this command, we have successfully created the database tables defined in the reviews app. You may use DB Browser for SQLite to explore the tables you have just created after the migration. To do so, open DB Browser for SQLite, click the Open Database button (Figure 2.17), and navigate to your project directory:

Figure 2.17: Click the Open Database button

Figure 2.17: Click the Open Database button

Select the database file named db.sqlite3 to open it (Figure 2.18).

Figure 2.18: Locating db.sqlite3 in the bookr directory

Figure 2.18: Locating db.sqlite3 in the bookr directory

You should now be able to browse the new sets of tables created. The following figure shows the database tables defined in the reviews app:

Figure 2.19: Database tables as defined in the reviews app

Figure 2.19: Database tables as defined in the reviews app