Models and Database Structure
In this chapter, we will cover the following topics:
- Using model mixins
- Creating a model mixin with URL-related methods
- Creating a model mixin to handle creation and modification dates
- Creating a model mixin to take care of meta tags
- Creating a model mixin to handle generic relations
- Handling multilingual fields
- Working with model translation tables
- Avoiding circular dependencies
- Adding database constraints
- Using migrations
- Changing a foreign key to the many-to-many field
When you start a new app, the first thing that you do is create the models that represent your database structure. We are assuming that you have already created Django apps, or, at the very least, have read and understood the official Django tutorial. In this chapter, you will see a few interesting techniques that will make your database structure consistent across the different apps in your project. Then, you will see how to handle the internationalization of the data in your database. After that, you will learn how to avoid circular dependencies in your models and how to set database constraints. At the end of the chapter, you will see how to use migrations to change your database structure during the process of development.
To work with the code in this book, you will need the latest stable version of Python, the MySQL or PostgreSQL database, and a Django project with a virtual environment.
You can find all the code for this chapter in the ch02 directory in the GitHub repository at: https://github.com/PacktPublishing/Django-3-Web-Development-Cookbook-Fourth-Edition.
Using model mixins
In object-oriented languages, such as Python, a mixin class can be viewed as an interface with implemented features. When a model extends a mixin, it implements the interface and includes all of its fields, attributes, properties, and methods. The mixins in Django models can be used when you want to reuse the generic functionalities in different models multiple times. The model mixins in Django are abstract base model classes. We will explore them in the next few recipes.
First, you will need to create reusable mixins. A good place to keep your model mixins is in a myproject.apps.core app. If you create a reusable app that you will share with others, keep the model mixins in the reusable app...
Creating a model mixin with URL-related methods
For every model that has its own distinct detail page, it is good practice to define the get_absolute_url() method. This method can be used in templates and also in the Django admin site to preview the saved object. However, get_absolute_url() is ambiguous, as it returns the URL path instead of the full URL.
In this recipe, we will look at how to create a model mixin that provides simplified support for model-specific URLs. This mixin will enable you to do the following:
- Allow you to define either the URL path or the full URL in your model
- Generate the other URL automatically, based on the one that you defined
- Define the get_absolute_url() method behind the scenes
Creating a model mixin to handle creation and modification dates
It is common to include timestamps in your models for the creation and modification of your model instances. In this recipe, you will learn how to create a simple model mixin that saves the creation and modification dates and times for your model. Using such a mixin will ensure that all of the models use the same field names for the timestamps, and have the same behaviors.
If you haven't yet done so, create the myproject.apps.core package to save your mixins. Then, create the models.py file in the core package.
How to do it...
Creating a model mixin to take care of meta tags
When you optimize your site for search engines, you not only have to use semantic markup for each page, but you also have to include appropriate meta tags. For maximum flexibility, it helps to have a way to define content for common meta tags, specific to objects that have their own detail pages on your website. In this recipe, we will look at how to create a model mixin for the fields and methods related to the keyword, description, author, and copyright meta tags.
As detailed in the previous recipes, make sure that you have the myproject.apps.core package for your mixins. Also, create a directory structure, templates/utils/includes/, under the package, and inside...
Creating a model mixin to handle generic relations
Aside from normal database relationships, such as a foreign-key relationship or a many-to-many relationship, Django has a mechanism to relate a model to an instance of any other model. This concept is called generic relations. For each generic relation, we save the content type of the related model as well as the ID of the instance of that model.
In this recipe, we will look at how to abstract the creation of generic relations in the model mixins.
For this recipe to work, you will need to have the contenttypes app installed. It should be in the INSTALLED_APPS list in the settings, by default, as shown in the following code:
Handling multilingual fields
Django uses the internationalization mechanism to translate verbose strings in the code and templates. But it's up to the developer to decide how to implement the multilingual content in the models. We'll show you a couple of ways for how to implement multilingual models directly in your project. The first approach will be using language-specific fields in your model.
This approach has the following features:
- It is straightforward to define multilingual fields in the model.
- It is simple to use the multilingual fields in database queries.
- You can use contributed administration to edit models with the multilingual fields, without additional modifications.
- If you need to, you can effortlessly show all of the translations of an object in the same template.
- After changing the amount of languages in the settings, you will need to create and run...
Working with model translation tables
The second approach to handling multilingual content in the database involves using model translation tables for each multilingual model.
The features of this approach are as follows:
- You can use contributed administration to edit translations as inlines.
- After changing the amount of languages in the settings, no migrations or other further actions are necessary.
- You can effortlessly show the translation of the current language in the template, but it would be more difficult to show several translations in specific languages on the same page.
- You have to know and use a specific pattern described in this recipe for creating model translations.
- It's not that simple to use this approach for database queries, but, as you will see, it's still possible.
Avoiding circular dependencies
When developing Django models, it is very important to avoid circular dependencies especially in the models.py files. Circular dependencies are imports in different Python modules from each other. You should never cross-import from the different models.py files, because that causes serious stability issues. Instead, if you have interdependencies, you should use the actions described in this recipe.
Let's work with categories and ideas apps to illustrate how to deal with cross dependencies.
How to do it...
Follow these practices...
Adding database constraints
For better database integrity, it's common to define database constraints, telling some fields to be bound to fields of other database tables, making some fields unique or not null. For advanced database constraints, such as making the fields unique with a condition or setting specific conditions for the values of some fields, Django has special classes: UniqueConstraint and CheckConstraint. In this recipe, you will see a practical example of how to use them.
Let's start with the ideas app and the Idea model that will have at least title and author fields.
How to do it......
In Agile software development, requirements for the project evolve and get updated from time to time in the process of development. As development happens iteratively, you will have to perform database schema changes along the way. With Django migrations, you don't have to change the database tables and fields manually, as most of it is done automatically, using the command-line interface.
Activate your virtual environment in the command-line tool, and change the active directory to your project's directory.
How to do it...
Changing a foreign key to the many-to-many field
This recipe is a practical example of how to change a many-to-one relation to a many-to-many relation, while preserving the already existing data. We will use both schema and data migrations in this situation.
Let's suppose that you have the Idea model, with a foreign key pointing to the Category model.
- Let's define the Category model in the categories app, as follows:
from django.db import models
from django.utils.translation import gettext_lazy as _
from myproject.apps.core.model_fields import MultilingualCharField
title = MultilingualCharField(